简体   繁体   English

活动恢复时的Android Innerclass TextView参考

[英]Android Innerclass TextView reference when activity resumes

I have a inner class that extends CountDownTimer. 我有一个扩展CountDownTimer的内部类。 Basically its a simple countdown timer that updates a TextView in the activity and plays a sound when the timer is finished. 基本上,它是一个简单的倒数计时器,可在活动中更新TextView并在计时器结束时播放声音。 The code for the inner class is: 内部类的代码是:

public class SetTimer extends CountDownTimer
{

    public SetTimer(long millisInFuture, long countDownInterval)
        {
            super(millisInFuture, countDownInterval);
        }

    @Override
    public void onFinish()
        {
            timeLeft.setText("0");
            Uri notification = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
            Ringtone r=RingtoneManager.getRingtone(getApplicationContext(), notification);
            r.play();

        }

    @Override
    public void onTick(long millisUntilFinished)
        {
            String t;
            t=String.format("%02d:%02d", TimeUnit.MILLISECONDS.toMinutes(millisUntilFinished), TimeUnit.MILLISECONDS.toSeconds(millisUntilFinished)
                    -TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millisUntilFinished)));
            timeLeft.setText(t);
        }

}

The code that creates and references the TextView is: 创建和引用TextView的代码是:

TextView timeLeft;

and in the onCreate method: 并在onCreate方法中:

timeLeft=(TextView) findViewById(R.id.txtTimeLeft);

This works fine until I rotate the display. 直到我旋转显示器,这都可以正常工作。 At that point the timer is still running and does play the sound at the end but it doesn't update the TextView. 那时,计时器仍在运行,并在最后播放声音,但不会更新TextView。 The TextView is declared at the top of the class and referenced in the onCreate method of the activity. TextView在类的顶部声明,并在活动的onCreate方法中引用。 If I restart the timer then it works. 如果我重新启动计时器,则可以使用。 I used Log.d to check if the onTick method was still getting called and it was. 我使用Log.d来检查onTick方法是否仍在被调用。 My guess is that the reference to the TextView has changed but I can't figure out how to set it back to the timer. 我的猜测是对TextView的引用已更改,但我不知道如何将其设置回计时器。 I tried declaring a TextView in the onTick method and updating that figuring it would then pick up a reference to the current instance of the TextView but that also didn't work. 我尝试在onTick方法中声明一个TextView并进行更新,以确保它随后将获取对TextView当前实例的引用,但这也不起作用。 The only other thing to note is that the SetTimer object is created when the user clicks on a button. 唯一要注意的是,当用户单击按钮时将创建SetTimer对象。 That code is: 该代码是:

timer=new SetTimer(interval, 100);

timer.start();

Any thoughts on how to have the SetTimer keep updating the TextView after the screen is rotated? 关于旋转屏幕后如何让SetTimer保持更新T​​extView的任何想法?

Update I completely rewrote the answer as I did not notice a terrible thing in your code at first glance. 更新我完全重写了答案,因为乍一看我没有发现代码中有什么可怕的事情。

You are possibly leaking resources and the fact your app does not crash - could be just a matter of time. 您可能正在泄漏资源,而您的应用程序不会崩溃-这可能只是时间问题。 First of all, your inner SetTimer class implicitly holds a reference to Activity (I guess, you declared this class in Activity, did not you?). 首先,您的内部SetTimer类隐式持有对Activity的引用(我想您是在Activity中声明了这个类,对吗?)。 That prevents your Activity from being garbage collected, and I guess, that's why you do not get an Exception when setting a value to a TextView that is "out-of-sight". 这样可以防止对您的Activity进行垃圾回收,而且我想这就是为什么在为“视力不佳 ”的TextView设置值时没有得到Exception的原因。

