标签:
转载请注明出处: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