原标题:谨慎设置启动Activity的launchMode

特斯拉计划在上海投资4200万元,建设一座集研发、生产于一体的超级充电桩工厂。该项目计划于2021年第一季度投产,初期规划年产一万根超级充电桩,主要为V3超级充电桩。

明天就是周六啦,提前祝大家周末愉快!

本篇文章来自伤心的猪大肠的投稿,讲解了Android设置launchMode属性的一些坑,相信会对大家有所帮助!同时也感谢作者贡献的精彩文章!

伤心的猪大肠的博客地址:

/ 背景/

最近在做App的启动优化,为了达到快速启动的效果,将我们的App的闪屏页(SplashActivity显示固定图片)移除掉,换成MainActivity的背景(windowBackground),最后再替换成App的主题,给用户快速响应的体验。

@drawable/flash_bg

//flash_bg.xml

android:gravity= "center"

android:top= "60dp">

android:layout_width= "100dp"

android:layout_height= "100dp"

android:src= "@drawable/ic_splash_logo"/>

AndroidManifest.xml

android:name= ".ui.main.MainActivity"

android:theme= "@style/AppWelcomeTheme"

这样一个 MainActivity 启动的时候,就会先显示一个预览窗口,给用户快速响应的体验。当 activity想要恢复原来 theme,可以通过在调用super.onCreate 和setContentView之前调用 setTheme(R.style.AppTheme),如下:

publicclassMyMainActivityextendsAppCompatActivity{

@Override

protectedvoidonCreate(Bundle savedInstanceState){

// Make sure this is before calling super.onCreate

setTheme(R.style.AppTheme);

super.onCreate(savedInstanceState);

// ...

}

}

但是却优化出了问题, 我们的MainActivity使用的启动模式是SingleTask,我将闪屏页去掉后,无论打开多少页面,将应用推至后台再启动就回到了主页(MainActivity),这是个很严重的问题,还好发现的及时。

问题排查

排查问题的时候,先看看之前的版本有没有该问题(并没有发现问题),再查看我的代码提交记录,发现AndroidManifest.xml中我主要做的修改去移除了闪屏界面,点击App直接启动的是主页MainActivity,但是坑爹的是我还以为是引入dynamic link带来的问题,还以为动态链需要从启动界面依次传递,等我移除所有的动态链后,发现该问题依旧存在,排除该问题。

结果我却又陷入了自我怀疑中,做了好几年的Android开发,什么时候(MainActivity)设置为SingleTask会有这种改变,为什么我一直没发现?难道是最新的api版本的变化带来的修改,为什么新的修改这么坑爹?

然后我又开始用不同版本的虚拟机进行测试,或者设置不同的targetSdkVersion进行测试,结果都一样,每次都是MainActiviy。我又陷入了沉思,这么多年MainActiviy都是用的SingleTask难道都是错觉吗?可是为什么之前的app都没有这个问题。( 其实是之前都有闪屏页SplashActivity,现在没有闪屏页SplashActivity了)

后面又仔细确定了提交记录的内容,发现可能影响的就是我移除了闪屏界面,恢复闪屏页面后果然没这个问题,确定问题后,就是有无闪屏页照成的问题,或者说是启动界面设置为SingleTask造成的问题。后面网上看了一些解决方案, 主要是通过设置启动模式为standard或者SingleTop,然后添加Flag为Intent.FLAG_ACTIVITY_CLEAR_TOP来解决的,或者说达到这SingleTask类似的清栈效果,同时又不会造成每次启动都是MainActivity。

/深入分析/

但网上清一色的文章并没有仔细分析为什么造成该问题,我又看了一些Activity的启动流程源码分析,也只是一笔带过某个方法名,并没有分析到该流程,没办法只能自己动手了。

启动流程图

可以看到图中的Activity.startActivity中的启动模块,然后大概看一下流程,很容易就能看出大致的方法出现在哪里,这就是熟悉启动流程的好处,也是画图的好处。

startActivityUnchecked

先说一下startActivityUnchecked相关代码的大致逻辑,从getReusableIntentActivity中获取一个reusedActivity,因为这个时候是热启动,我们的Activity之前已经创建了,并没有新的Activity要插入栈中,所以返回不为空;

进入if (reusedActivity != null) {判断逻辑,下面isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)条件也成立,又进入下一个逻辑判断,然后判断是否为根Activity,设置启动的Activity为我们的mStartActivity(MainActivity),所以当APP的启动Activity为MainActivity时,同时设置启动模式为SingleTask或者SingleInstance,每次点击app图标看到的界面就是MainActivity。