So you should either declare your class as private static class , static class (inner classes), or public class (in it's own file). 因此,您应该将您的类声明为私有静态类静态类 (内部类)或公共类 (在其自己的文件中)。 That way, you will not hold an implicit reference to your Activity and will not cause a memory leak when it gets destroyed. 这样,您将不会持有对Activity的隐式引用,并且在销毁Activity时不会导致内存泄漏。

But now you won't be able to access a textview directly, as it is a member of your Activity class. 但是现在您将无法直接访问textview ,因为它是Activity类的成员。 Let's solve it that way: 让我们以这种方式解决它:

  1. Declare an interface inside SetTimer: 在SetTimer中声明一个接口:

     interface OnTickUpdateListener{ public void onTickUpdate(String text); } 
  2. Declare an instance of such interface in your SetTimer and modify constructor: 在SetTimer中声明此类接口的实例并修改构造函数:

     public class SetTimer extends ... {//this class IS IN IT's OWN FILE!!!! private OnTickUpdateListener listener; public void registerListener(OnTickUpdateListener listener){ this.listener = listener; } public void unregisterListener(){ this.listener = null; } ... } 
  3. Let's trigger the listener when the timer ticks: 让我们在计时器计时时触发监听器:

     @Override public void onTick(long millisec){ if(listener != null){ String t; t = String.valueOf(millisec);//or whatever listener.onTickUpdate(t); } } 
  4. Now, make your Activity implement your interface: 现在,使您的活动实现您的界面:

     public class MyActivity extends Activity implements SetTimer.OnTickUpdateListener{ @Override public void onTickUpdate(String text){ textView.setText(text); } 

Now to the harder part. 现在到更困难的部分。 We need to save a SetTimer instance when Activity is destroyed. 当Activity被销毁时,我们需要保存一个SetTimer实例。 That would be a nice trick to put a SetTimer inside a retained Fragment invisible to user, that would work much like an "immortal container" :) 将SetTimer放在对用户不可见的保留Fragment中,这将是一个很好的技巧,它的工作方式就像“不朽的容器” :)

  1. Create a Fragment class: 创建一个Fragment类:

     public class MyFragment extends Fragment{ public static final String TAG = MyFragment.class.getSimpleName(); private SetTimer timer; private static final int interval = 10; private MyActivity myActivity; @Override public void onAttach(Activity activity){ super.onAttach(activity); this.myActivity = (MyActivity) activity; } @Override public void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); setRetainInstanceState(true); timer = new SetTimer(interval, 10); timer.start(); } @Override public void onActivityCreated(Bundle state){ super.onActivityCreated(state); timer.registerListener(myActivity);//activity should receive ticks } @Override public void onDetach(){ super.onDetach(); timer.unregisterListener();//ensure we do not post a result to non-existing Activity } } 
  2. And last, add your MyFragment in onCreate method of MyActivity: 最后,在MyActivity的onCreate方法中添加MyFragment:

     public class MyActivity extends Activity implements SetTimer.OnTickUpdateListener{ private MyFragment fragment; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); FragmentManager fm = getFragmentManager(); fragment = (MyFragment) fm.findFragmentByTag(MyFragment.TAG); if(fragment == null){ fragment = new MyFragment(); fm.beginTransaction().add(R.id.container, fragment, MyFragment.TAG).commit(); } } 

That way, we restore the existing fragment upon Activity recreation, and MyFragment registers new MyActivity as a listener, which will receive tick updates. 这样,我们将在Activity重新创建时恢复现有的片段,并且MyFragment将新的MyActivity注册为侦听器,该侦听器将接收刻度更新。

PS: I wrote this from scratch and did not test it, so if you encounter any error - please post them so we could work the out. PS:我是从头开始编写的,并未对其进行测试,因此,如果您遇到任何错误-请张贴它们,以便我们解决。

As your reference to textview is getting changed I am guessing that you have added 随着您对textview的引用的更改,我想您已经添加了

android:configChanges="keyboardHidden|orientation|screenSize"

in your AndroidManifest so that activity does not get restarted on rotation. 在您的AndroidManifest中,这样活动就不会在旋转时重新启动。

Here is my MainActivity code that should work for you 这是我的MainActivity代码,适合您

public class MainActivity extends Activity {

TextView timeLeft;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    timeLeft = (TextView)findViewById(R.id.textview);
    SetTimer st=new SetTimer(10000, 1000);
    st.start();
}


@Override
public boolean onCreateOptionsMenu(Menu menu) {
    // Inflate the menu; this adds items to the action bar if it is present.
    getMenuInflater().inflate(R.menu.main, menu);

    return true;
}


@Override
public void onConfigurationChanged(Configuration newConfig) {
    // TODO Auto-generated method stub
    super.onConfigurationChanged(newConfig);
    setContentView(R.layout.activity_main);
    timeLeft = (TextView)findViewById(R.id.textview);
}


public class SetTimer extends CountDownTimer
{

    public SetTimer(long millisInFuture, long countDownInterval)
        {
            super(millisInFuture, countDownInterval);
        }

    @Override
    public void onFinish()
        {
            timeLeft.setText("0");
            Uri notification = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
            Ringtone r=RingtoneManager.getRingtone(getApplicationContext(), notification);
            r.play();

        }

    @Override
    public void onTick(long millisUntilFinished)
        {
            String t;
            t=String.format("%02d:%02d", TimeUnit.MILLISECONDS.toMinutes(millisUntilFinished), TimeUnit.MILLISECONDS.toSeconds(millisUntilFinished)
                    -TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millisUntilFinished)));
            timeLeft.setText(t);
        }

}

} }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM