前言

  前些天,测试MM发现了一个比较奇怪的bug。

  具体表现是:

  1、将app包通过电脑QQ传送到手机QQ上面,点击安装,安装后选择打开app (此间的应用逻辑应该是要触发 【闪屏页Activity】, 然后跳转 【主页Activity】)

  2、然后MM在 【主页Activity】 时按下了 【Home键】,回到桌面

  3、再点击app的icon图标,原谅耿直的我们都是觉得应该直接回到【主页Activity】,但是结果却是又一次触发 【闪屏页Activity】,亮瞎了24K钛合金狗眼的我们觉得这玩法不对吧?

  4、然后,收拾收拾心情开始定位之路吧~

现象分析

  先说说项目结构吧,我们这边的项目需求逻辑是 先进入 【闪屏页Activity】(普通的Activity,启动模式为standard),然后根据一堆初始化操作和判断,一般是接着进入【主页Activity】(Activity的启动模式为singleTask);点击home键不做任何拦截处理,按照系统默认逻辑返回Lanuch桌面。

  也就是说,app的整体交互逻辑并没有特殊之处,并非业务逻辑导致的bug。那么回顾下不同的地方,也就是启动App的入口的区别了,一者是平常的桌面Icon图标启动,一者是QQ安装这类第三方平台启动。我们都知道,桌面启动的话也是通过startActivity这个api通过特定的Intent向ActivityManagerServer发起启动任务;所以我们可以推导出QQ安装启动这类方式也是通过Intent启动对应的App。

  再往下分析的话,可能需要一些前置知识需要了解才能更好的理解。

前置知识

1、Activity的Task管理

  一般来说,整个Android系统的App启动与切换管理依赖于相关Activity的Task的管理。一个Task之中可能含有若干个Activity,为了简便起见,我们这里记录【Task A】的Activity分别为 【A1】 、【A2】等,【Task B】的Activity分别为 【B1】 、【B2】。

那么我们来分析下App之间是怎么切换的。

  假设应用都是单Task应用(相对于大部分的普通App来说,都是采用单一Task来管理的)

  桌面程序App:【TaskA】 ---- 存在Activity有【A1】 ----  其栈的结构为 A1

  应用程序B:【TaskB】 ---- 存在Activity有【B1】【B2】 ---- 其栈的结构为 B1B2

  应用程序C: 【TaskC】 ---- 存在Activity有【C1】【C2】 ---- 其栈的结构为 C1C2

a、那么我们进入桌面时:Task之间的结构是 A1 ---- 也就是只有一个【TaskA】栈(桌面Task),并且位于最前端(这里表现为最后添加的末端)

b、然后我们点击应用程序B的图标,启动B :Task之间的结构是 A1B1B2  ---- 添加了一个【TaskB】,而且【TaskB】也是位于最前端,现在显示的是【TaskB】的B2的Activity的界面

c、接着点击home键: Android对于home做了特殊默认处理,就是会把桌面Task挪到所以Task最前端,Task结构应该变成  B1B2A1 ---- 【TaskA】挪到队列最前端,现在显示的是【TaskA】的A1的Activity的界面,也就是桌面

d、我们再在桌面点击应用程序C的图标,启动C : Task之间的结构变成 B1B2A1C1C2 ---- 添加了一个【TaskC】,而且【TaskC】也是位于最前端,现在显示的是【TaskC】的C2的Activity的界面

从上面的例子,我们可以大致了解到Android是怎么管理不同app之间切换的逻辑:

  我们编写任何一个Activity的时候,都可以在AndroidManifest里面显式指定一个taskAffinity的属性,也就是说该Activity归属于对应taskAffinity的栈;如果没有指定任何taskAffinity,那么该Activity将会直接归属于包名所在的Task之下。而我们启动一个Activity时(这里只讨论standard启动模式),那么回去先搜寻对应的Task是否存在,如果不存在,新建一个Task并将Activity入栈,如果已经存在对应的Task,那么直接在对应Task入栈即可。

那么问题来了:如果我们在上面第d步点击的图片并不是程序C的图标,而是重新点击了程序B的图标,此时【TaskB】是已经存在的了,那么为了不会讲B的入口activity(B1)直接在【TaskB】入栈,而是将【TaskB】挪到前台并不做任何Activity启动的操作呢?

2、桌面的启动管理:

  回头研究下AndroidManifest这个文件,我们轻而易举发现,但凡是App入口Activity,那么一定会包含

<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>

