作者:王三端
Activity作为android四大组件之一,地位就不用多说了吧,该组件看起来是比较简单的,但是也涉及到很多知识点,要想全部理解并在合适的业务场景下使用,也是需要一定的技术沉淀,本文主要是对activity一些重要知识点进行总结整理,可能平时不一定用到,但是一定要有所了解。
当然这些知识点并没有设计过多源码部分,比如activity的启动流程什么的,主要是零散的知识点,对于activity的启动流程网上文章太多了,后面自己也准备重新梳理下,好记性不如烂笔头,在不断学习整理的过程中,一定会因为某个知识点而豁然开朗。
1.1 两个页面跳转
从MainActivity跳转到SecordActivity的生命周期,重点关注Main的onPause和onStop与Secord几个关键生命周期的顺序,以及从Secord返回时与Main的生命周期的交叉:
可以发现Main页面的onPause生命周期之后直接执行Secord的onCreate,onStart,onResume,所以onPause生命周期内不要执行耗时操作,以免影响新页面的展示,造成卡顿感。
1.2 弹出Dialog
1.3 横竖屏切换
如横竖屏切换时需要对布局进行适配,可在res下新建layout-port、layout-land目录,并提供相同的xml布局文件,横竖屏切换时即可自动加载相应布局。(前提是未配置configChanges忽略横竖屏影响,否则不会重新加载布局)
1.4 启动模式对生命周期的影响
1、A(singleTask)启动(startActivity)B(standard),再从B启动A,生命周期如下:
A启动B:A_onPause、B_onCreate、B_onStart、B_onResume、A_onStop
第二步:B_onPause、A_onNewIntent、A_onRestart、A_onStart、A_onResume、B_onStop、B_onDestory
2、A(singleTask)启动A,或者A(singleTop)启动A
A_onPause、A_onNewIntent、A_Resume
3、singleInstance模式的activity
多次启动A(singleInstance),只有第一次会创建一个单独的任务栈(全局唯一),再次启动会调用A_onPause、A_onNewIntent、A_Resume。
Activity的启动模式一直是standard、singleTop、singleTask、singleInstance四种,Android 12新增了singleInstancePerTask启动模式,在这里不一一介绍,仅介绍重要知识点。
2.1 singleTask
Activity是一个可以跨进程、跨应用的组件,当你在 A App里打开 B App的Activity的时候,这个Activity会直接被放进A的Task里,而对于B的Task,是没有任何影响的。
从A应用启动B应用,默认情况下启动的B应用的Activity会进入A应用当前页面所在的任务栈中,此时按home建,再次启动B应用,会发现B应用并不会出现A启动的页面(前提是A应用启动的不是B应用主activity,如果是必然一样),而是如第一次启动一般.
如果想要启动B应用的时候出现被A应用启动的页面,需要设置B应用被启动页的launchmode为singleTask,此时从A应用的ActivityA页面启动B应用的页面ActivityB(launchmode为singleTask),发现动画切换方式是应用间切换,此时ActivityB和ActivityA分别处于各自的任务栈中,并没有在一个task中,此时按Home键后,再次点击启动B应用,发现B应用停留在ActivityB页面。
如果想要实现上述效果,除了设置launchmode之外,还可以通过设置allowTaskReparenting属性达到同样的效果,Activity 默认情况下只会归属于一个 Task,不会在多个Task之间跳来跳去,但你可以通过设置来改变这个逻辑,如果你不设置singleTask,而是设置allowTaskReparenting为true,此时从A应用的ActivityA页面启动B应用的页面ActivityB(设置了allowTaskReparenting为true),ActivityB会进入ActivityA的任务栈,此时按Home键,点击启动B应用,会进入ActivityB页面,也就是说ActivityB从ActivityA的任务栈移动到了自己的任务栈中,此时点击返回,会依次退出ActivityB所在任务栈的各个页面,直到B应用退出。
注意:allowTaskReparenting在不同Android版本上表现有所不同,Android9以下是生效的,Android9,10又是失效的,但Android11又修复好了,在使用时一定要好好测试,避免一些因版本差异产生的问题。
2.2 singleInstance
singleInstance具备singleTask模式的所有特性外,与它的区别就是,这种模式下的Activity会单独占用一个Task栈,具有全局唯一性,即整个系统中就这么一个实例,由于栈内复用的特性,后续的请求均不会创建新的Activity实例,除非这个特殊的任务栈被销毁了。以singleInstance模式启动的Activity在整个系统中是单例的,如果在启动这样的Activity时,已经存在了一个实例,那么会把它所在的任务调度到前台,重用这个实例。
2.3 singleInstancePerTask
释义:singleInstancePerTask的作用和singleTask几乎一模一样,不过singleInstancePerTask不需要为启动的Activity设置一个特殊的taskAffinity就可以创建新的task,换句话讲就是设置singleInstancePerTask模式的activity可以存在于多个task任务栈中,并且在每个任务栈中是单例的。
多次启动设置singleInstancePerTask模式的Activity并不会多次创建新的任务栈,而是如singleInstance模式一样,把当前Activity所在的任务栈置于前台展示,如果想每次以新的任务栈启动需要设置FLAG_ACTIVITY_MULTIPLE_TASK和FLAG_ACTIVITY_NEW_DOCUMENT,使用方式如下:
intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
此时,每次启动Activity就会单独创建新的任务栈。
注意:测试需要在Android12的真机或者模拟器上,否则默认为Standard模式
taskAffinity可以指定任务栈的名字,默认任务栈是应用的包名,前提是要和singleTask,singleInstance模式配合使用,standard,singleTop模式无效,当app存在多个任务栈时,如果taskAffinity相同,则在最近任务列表中只会出现处于前台任务栈的页面,后台任务栈会“隐藏”在某处,如果taskAffinity不同,最近任务列表会出现多个任务页面,点击某个就会把该任务栈至于前台。
activity跳转后设置FLAG_ACTIVITY_CLEAR_TASK即可清空任务栈,并不是新建一个任务栈,而是清空并把当前要启动的activity置于栈底,使用场景比如:退出登录跳转到登录页面,可以以此情况activity任务栈。
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK|Intent.FLAG_ACTIVITY_NEW_TASK);
注意:FLAG_ACTIVITY_CLEAR_TASK必须与FLAG_ACTIVITY_NEW_TASK一起使用.
FLAG_ACTIVITY_NEW_TASK
FLAG_ACTIVITY_NEW_TASK并不像起名字一样,每次都会创建新的task任务栈,而是有一套复杂的规则来判断:
//以下代码基于android 12public void startActivity(Intent intent, Bundle options) { warnIfCallingFromSystemProcess(); final int targetSdkVersion = getApplicationInfo().targetSdkVersion; //检测FLAG_ACTIVITY_NEW_TASK if ((intent.getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) == 0 && (targetSdkVersion < Build.VERSION_CODES.N || targetSdkVersion >= Build.VERSION_CODES.P) && (options == null || ActivityOptions.fromBundle(options).getLaunchTaskId() == -1)) { //未设置FLAG_ACTIVITY_NEW_TASK,直接抛出异常 throw new AndroidRuntimeException( "Calling startActivity() from outside of an Activity " + " context requires the FLAG_ACTIVITY_NEW_TASK flag." + " Is this really what you want?"); } //正常启动activity mMainThread.getInstrumentation().execStartActivity( getOuterContext(), mMainThread.getApplicationThread(), null, (Activity) null, intent, -1, options);}
注意:FLAG_ACTIVITY_NEW_TASK的设置效果受到taskAffinity以及其他一些配置的影响,实际使用过程中一定要进行充分测试,并且不同的android版本也会表现不同,极端场景下要仔细分析测试,选择最优方案;
提示:通过adb shell dumpsys activity activities命令可以查看activity任务栈;
正常情况下,app运行在以包名为进程名的进程中,其实android四大组件支持多进程,通过manifest配置process属性,可以指定与包名不同的进程名,即可运行在指定的进程中,从而开启多进程,那么,开启多进程有什么优缺点呢?
多进程下,可以分散内存占用,可以隔离进程,对于比较重的并且与其他模块关联不多的模块可以放在单独的进程中,从而分担主进程的压力,另外主进程和子进程不会相互影响,各自做各自的事,但开启了多进程后,也会带来一些麻烦事,比如会引起Application的多次创建,静态成员失效,文件共享等问题。
所以是否选择使用多进程要看实际需要,我们都知道app进程分配的内存是有限的,超过系统上限就会导致内存溢出,如果想要分配到更多的内存,多进程不失为一种解决方案,但是要注意规避或处理一些多进程引起的问题;
设置多进程的方式:
android:process=":childProcess" //实际上完整的进程名为:包名:childProcess,这种方式声明的属于私有进程。android:process="com.child.process" //完整的进程名即为声明的名字:com.child.process,这种方式声明的属于全局进程。
excludeFromRecents如果设置为true,那么设置的Activity将不会出现在最近任务列表中,如果这个Activity是整个Task的根Activity,整个Task将不会出现在最近任务列表中.
使用Activity Result Api代替,使用方式如下:
private val launcherActivity = registerForActivityResult( ActivityResultContracts.StartActivityForResult()) { Log.e("code","resultCode = "+it.resultCode)}findViewById
简单理解,所谓Deep Link就是可以通过外部链接来启动app或者到达app指定页面的一想技术,比如可以通过点击短信或者网页中的链接来拉起app到指定页面,以达到提供日活或者其他目的,一般流程是可以通过在manifest的activity标签中配置固定的schema来实现这种效果,形如:
然后在网页中就可以通过如下方式来启动当前activity:
你好
格式
:// : / ?
被启动的app可以通过如下方式拿到传递的参数以及schmea配置项:
val host = schemaIntent.data?.hostval path = schemaIntent.data?.pathval schema = schemaIntent.data?.schemeval query = schemaIntent.data?.queryLog.e("schema","host = $host, path = $path, schema = $schema, query = $query")
结果:
注意:
1.intent-filter与Main主Activity搭配使用时,要单独开启一个intent-filter,否则匹配不到。 2.从android12开始,设置了intent-filter标签后,activity的exported必须设置成true,这个要注意(android12之前,其实添加了intent-filter,系统也会默认设置exported为true)。
app link
App link是一种特殊的Deep link,它的作用就是可以使通过网站地址打开app的时候,不需要用户选择使用哪个应用来打开,换种说法就是,我可以设置默认打开次地址的应用,这样一来,就可以直接引导到自己的app。
通过startActivityForResult启动activity,通常会在被启动的activity的合适时机调用setResult来回调数据给上一个页面,然后当前页面返回的时候就会回调onActivityResult,这里要注意setResult的调用时机,请一定要在activity的finish()方法之前调用,否则可能不会生效(不会回调onActivityResult)。
原因如下:
private void finish(int finishTask) { if (mParent == null) { int resultCode; Intent resultData; //会在finish的时候把回调数据赋值 synchronized (this) { resultCode = mResultCode; resultData = mResultData; } ··· if (ActivityClient.getInstance().finishActivity(mToken, resultCode, resultData, finishTask)) { mFinished = true; } } else { mParent.finishFromChild(this); } ···}//setResult对mResultCode,mResultData赋值public final void setResult(int resultCode) { synchronized (this) { mResultCode = resultCode; mResultData = null; }}
由上述代码可以看出,setResult必须在finish之前赋值,才能够在finish的时候拿到需要callback的数据,以便在合适的时机回调onActivityResult;
activity在非正常情况被销毁的时候(非正常情况:横竖屏切换,系统配置发生变化,内存不足后台activity被回收等),当重新回到该activity,系统会重新实例化该对象,如果没有对页面输入的内容进行保存,就会存在内容丢失的情况,此时可以通过onSaveInstanceState来保存页面数据,在onCreate或者onRestoreInstanceState中对数据进行恢复,形如:
override fun onSaveInstanceState(outState: Bundle) { outState.putString("SAVE_KEY","SAVE_DATA") outState.putString("SAVE_KEY","SAVE_DATA2") super.onSaveInstanceState(outState)}//需要判空,savedInstanceState不一定有值override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) if(null != savedInstanceState){ saveData = savedInstanceState.getString("SAVE_KEY") ?: "" saveData2 = savedInstanceState.getString("SAVE_KEY2") ?: "" } setContentView(R.layout.activity_main)}//或者在onRestoreInstanceState恢复数据,无需判空,回调此方法一定有值override fun onRestoreInstanceState(savedInstanceState: Bundle) { saveData = savedInstanceState.getString("SAVE_KEY") ?: "" saveData2 = savedInstanceState.getString("SAVE_KEY2") ?: "" super.onRestoreInstanceState(savedInstanceState)}
注意:请使用onSaveInstanceState(outState: Bundle)一个参数的方法,两个参数的方法和Activity的persistableMode有关。
这里也分享一些珍藏资源,从面试简历模板到大厂面经汇总,从大厂内部技术资料到互联网高薪必读书单,以及Android面试核心知识点(844页)和Android面试题合集2022年最新版(354页)等等,这些资料整理给大家,希望踩过的坑不要再踩,遭遇的技术瓶颈一次性消灭。
如果需要的话,可以顺手帮我点赞评论一下,直接私信我【笔记】免费领取!
01.Android必备底层技术:
02.Framework:
03.Android常用组件:
04.高级UI:
05.Jetpack:
06.性能优化:
如果需要的话,可以顺手帮我点赞评论一下,直接私信我【笔记】免费领取!
07.音视频:
08.开源框架原理:
09.Gradle:
10.kotlin:
11.Flutter:
12.鸿蒙:
如果需要的话,可以顺手帮我点赞评论一下,直接私信我【笔记】免费领取!
Android路漫漫,共勉!
留言与评论(共有 0 条评论) “” |