喜糖

移动开发工程师 。涉及 android、ios、jni

导航

HOME键与Notification配合使用的bug重现【原创】

Posted on 2011-09-30 17:19  喜糖  阅读(846)  评论(0编辑  收藏  举报
  • 前言

  最近几天刚刚跳槽完毕。在家歇了快一个月了。重现开始上班后,还真有点不适应。上班实在是太辛苦了, 还是坐地铁13号线。但是变成反向乘坐了。 昨天才拿到的电脑,连代码还没看呢,就接到了新的任务:解决一个bug。 好了,废话不多说,先描述一下bug情况。

  • BUG描述     
程序中在某个地方加入一个Notification。把程序全部退出(是finish的那种),用notification来启动程序,进行操作。随便进入了一个页面A,此时点击“home”,然后再长按“HOME”,回到该程序。 结果不能返回到之前的页面A了。
简短分析: 刚开始我认为是程序的问题,加入很多log日志,也没能解决该问题。 最后,我怀疑是android系统的问题。下面我就写了一个简单的代码,来重现该问题。
  • DEMO实例
先来个图,看看这个DEMO的演示情况:
  
结合图像,展示功能列表:
  1. 该程序共有3个Activity。分别从title中进行区别:test,test2, test3.
  2. 消息栏中有个图片,可以使用该图标来启动程序。程序到达test2.
  3. 正常启动程序,到达的activity为test。
  4. test和test2的 Activity中,b按钮的功能是掉转到test3. 
  5. 在test的Activity中,a按钮的功能是生成notification。
代码:
  
public class TestActivity extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}

public void a(View view) {
//启动掉转到Test2
Intent intent = new Intent(TestActivity.this, Test2Activity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP
| Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent contentIntent = PendingIntent.getActivity(TestActivity.this, 0,
intent,PendingIntent.FLAG_UPDATE_CURRENT);

Notification notification = new Notification(R.drawable.icon, "ticker", System.currentTimeMillis());
notification.setLatestEventInfo(TestActivity.this, "title", "text", contentIntent);

NotificationManager notificationMgr = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
notificationMgr.notify(1000, notification);
}

public void b(View view) {
//掉转到Test3
Intent intent = new Intent(TestActivity.this, Test3Activity.class);
startActivity(intent);
}

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if(keyCode == KeyEvent.KEYCODE_BACK) System.exit(0);
return super.onKeyDown(keyCode, event);
}
}
能靠notification启动的Activity:
public class Test2Activity extends Activity {

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}

public void a(View view) {}

public void b(View view) {
//掉转到Test3
Intent intent = new Intent(Test2Activity.this, Test3Activity.class);
startActivity(intent);
}

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if(keyCode == KeyEvent.KEYCODE_BACK) System.exit(0);
return super.onKeyDown(keyCode, event);
}
}
Test3就没有什么代码可以展示了,就是一个表达的区别页面。
  • 重现操作流程
  1. 启动程序。页面为Test Activity。
  2. 点击a按钮,启动一个notification待用。
  3. 点击“back”,关闭该程序。
  
  4. 点击notification,启动程序到达Activity Test2.
  5. 简介b按钮,跳转页面到TEST3.
  6. 点击“HOME”键,回到主界面。
  7. 长按“HOME”,尝试回到之前的程序。bug发生(页面没有回到TEST3,而是到达页面TEST2.更让人奇特的是Test3执行力OnDestory方法)

  • 深入分析
  1。 首先要理解activity的launchMode的值。参考文章:http://marshal.easymorse.com/archives/2950
    我的解析: singleTask是用在一个应用中要共享一个activity。他们的taskId与之前是一样的。当被标记为singleTask的应用已经存在时,就直接跳转到该ACT,并清空上面的ACT。
          singleInstance是用在多个应用共享一个ACT。也就是该ACT的taskID和之前的是不一样的。并且该ACT的栈中,有且仅有那个一个ACT。
       2。 singleTask是否启动新的taskID。
    singleTask并不会每次都新启动一个task。如果已经存在一个task与新活动亲和度(taskAffinity)一样,该活动将启动到该task。如果不是,才启动一个新task。

同一个application里面,每个activity的taskAffinity默认都是一样的。也就是说楼主代码里的ActivityTwo和ActivityMain的taskAffinity是一样的,假设这个taskAffinity是"com.test"。ActivityMain启动时,新建了一个task,这个task的taskAffinity就是"com.test"。ActivityTwo具有singleTask属性,启动时,会先寻找是否有相同taskAffinity的task存在,没有的话,才会启动一个新的task,但是这时发现已经有一个task存在了,它的taskAffinity也是"com.test",就不会启动新的task了,而是把ActivityTwo放入这个task的栈中,这时候task id是一样的。

所以使用singleTask时,要想新建一个task,就得保证taskAffinity值不同,比如设置ActivityTwo的taskAffinity="com.test2",这时再运行楼主的代码,就会发现task id不一样了。

以上是两个activity在同一个application中的情况。如果在某个application调用其他application里声明的singleTask模式的Activity呢。taskAffinity的值默认是包名,两个application一般包名都不一样,如果taskAffinity都是默认的话,它会重新创建一个Task,然后将该Activity实例化并压入task的栈。


  3。 理解了上面的内容。下面就来点关键的。
    在开发者文档中有这样写的内容:
    
  当Intent中包含有Flag_ACTIVITY_NEW_TASK时,
    当有了上面那个属性,只有affinity的值与之前的affinity的值不一致,才考虑去new一个新的之。


  • 解决办法:
在其他的程序中加入:intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT|Intent.FLAG_ACTIVITY_NEW_T‌​ASK);  
//如果activity在task存在,拿到最顶端,不会启动新的Activity
  intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
  //如果activity在task存在,将Activity之上的所有Activity结束掉
  intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
  //默认的跳转类型,将Activity放到一个新的Task中
  intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  //如果Activity已经运行到了Task,再次跳转不会在运行这个Activity
  intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);