这几行代码。这里到底有什么玄机呢?其实这个就是跟桌面约定好的启动拦截过滤器。因为桌面有一个很明显的需求就是,如果我们再次点击已经在后台的App图标时,是应该将该后台任务挪到前台而不是再次启动该App程序。

而从柯元旦所著的《android内核剖析》一书中有记录如下规则:

  每次启动Intent导致新创建Task的时候,该Task会记录导致其创建的Intent;而如果后续需要有一个新的与创建Intent完全一致(完全一致定位为:启动类,action、category等等全部一样,不可多项也不可缺少),那么该Intent并不会触发Activity的新建启动,而只会将已经存在的对应Task移到前台;这也就是为什么桌面会在再次点击图标时将后台任务挪到前台而不是重新启动App的实现。

  那么为啥要指定入口Activity特定的action和category呢,有一个原因我们可以确定,就是为了让桌面启动app所用的Intent具有特殊性,也就是添加了特别的拦截器,避免其他应用内或者应用间的Intent对于这个启动方式的干扰。

说了这么多,我们可以着手分析上续bug的产生原因了。

原理剖析

  从此我们可以知道QQ安装器其实也就是使用Intent来启动其刚刚安装的那个App,但是问题所在的是:他们的启动Intent并没有跟桌面的启动Intent完全一致!

我们将桌面的Task记为【TaskL】,QQ安装器的Task记为【TaskQ】,我们应用的Task记为【TaskA】,那么分析如下:

进入桌面: L1 ---- L1是单纯的桌面

打开QQ: L1Q1Q2 ---- Q2是安装完毕后询问是否启动对应程序的Activity

点击打开: L1Q1Q2A1A2 ---- A1是入口闪屏页,A2是主页Activity

返回桌面: Q1Q2A1A2L1 ---- 回到桌面页,也就是L1前置

点击A的图标: Q1Q2L1A1A2A1 ---- 找到【TaskA】,挪到前台,由于比对Intent并不是完全一致,所以该请求是新启动Activity,那么把A1添加到对应的【TaskA】中

所以bug出现了,出现了再一次的闪屏页【A1】,问题定位成功!

PS:这里我稍微变种一下,因为一般我们闪屏页都是在启动主页后finish的,而主页一般是singleTask模式

打开QQ: L1Q1Q2 ---- Q2是安装完毕后询问是否启动对应程序的Activity

点击打开: L1Q1Q2A2 ---- A1是入口闪屏页,A2是主页Activity,启动后A1业务逻辑应该finish掉,所以从【TaskA】中挪去

返回桌面: Q1Q2A2L1 ---- 回到桌面页,也就是L1前置

点击A的图标: Q1Q2L1A2A1 -> Q1Q2L1A2A1 ---- 找到【TaskA】,挪到前台,由于比对Intent并不是完全一致,所以该请求是新启动Activity,那么把A1添加到对应的【TaskA】中,然后A1所再一次触发启动主页,但是主页是singleTask模式,所以又回到了上次对应的A2主页,所以现象为再一次出现闪屏页,然后回到原先的主页界面。

解决思路

  1、让腾讯那些第三方平台修正其启动Intent的设置,使其与原声桌面启动Intent保持完全一致。(PS:基本不可能)

  2、自身业务代码规避,我们可以知道,如果是多余的闪屏页入口Activity的话,其基本不可能位于Task的根部,而如果正常启动的话,闪屏页入口Activity必定在多对应的Task的根部位置,那么我们可以从这个地方对于这个bug进行规避,方法就是在闪屏页入口Activity的onCreate代码加入如下一段代码:

// 避免从桌面启动程序后,会重新实例化入口类的activity
if (!this.isTaskRoot()) {
Intent intent = getIntent();
if (intent != null) {
String action = intent.getAction();
if (intent.hasCategory(Intent.CATEGORY_LAUNCHER) && Intent.ACTION_MAIN.equals(action)) {
finish();
return;
}
}
}

问题解决!

http://www.cnblogs.com/net168/p/5722752.html