privateintstartActivityUnchecked( finalActivityRecord r, ActivityRecord sourceRecord,

IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,

intstartFlags, booleandoResume, ActivityOptions options, TaskRecord inTask,

ActivityRecord[] outActivity, booleanrestrictedBgActivity) {

···

//从getReusableIntentActivity中获取

ActivityRecord reusedActivity = getReusableIntentActivity;

···

//不为空时进入该循环

if(reusedActivity != null) {

// When the flags NEW_TASK and CLEAR_TASK are set, then the task gets reused but

// still needs to be a lock task mode violation since the task gets cleared out and

// the device would otherwise leave the locked task.

···

···

// This code path leads to delivering a new intent, we want to make sure we schedule it

// as the first operation, in case the activity will be resumed as a result of later

// operations.

//isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)表示启动模式为或者SingleInstance或者SingleTask时,进入该判断

if((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0

|| isDocumentLaunchesIntoExisting(mLaunchFlags)

|| isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)) {

finalTaskRecord task = reusedActivity.getTaskRecord;

// In this situation we want to remove all activities from the task up to the one

// being started. In most cases this means we are resetting the task to its initial

// state.

//大多数情况下我们可能准备清空当前task或者回到task的初始状态

finalActivityRecord top = task.performClearTaskForReuseLocked(mStartActivity,

mLaunchFlags);

// The above code can remove {@code reusedActivity} from the task, leading to the

// the {@code ActivityRecord} removing its reference to the {@code TaskRecord}. The

// task reference is needed in the call below to

// {@link setTargetStackAndMoveToFrontIfNeeded}.

if(reusedActivity.getTaskRecord == null) {

reusedActivity.setTask(task);

}

if(top != null) {

//是否为根activity

//boolean frontOfTask; // is this the root activity of its task?

if(top.frontOfTask) {

// Activity aliases may mean we use different intents for the top activity,

// so make sure the task now has the identity of the new intent. //设置启动Activity为根Activity

top.getTaskRecord.setIntent(mStartActivity);

}

//将会调用该Activity的onNewIntent,一旦调用了mStartActivity,因为我们也设置了SingleTask或者SingleInstance,所以我们每次看到的都是mStartActivity

deliverNewIntent(top);

}

}

}

···

···

}

先来看看getReusableIntentActivity方法,看看该方法的注释,很快就明白作用了,所以返回的不是null,所以会进入上面的判断逻辑中

/**

* Decide whether the new activity should be inserted into an existing task. Returns null

* if not or an ActivityRecord with the task into which the new activity should be added.

*/

