内容简介:android的启动模式是在我们日常开发中经常用使用到,这个也是在面试用经常问到的一个问题。虽然我们对他很熟悉,但也会有些地方了解的太全面,因此写篇文章来来总结这方面的知识。文章主要内容来自《android开发艺术探讨》这本书,在文章的最后这本书的网页版本可供查看。standard:标准启动模式
android的启动模式是在我们日常开发中经常用使用到,这个也是在面试用经常问到的一个问题。虽然我们对他很熟悉,但也会有些地方了解的太全面,因此写篇文章来来总结这方面的知识。文章主要内容来自《android开发艺术探讨》这本书,在文章的最后这本书的网页版本可供查看。
项目源码
目录
- 四中启动模式
- 什么是任务栈
- Activity如何指定需要的任务栈
- TaskAffinity使用场景
- Activity 的Flags
- IntentFilter的匹配规则
- 如何判断隐式启动是否成功
1. 四中启动模式
standard:标准启动模式
- 每启动一个Activity都会重新创建,不管这个实例是否存在。
<!--系统默认启动方式,不需要指定launchMode值--> <activity android:name=".StandardActivity"/> 复制代码
下面内容摘自《android开发艺术探讨》第一章16页底部
在standard模式下,谁启动了这个Activity那么这个Activity就运行在它的任务栈中。例如:ActivityA启动了ActivityB(B为标准模式),那么ActivityB就会进入ActivityA的任务栈中。
启动Activity的时候传入的Context不要是ApplicationContext。如果一定要传,那么一定要设置 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 否则回报下面异常:
singleTop:栈顶复用模式
- 新启动Activity栈顶已经存在,不会重新创建,同时会调用onNewIntent方法
- Activity如果存在,但是不再栈顶,则会重新创建,并将新的Activity压入栈顶。
<activity android:name=".SingleTopActivity" android:launchMode="singleTop"/> 复制代码
singleTask:栈内复用模式
- 只要Activity在栈内存在,多次启动此Activity也不会创建新的实例,并且系统会调用onNewIntent方法
- 如果Activity在栈内存在,但是没有在栈顶,系统会将该Activity之上的Activity全部挤出栈顶,使该Activity位于栈顶。
<activity android:name=".SingleTaskActivity" android:launchMode="singleTask" /> 复制代码
singleInstance:单实例模式
- 该Activity只能单独位于一个任务栈中
<activity android:name=".SingleInstanceActivity" android:launchMode="singleInstance" > 复制代码
生命周期执行:
- singleTask、singleInstance、singleInstance模式下,如果启动该Activity正好在顶部。那么他的生命周期执行为:
onPause-->onNewIntent-->onResume 复制代码
- singleTask、singleInstance模式下,如果栈内有Activity实例,但不在栈顶。那么生命周期执行如下
onNewIntent-->onRestart-->onStart 复制代码
2. 什么是任务栈
查看activity在栈中的情况,可在控制台输入:adb shell dumpsys activity activities 通过搜索关键字 most recent first 快速定位 留意包名
任务栈(Task):
Task特点:
- android的任务栈主要用于存放Activity,遵循先进后出的原则。
- android的任务栈是一个包含了Activity的集合,我们每次打开新的Activity或者关闭一个Activity任务栈中就会增加或减少一个Activity组件。
- 任务栈在没有Activity或者App退出的时候都会被销毁。
- 一个App不止有一个任务栈,任务栈的Activity可以来自不同的App,同一个App的Activity也可以不再一个任务栈中。
3. Activity如何指定需要的任务栈
Activity指定需要启动的任务栈可以用过在配置文件中添加taskAffinity属性来实现。
TaskAffinity特点:
- 默认情况下,Activity启动的任务栈名称为应用包名。
- 如果自己指定该属性值,不能与包名相同,否则相当于没指定。
- 该属于一般配合singleTask或者allowTaskReparenting属性结合使用,在其他情况下没有实际意义。
<activity android:name=".TestActivity" android:launchMode="singleTask" android:taskAffinity="com.test.singleTask.affinity"/> <activity android:name=".Test2ActivityC" android:exported="true" android:allowTaskReparenting="true"/> 复制代码
4. TaskAffinity使用场景
下面这段内容摘自 Activity启动模式与任务栈(Task)全面深入记录(下) 这篇文章。
TaskAffinity与singleTask应用场景
假如现在有这么一个需求,我们的客户端app正处于后台运行,此时我们因为某些需要,让微信调用自己客户端app的某个页面,用户完成相关操作后,我们不做任何处理,按下回退或者当前Activity.finish(),页面都会停留在自己的客户端(此时我们的app回退栈不为空),这显然不符合逻辑的,用户体验也是相当出问题的。我们要求是,回退必须回到微信客户端,而且要保证不杀死自己的app.这时候我们的处理方案就是,设置当前被调起Activity的属性为: LaunchMode=""SingleTask" taskAffinity="com.tencent.mm" 其中com.tencent.mm是借助于 工具 找到的微信包名,就是把自己的Activity放到微信默认的Task栈里面,这样回退时就会遵循“Task只要有Activity一定从本Task剩余Activity回退”的原则,不会回到自己的客户端;而且也不会影响自己客户端本来的Activity和Task逻辑。
TaskAffinity与allowTaskReparenting应用场景
一个e-mail应用消息包含一个网页链接,点击这个链接将出发一个activity来显示这个页面,虽然这个activity是浏览器应用定义的,但是activity由于e-mail应用程序加载的,所以在这个时候该activity也属于e-mail这个task。如果e-mail应用切换到后台,浏览器在下次打开时由于allowTaskReparenting值为true,此时浏览器就会显示该activity而不显示浏览器主界面,同时actvity也将从e-mail的任务栈迁移到浏览器的任务栈,下次打开e-买了时并不会再显示该activity。
Taskffinity与singleTask实例:
注: 如果使用我在GitHub上建立的项目测这个功能的时候,请将TestTaskffinityOrAllowTaskRep.zip这个压缩包解压,并导入AS中。这个压缩包是我在测试的时候写的用于跳转androidreview应用的testTask应用。
testTask应用(简称T应用)MainActivity中有一个按钮A,点击按钮会调用androidreview应用(简称A应用)的SingleTaskActivity。下面是两个应用的主要代码。
testTask应用代码:
switch (v.getId()) {
case R.id.mBnt_ForSingleTask:
//跳转androidreview应用SingleToak页面的按钮方法
ComponentName cnForSingleTask = new ComponentName(
"com.hdd.androidreview",
"com.hdd.androidreview.Patterm.SingleTaskActivity");
intent.setComponent(cnForSingleTask);
startActivity(intent);
break;
}
复制代码
androidreview应用代码:
<activity
android:name=".Patterm.SingleTaskActivity"
android:exported="true"
android:launchMode="singleTask"
android:taskAffinity="cmom.han.testbt.testTask">
</activity>
复制代码
从上面的A应用代码配置信息中可以看到taskAffinity属性配置的是T应用的包名。因此SingleTaskActivity会在T的任务中被创建。假如MainActivity的按钮A点击事件中启动了SingleTaskActivity。那么cmom.han.testbt.testTask任务栈中会存在SingleTaskActivity和MainActivity两个Activity。下面是Activity在栈中的信息。
这时如果按了hone键返回桌面SingleTaskActivity进入后台,然后点击A应用的图标启动的却是A应用的MainActivity。出现这种情况是因为SingleTaskActivity的taskAffinity属性指定的是T应用包名。在T应用的MainActivity启动的SingleTaskActivy是在T应用的任务栈中。
TaskAffinity与allowTaskReparenting实例 T应用的B按钮点击事件代码:
case R.id.mBnt_ForAllowTaskRep:
//allowTaskReparenting模式跳转PattermActivity
intent.setAction("com.hdd.androidreview.PattermActivity");
ComponentName cnForAllowTaskRep = new ComponentName(
"com.hdd.androidreview",
"com.hdd.androidreview.Patterm.PattermActivity");
intent.setComponent(cnForAllowTaskRep);
break;
复制代码
A应用的PattermActivity(简称PActivity)的配置信息:
<activity
android:name=".Patterm.PattermActivity"
android:allowTaskReparenting="true"
android:theme="@style/AppTheme.NoActionBar" >
<intent-filter>
<action android:name="com.hdd.androidreview.PattermActivity"/>
</intent-filter>
</activity>
复制代码
点击T用于的B按钮,会启动A用的PActivity。该Activity的allowTaskReparenting属性为true,那么Activity会被移动到T应用的任务中站创建。当T应用按home返回桌面,再点击A应用;PActivity会被移回A的任务栈中。
T应用调用A应用的PActivity栈内信息
T应用按home返回桌面,在启动A应用栈内信息:
allowTaskReparenting仅限于以standard 和singleTop启动的activity使用
5. Activity 的Flags
指定Activity的启动模式有两种,一种是在AndroidMenifest.xml中指定
<activity
android:name=".Patterm.SingleInstanceActivity"
android:launchMode="singleInstance">
复制代码
另外一种是通过Intent来指定
Intent intent = new Intent(); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); //intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TOP); intent.setClass(context, CycleActivity.class); context.startActivity(intent); 复制代码
注:intent指定的优先级大于xml中指定的优先级;如果两个方式都指定了启动方式,那么系统会以intent指定的启动方式为准。
FLAG_ACTIVITY_NEW_TASK
等同于在xml中配置了singleTask启动码模式
FLAG_ACTIVITY_SINGLE_TOP
等同于在xml中配置了singleTop启动码模式
FLAG_ACTIVITY_CLEAR_TOP
singTask自带该标记。这个标记会清除同一个任务栈中目标Activity之上的Activity。 如果目标Activity采用了standard启动模式,但是任务栈中已经存在了Activity的实例。那么系统会清除任务中该实例以及它上面的Activity,并且会重新创建一个目标Activity实例放入栈顶。
6. IntentFilter的匹配规则
启动Activity有两种,一种为显示调用
Intent intent = new Intent(MainActivity.this, TestActivity.class); startActivity(intent); 复制代码
另一种为隐式调用要配置清单文件
<!--隐式调用-->
<activity
android:name=".Patterm.PattermActivity"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="com.hdd.androidreview.asdf" />
<category android:name="com.hdd.123456" />
<!--比就加上,否则会报错-->
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
复制代码
Activity跳转代码
Intent intent = new Intent();
intent.setAction("com.hdd.androidreview.asdf");
//category非必须指定。如果要指定,一点要和清单文件中填写的一至
// intent.addCategory("com.hdd.123456");
context.startActivity(intent);
复制代码
1. action
- 可以再清单文件中配置多个
- intent指定的action值只要和清单文件中的其中一个字符串值一样,即可匹配成功。
- 如果清单文件中配置了action,那么在intent跳转中必须至少指定且配对成功一个。
<activity
android:name=".Patterm.PattermActivity"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="com.hdd.androidreview.asdf" />
<action android:name="com.hdd.androidreview.qwer" />
<action android:name="12345678" />
<!--必须加上,否则会报错-->
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
//java代码
Intent intent = new Intent();
intent.setAction("12345678");
context.startActivity(intent);
复制代码
2. category
- category可以再清单文件中添加多个
- intent的addCategory()字符串有一个相同就可以匹配成功。
- 他和action区别是,action是要在intent必须要指定的,切至少和一个匹配成功。category可以不用指定。如果指定也是至少和一个匹配成功。
Intent intent = new Intent();
//必须指定一个并匹配成功
intent.setAction("com.hdd.androidreview.asdf");
//category非必须指定。如果要指定,一点要和清单文件中填写的一至
// intent.addCategory("com.hdd.123456");
context.startActivity(intent);
复制代码
3. data
-
data的匹配规则和action类似,如果过滤规则中定义了data,那么intent中必须要匹配data。
-
data有mimeType和URL两部分组成
mimeType为 媒体类型 : image/jpeg、audio/mpeg4-generic以及video/*等。
URL数据结构为:
<scheme>://<host>:<prot>/[<path>|<pathPrefix>|<pathPattern>] 复制代码
例如:
content://com.example.project:200/folder/subfolder/etc http://www.baidu.com:80/search/info 复制代码
Scheme:URL的模式,比如http、file、content等;如果URL没有指定scheme,这个URL是无效的。
Host:URL主机名,比如www.baidu.com,如果未指定,URL无效。
Port:RUL端口号,比如80,只有scheme和host指定了port才有意义。
Path:表示完整的路径信息。 PathPattern:表示完整路径信息,里面可以包含通配符。 PathPrefix:表示路径的前缀信息
data在定义的时候有两种情况需要注意
- 第一种情况
非完整写法,及只指定了mimeType或者只指定了URL。
注:如果只指定了URL,系统会默认设置mimeType的值为content和file
<!-- 隐式调用,只配置URL -->
<activity android:name=".RegulationActivity">
<intent-filter>
<action android:name="12345678" />
<!-- 必须加上,否则会报错 -->
<category android:name="android.intent.category.DEFAULT" />
<data
android:host="www.baidu.com"
android:scheme="http" />
</intent-filter>
</activity>
<!-- 隐式调用,只配置mimeType· -->
<activity android:name=".RegulationActivity">
<intent-filter>
<action android:name="12345678" />
<!-- 必须加上,否则会报错 -->
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="audio/mpeg" />
</intent-filter>
</activity>
复制代码
java代码:
//intent指定只配置了URL的data
intent.setAction("12345678");
intent.setData(Uri.parse("http://www.baidu.com"));
context.startActivity(intent);
//intent指定只配置了mimeType的data
intent.setAction("12345678");
intent.setType("audio/mpeg");
context.startActivity(intent);
复制代码
- 第二种情况
完整写法
注:如果intent指定的为完整的data,必须要使用setDataAndType(),因为setData()和setType()会彼此清楚对方的值。
<!-- 隐式调用 -->
<activity android:name=".RegulationActivity">
<intent-filter>
<action android:name="12345678" />
<!-- 必须加上,否则会报错 -->
<category android:name="android.intent.category.DEFAULT" />
<data
android:mimeType="audio/mpeg"
android:host="www.baidu.com"
android:scheme="http" />
</intent-filter>
</activity>
复制代码
java代码:
//intent完整的data
intent.setAction("12345678");
intent.setDataAndType(Uri.parse("http://www.baidu.com"), "audio/mpeg");
context.startActivity(intent);
复制代码
还有一种特殊写法:
//写法1
<intent-filter>
<data
android:mimeType="audio/mpeg"
android:host="www.baidu.com"
android:scheme="http" />
</intent-filter>
//写法2
<intent-filter>
<data android:mimeType="audio/mpeg" />
<data android:scheme="http" />
<data android:host="www.baidu.com" />
</intent-filter>
复制代码
上面的两个写法上在使用效果上是一样的。
7. 如何判断隐式启动是否成功
第一种,使用intent的resolveActivity
ComponentName componentName = intent.resolveActivity(context.getPackageManager());
if (componentName != null)
context.startActivity(intent);
else
Toast.makeText(context, "匹配不成功", Toast.LENGTH_SHORT).show();
复制代码
第二种,使用PackageManager的resolveActivity
PackageManager packageManager=context.getPackageManager();
ResolveInfo resolveInfo = packageManager.resolveActivity(intent,PackageManager.MATCH_DEFAULT_ONLY);
//判断是否匹配成功
if (resolveInfo != null)
context.startActivity(intent);
else
Toast.makeText(context, "匹配不成功", Toast.LENGTH_SHORT).show();
复制代码
上面的代码中返回ResolveInfo不是最佳的Activity信息,而是所有匹配成功的Activity信息。resolveActivity()填写的第二个参数必须是MATCH_DEFAULT_ONLY。具体原因可以查看《android开发艺术探索》第一章34页,这里就不解释了。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
JavaScript Patterns
Stoyan Stefanov / O'Reilly Media, Inc. / 2010-09-21 / USD 29.99
What's the best approach for developing an application with JavaScript? This book helps you answer that question with numerous JavaScript coding patterns and best practices. If you're an experienced d......一起来看看 《JavaScript Patterns》 这本书的介绍吧!