【转载】Android Bug分析系列:第三方平台安装app启动后,home键回到桌面后点击app启动时会再次启动入口类bug的原因剖析的更多相关文章

  1. Android Bug分析系列:第三方平台安装app启动后,home键回到桌面后点击app启动时会再次启动入口类bug的原因剖析

    前言 前些天,测试MM发现了一个比较奇怪的bug. 具体表现是: 1.将app包通过电脑QQ传送到手机QQ上面,点击安装,安装后选择打开app (此间的应用逻辑应该是要触发 [闪屏页Activity] ...

  2. Cordova Android源代码分析系列一(项目总览和CordovaActivity分析)

    版权声明:本文为博主offbye西涛原创文章.未经博主同意不得转载. https://blog.csdn.net/offbye/article/details/31776833 PhoneGap/Co ...

  3. Android 代码混淆、第三方平台加固加密、渠道分发 完整教程(图文)

    第一步:代码混淆(注意引入的第三方jar) 在新版本的ADT创建项目时,混码的文件不再是proguard.cfg,而是project.properties和proguard-project.txt. ...

  4. 【Android进阶】使用第三方平台ShareSDK实现新浪微博的一键分享功能

    在公司最近的一个项目中,需要实现一键分享功能,在这里我使用的是第三方平台ShareSDK,将使用经验与大家分享 先看效果图 主界面 分享界面 由于第一次使用,所以需要先进行新浪授权,授权界面 分享结果 ...

  5. [转载]Android核心分析

    2013-12-19 15:44:03 转载自: http://blog.csdn.net/column/details/androidcore.html 很好的文章,阅读请跳转到转载链接,转载备以后 ...

  6. 【转】Android总结篇系列:Activity Intent Flags及Task相关属性

    [转]Android总结篇系列:Activity Intent Flags及Task相关属性 同上文一样,本文主要引用自网上现有博文,并加上一些自己的理解,在此感谢原作者. 原文地址: http:// ...

  7. Android总结篇系列:Activity Intent Flags及Task相关属性

    同上文一样,本文主要引用自网上现有博文,并加上一些自己的理解,在此感谢原作者. 原文地址: http://blog.csdn.net/liuhe688/article/details/6761337 ...

  8. [转载] Android逃逸技术汇编

    本文转载自: http://blogs.360.cn/360mobile/2016/10/24/android_escape/ 摘    要 传统逃逸技术涉及网络攻防和病毒分析两大领域,网络攻防领域涉 ...

  9. Android开发数据库之第三方ORM框架(GreenDao)

    移动APP追求追求功能实现的同一时候,用户体验很重要.開始APP的同一时候.要时刻的切换开发人员的角色,如你开发的时候.是 站在APP的开发角色,处于生产者的位置:当你測试的时候.你应该把自己放在用户 ...

随机推荐

  1. 获取GetOpenFileName多选文件名

    void CWriteWnd::OpenFileDialog() { OPENFILENAME ofn; TCHAR szOpenFileNames[*MAX_PATH] = _T("&qu ...

  2. updateByPrimaryKeySelective更新失败

    问题:使用Mybatis中Mapper内置方法updateByPrimaryKeySelective更新失败. 发现:控制台打印出来的sql语句发现where条件出现所有属性. 解决:映射的实体类没有 ...

  3. tensorflow拟合随机生成的三维数据【学习笔记】

    平台信息:PC:ubuntu18.04.i5.anaconda2.cuda9.0.cudnn7.0.5.tensorflow1.10.GTX1060 作者:庄泽彬(欢迎转载,请注明作者) 说明:感谢t ...

  4. system.data.sqlite的源代码下载

    帮助文档 http://system.data.sqlite.org/index.html/doc/trunk/www/index.wiki 历史版本https://system.data.sqlit ...

  5. 【修改帐号信息】Eclipse中修改SVN用户名和密码方法

    由于在svn 的界面中并没有为我们提供直接更换用户名密码的地方,所以一旦我们需要更换用户名的就需要自己想一些办法. 解决方案: 在Eclipse 使用SVN 的过程中大多数人往往习惯把访问SVN 的用 ...

  6. SQLSERVER 内存占用高的处理方式

    https://www.cnblogs.com/srsrd/p/6962982.html 方法一: 方法二: 使用以下语句查找出什么语句占内存最高,针对占内存高的语句进行优化SELECT SS.SUM ...

  7. DPDK无法分出大页面:EAL: No free hugepages reported in hugepages-2048kB 解决方法

    参考: [dpdk-users] Fw: DPDK Error --> EAL: No free hugepages reported in hugepages-2048kB DPDK无法分出连 ...

  8. UVa 11100 旅行2007

    https://vjudge.net/problem/UVA-11100 题意: 给定n个正整数,把它们划分成尽量少的严格递增序列,尽量均分. 思路: 因为必须严格递增,所以先统计每个数字出现的次数, ...

  9. html 画圆

    <html> <head> <script type = "text/javascript" src = "https://d3js.org ...

  10. c++ 多继承 public

    以下代码会报错 #include <iostream> using namespace std; class Sofa { public: Sofa(); ~Sofa(); void si ...