活动恢复时的Android Innerclass TextView参考

时间:2014-03-19 15:50:05

标签: android

我有一个扩展CountDownTimer的内部类。基本上它是一个简单的倒计时器,可以更新活动中的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);
        }

}

创建和引用TextView的代码是:

TextView timeLeft;

并在onCreate方法中:

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

这可以正常工作,直到我旋转显示器。此时计时器仍在运行并且在结束时播放声音,但它不会更新TextView。 TextView声明在类的顶部,并在活动的onCreate方法中引用。如果我重新启动计时器然后它工作。我使用Log.d来检查onTick方法是否仍然被调用,它是。我的猜测是对TextView的引用已经改变,但我无法弄清楚如何将它设置回计时器。我尝试在onTick方法中声明一个TextView并更新,然后确定它会选择对TextView的当前实例的引用,但这也没有用。唯一需要注意的是,当用户单击按钮时会创建SetTimer对象。该代码是:

timer=new SetTimer(interval, 100);

timer.start();

有关如何让屏幕旋转后如何让SetTimer不断更新TextView的想法?

2 个答案:

答案 0 :(得分:5)

更新我完全重写了答案,因为乍一看我的代码中没有发现可怕的事情。

您可能正在泄漏资源,而且您的应用程序不会崩溃 - 这可能只是时间问题。首先,你的内部 SetTimer 类隐含了对 Activity 的引用(我猜你在Activity中声明了这个类,不是吗?)。这可以防止您的活动被垃圾回收,我想,这就是为什么在将值设置为“超出范围的 TextView 时不会出现异常的原因”

因此,您应该将您的课程声明为私有静态课程静态课程(内部课程)或公共课程(在其自己的课程中)文件)。这样,您就不会对Activity进行隐式引用,并且在销毁时不会导致内存泄漏。

但现在您无法直接访问 textview ,因为它是活动类的成员。让我们这样解决:

  1. 在SetTimer中声明一个接口:

    interface OnTickUpdateListener{
        public void onTickUpdate(String text);
    }
    
  2. 在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. 当计时器滴答时,让我们触发监听器:

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

    public class MyActivity extends Activity implements SetTimer.OnTickUpdateListener{
        @Override
        public void onTickUpdate(String text){
            textView.setText(text);
    }
    
  5. 现在更难的部分。我们需要在销毁Activity时保存SetTimer实例。这将是一个很好的技巧,将SetTimer置于保留的片段中对用户不可见,这将像“不朽的容器”一样工作:)

    1. 创建一个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. 最后,在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();
      
              }
          }
      
    3. 这样,我们在Activity重新创建时恢复现有片段,MyFragment将新MyActivity注册为侦听器,它将接收tick更新。

      PS:我从头开始写这个并没有测试过,所以如果你遇到任何错误 - 请发布它们以便我们可以解决。

答案 1 :(得分:0)

由于您对textview的引用发生了变化,我猜你已经添加了

android:configChanges="keyboardHidden|orientation|screenSize"

在您的AndroidManifest中,以便在轮换时不会重新启动活动。

以下是适合您的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);
        }

}

}