privateActivityRecord getReusableIntentActivity{

// We may want to try to place the new activity in to an existing task. We always

// do this if the target activity is singleTask or singleInstance; we will also do

// this if NEW_TASK has been requested, and there is not an additional qualifier telling

// us to still place it in a new task: multi task, always doc mode, or being asked to

// launch this as a new task behind the current one.

再来看看deliverNewIntent中被调用到的deliverNewIntentLocked,最终决定哪个Activity的onNewIntent会被调用到,也就是我们的mStartActivity

/**

* Deliver a new Intent to an existing activity, so that its onNewIntent

* method will be called at the proper time.

*/

finalvoiddeliverNewIntentLocked( intcallingUid, Intent intent, String referrer){

// The activity now gets access to the data associated with this Intent.

/首次安装问题/

在开发过程中,安装完成一个app时,在安装界面直接点击打开。我们进入了app的首页,这时我们按home键返回桌面,再点击应用图标,会发现没有直接进入首页,而是先进入了app的闪屏页,在进入首页。重复这一步一直如此。这时我们按back键返回,发现没有直接退回桌面,而是返回到之前打开的多个首页。但是如果一开始安装完我们不是直接打开,而是在桌面点击应用进入就不会这样了。

解决方案

if(! this.isTaskRoot) { // 当前类不是该Task的根部,那么之前启动

Intent intent = getIntent;

if(intent != null) {

String action = intent.getAction;

if(intent.hasCategory(Intent.CATEGORY_LAUNCHER) && Intent.ACTION_MAIN.equals(action)) { // 当前类是从桌面启动的

finish; // finish掉该类,直接打开该Task中现存的Activity

return;

}

}

}

/总结/

千万要注意,不要在你的启动界面(如果你想把MainActivity的windowbackground设置为闪屏界面,移除闪屏页,直接启动MainActivity给用户造成快速启动的感觉)设置启动模式为SingleTask或者SingleInstance,一旦设置后,不管软启动或者热启动都是从该启动界面开始启动App,除非特殊的需求,否则千万不要这么设置。如果想要实现类似SingleTask的清栈效果,可以使用singleTop结合对应的Flag进行实现(注意standard无论配合什么flag都会重新创建一个新的实例)。

最后说一下另一种情况,如果你一定想要在启动页设置为SingleTask/SingleInstance(不设置活不下去了),那也有办法,就是添加个闪屏界面(SplashActivity设置为SingleTask/SingleInstance),然后启动MainActivity,这里千万要注意, 闪屏界面(SplashActivity)一定要及时关掉,同时在闪屏页面的onCreate方法中添加如下代码,否则你每次点击app图标都是从闪屏页开始显示了。但是这样上面的移除闪屏页快速启动的优化就没意义了。

@Override

protectedvoidonCreate(@Nullable Bundle savedInstanceState){

super.onCreate(savedInstanceState);

Log.i(TAG, "onCreate: =========");

//关键代码

if(!isTaskRoot) {

Intent intent = getIntent;

if(intent != null) {

if(intent.hasCategory(Intent.CATEGORY_LAUNCHER) && Intent.ACTION_MAIN.equals(intent.getAction)) {

finish;

return;

}

}

}

Intent intent = newIntent( this, MainActivity.class);

startActivity(intent);

finish;

责任编辑:

activity android:launchmode,谨慎设置启动Activity的launchMode相关推荐

  1. Android应用程序内部启动Activity过程(startActivity)的源代码分析

    上文介绍了Android应用程序的启动过程,即应用程序默认Activity的启动过程,一般来说,这种默认Activity是在新的进程和任务中启动的:本文将继续分析在应用程序内部启动非默认Activit ...

  2. Android动画——使用动画启动Activity

    1.使用动画启动Activity概述 我们在Android开发应用时,会遇到一个页面跳转到另一个页面的情况,这时候我们如果使用动画过渡会使得页面更加的流畅. 这是一个滑动式的进入和退出的动画 可以看到 ...

  3. Android Q 限制后台启动Activity

    描述 Android Q限制在没有用户交互的情况下加载Activity.这一变化可以最大限度的减少对用户的打扰,保持用户对屏幕上所显示内容的可控性. 运行在Android Q上的APP仅在以下一种或多 ...

  4. android 10+从后台启动 Activity 的限制

    限制后台启动activity 如果未满足相关条件,则后台不允许启动activity,并会打印如下相关的log: // anything that has fallen through would cu ...

  5. android 退出多个activity,Android 中 退出多个activity的经典方法

    1.使用list集合方式 用list保存activity实例,然后逐一干掉 import java.util.linkedlist; import java.util.list; import and ...

  6. android 启动其他app的activity,Android在一个app中启动其他app中的service或者Activity

    前言: 启动另一个app的activity和service其实是一样的,区别在于startActivity(intent)还是startService(intent)而已:所以下面案例以启动另一个ap ...

  7. android传值给activity,android怎么实现PopuWindow与Activity传值

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 SendAdvicePopu extends PopuWindow{ private Button sendButton = null; private ...

  8. 【Android 应用开发】Activity 任务亲和性 taskAffinity 设置 ( taskAffinity 属性 )

    文章目录 I . 任务亲和性 ( taskAffinity ) 简介 II . 任务亲和性 ( taskAffinity ) 设置 III . 任务亲和性 ( taskAffinity ) 与 FLA ...

  9. Android 7.0 ActivityManagerService(2) 启动Activity的过程:一

    从这一篇博客开始,我们将阅读AMS启动一个Activity的代码流程. 自己对Activity的启动过程也不是很了解,这里就初步做一个代码阅读笔记,为以后的迭代打下一个基础. 一.基础知识 在分析Ac ...

  10. android listview 滑动黑屏,Android 跨进程启动Activity黑屏(白屏)的三种解决方案

    当Android跨进程启动Activity时,过程界面很黑屏(白屏)短暂时间(几百毫秒?).当然从桌面Lunacher启动一个App时也会出现相同情况,那是因为App冷启动也属于跨进程启动Activi ...

最新文章

  1. 机器学习基础-数据降维
  2. 记忆的天空:“崩塌型”记忆活动原理
  3. .NET 产品版权保护方案 (.NET源码加密保护)
  4. 2个js实现图片轮播效果(用)
  5. 面试问题_教资面试,结构化面试问题分享
  6. VC++多线程工作笔记0006---线程间同步机制1
  7. java 送参数_关于java:如何以编程方式发送带参数的HTTP请求?
  8. 【HTML5】页面传递参数给下一个页面
  9. java outputstrea_java的InputStream和OutputStream的理解【转】
  10. 投票系统显示结果--jQuery插件
  11. AWS SQS, SWF and SNS
  12. 第六章-循环控制结构
  13. 【笔记】2022.06.20 python数据分析三大神器numpy、pandas、matplotlib
  14. Python 遗传算法实现字符串
  15. linux防火墙(firewall、iptable)
  16. Pytorch中的dataset类——创建适应任意模型的数据集接口
  17. CUDA:在NPP中直方图均衡化实例
  18. go test 单元函数测试
  19. PyCharm 使用 Sublime-Monokai 配色方案
  20. c语言抽象数据类型的定义,C++问题,定义“有理数”的抽象数据类型

热门文章

  1. Android设置标题栏图标
  2. 活动目录权限委派|父域子域管理
  3. [转]JS弹出div和关闭
  4. 函数 php_PHP丨PHP基础知识之PHP基础入门——函数「理论篇」
  5. js foreach用法_36 个JS 面试题为你助力金九银十(面试必读)
  6. mongodb python 大于_菜鸟成长记--如何根据关键词爬取微博内容?(scrapy+mongodb)
  7. C#利用反射,遍历获得一个类的所有属性名,以及该类的实例的所有属性的值
  8. SQL语句:查询多表更新数据
  9. 2018年全国卷Ⅰ卷理科数学图片版
  10. Windows Azure Cloud Service (25) 使用Startup注册COM组件(下)