标签:
转载请注明出处:http://blog.csdn.net/llew2011/article/details/52509515
随着公司新业务的起步由于原有APP_A的包已经很大了,所以上边要求另外开发一款APP_B,要求是APP_A和APP_B账号通用且两个APP可以相互打开。账号通用也就是说在APP_A上登录了那么打开APP_B也就默认是登录状态,这个实现也不复杂就不介绍了;APP相互打开本来也不是难事,但是在测试的过程中发现了一个之前没有遇到的问题,现象如下图的demo所示:
运行现象是在APP_A中打开了APP_B后,这时候在APP_B中进行任何操作都是没问题的,在APP_B不退出的情况下若摁了HOME键切换到桌面后此时再点击APP_A的icon图标打开APP_A时,发现界面竟然是APP_B的界面,当时感觉很诡异,是什么原因导致出现这种现象呢?当时就琢磨着可能是APP_B运行在了APP_A的任务栈中了,于是开始排查代码,在APP_B中响应APP_A的代码如下所示:
<activity android:name="com.llew.wb.A" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="llew" /> </intent-filter> </activity>由于我们APP_A和APP_B约定了相互打开采用scheme的形式,所以响应代码看起来是没有问题的,接着查看在APP_A中打开APP_B的代码,如下所示:
public void openAPP_B1() { Uri uri = Uri.parse("llew://"); Intent intent = new Intent(Intent.ACTION_VIEW, uri); startActivity(intent); }这段就是打开我们APP_B的代码,看上去也没有什么问题,但是为什么会出现上述现象呢?然后我就尝试在openAPP_B中采用另外的方式,代码如下:
public void openAPP_B2() { Intent intent = getPackageManager().getLaunchIntentForPackage("packageName"); if(null != intent) { startActivity(intent); } }
方式二以前使用过并看过这块相关源码,所以首先就想到了通过PackageManager来获取Intent来启动我们的APP_B,运行程序后发现第二种方式是没问题的,那也就是说在第二种中采用PackageManager获取到的Intent肯定是和采用第一种方式获取到的Intent是有区别的,那他们的区别在哪呢?先不说结论我们接着往下看,运行程序通过debug模式分别查看这两种方式获取到的Intent的不同之处:
方式一的intent截图如下所示:
方式二的intent截图如下所示:
通过对比这两种方式的Intent对象可以发现方式二中的intent对象包含了flg属性,而该flg属性的值恰好是Intent.FLAG_ACTIVITY_NEW_TASK的值,这时候豁然开朗了,原来方式二中的Intent添加了FLAG_ACTIVITY_NEW_TASK标记,也就是说采用方式一开打APP_B时的页面是运行在APP_A的任务栈中,而通过方式二打开APP_B的页面运行在了新的任务栈中。为了证明通过方式一打开的APP_B的页面是运行在APP_A的任务栈中,我们可以使用adb shell dumpsys activity activities 命令来查看Activity任务栈的情况,截图如下:
然后我们在方式一中的Intent也添加FLAG_ACTIVITY_NEW_TASK标记在运行一下,使用adb shell dumpsys activity activities 命令查看一下,截图如下:
出现以上问题的原因就是APP_B运行在了APP_A的任务栈中,解决方法也就是在启动APP_B的时候让APP_B运行在新的任务栈中,接下来顺带进入源码看一看通过PackageManager获取到的Intent对象在哪赋值的flag标记吧,在Activity中调用getPackageManager()辗转调用的是其间接父类ContextWrapper的getPackageManager()的方法,源码如下所示:
@Override public PackageManager getPackageManager() { // mBase为Context类型,其实现类为ContextImpl return mBase.getPackageManager(); }ContextWrapper的getPackageManager()方法中调用的是Context的getPackageManager()同名方法,而mBase的实现类为ContextImpl,所以我们直接查看ContextImpl的getPackageManager()方法,源码如下:
@Override public PackageManager getPackageManager() { // 如果mPackageManager非空就直接返回 if (mPackageManager != null) { return mPackageManager; } // 通过ActivityThread获取IPackageManager对象pm IPackageManager pm = ActivityThread.getPackageManager(); if (pm != null) { // 新建ApplicationPackageManager对象并返回 // Doesn't matter if we make more than one instance. return (mPackageManager = new ApplicationPackageManager(this, pm)); } return null; }通过源码我们知道getPackageManger()方法获取的是ApplicationPackageManager对象,获取Intent对象就是调用该对象的getLaunchIntentForPackage()方法,源码如下:
@Override public Intent getLaunchIntentForPackage(String packageName) { // First see if the package has an INFO activity; the existence of // such an activity is implied to be the desired front-door for the // overall package (such as if it has multiple launcher entries). Intent intentToResolve = new Intent(Intent.ACTION_MAIN); intentToResolve.addCategory(Intent.CATEGORY_INFO); intentToResolve.setPackage(packageName); List<ResolveInfo> ris = queryIntentActivities(intentToResolve, 0); // Otherwise, try to find a main launcher activity. if (ris == null || ris.size() <= 0) { // reuse the intent instance intentToResolve.removeCategory(Intent.CATEGORY_INFO); intentToResolve.addCategory(Intent.CATEGORY_LAUNCHER); intentToResolve.setPackage(packageName); ris = queryIntentActivities(intentToResolve, 0); } if (ris == null || ris.size() <= 0) { return null; } // 运行到这里是查找到了符合条件的Intent了,新建Intent Intent intent = new Intent(intentToResolve); // 在这里给Intent添加了我们期待的FLAG_ACTIVITY_NEW_TASK标签 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setClassName(ris.get(0).activityInfo.packageName, ris.get(0).activityInfo.name); // 返回新建的Intent对象 return intent; }
通过源码我们看到在ApplicationPackageManager的getLaunchIntentForPackage()方法中给符合条件的Intent添加了FLAG_ACTIVITY_NEW_TASK标签,而该标签的作用就是为目标Activity开启新的任务栈并把目标Activity放到栈底。
开始讲解Activity的launchMode之前我们先提一下任务和返回栈的概念,以下部分内容参考自官方文档。
好了,用了不小篇幅介绍了任务和返回栈的概念,若要改变返回栈的默认行为,可通过Activity的launchMode以及Intent的Flag标签,我们今天主要讲解的是通过launchMode来改变任务栈的默认行为,Android系统为launchMode提供了四种机制,分别是standard,singleTop,singleTask,singleInstance,为了方便查看任务栈的相关信息,这里给大家说一个命令:adb shell dumpsys activity,如果有对该命令不熟悉的,请自行查阅并掌握。下面我们来逐一讲解launchMode的各个属性值。
if( 发现一个 Task 的 affinity == Activity 的 affinity ){ if(此 Activity 的实例已经在这个 Task 中){ 这个 Activity 启动并且清除顶部的 Acitivity ,通过标识 CLEAR_TOP } else { 在这个 Task 中新建这个 Activity 实例 } } else { // Task 的 affinity 属性值与 Activity 不一样 新建一个 affinity 属性值与之相等的 Task 新建一个 Activity 的实例并且将其放入这个 Task 之中 }
根据以上结果我们已经大致掌握了launchMode的各种特性了,为了深刻理解需要小伙伴们自己动手实验尝试各种情况下的Activity的打开方式。本篇文章到此就结束了,感谢观看(*^__^*) ……
【参考文章:】
1、https://developer.android.com/guide/components/tasks-and-back-stack.htm
2、http://www.songzhw.com/2016/08/09/explain-activity-launch-mode-with-examples/
Android 源码系列之<九>从源码的角度深入理解Activity的launchModel特性
标签:
原文地址:http://blog.csdn.net/llew2011/article/details/52509515