Android宝典|Activity必考知识点总结

目录

  1. 思维导图
  2. 概述
  3. 生命周期
    • 正常情况下的生命周期(注意透明 Activity 和 DialogActivity)
    • 异常情况下的生命周期
  4. 启动模式
    • LaunchMode
      • standard
      • singleTop
      • singleTask
      • singleInstance
    • Intent.Flags
      • FLAG_ACTIVITY_NEW_TASK
      • FLAG_ACTIVITY_SINGLE_TOP
      • FLAG_ACTIVITY_CLEAR_TOP
      • FLAG_ACTIVITY_EXCLUDE_FORM_RECENTS
  5. IntentFilter 匹配规则
    • action
    • category
    • data
      • URI
      • mimeType
  6. 重要方法
    • onSaveInstanceState、onRestoreInstanceState
    • startActivityForResult
    • onNewIntent
    • onConfigurationChanged
    • onTirmMemory
  7. 转场动画
    • overridePendingTransition
    • 设置 Application Stype
    • ActivityOptions
  8. 启动流程
    1. Launcher 请求 AMS
    2. AMS 到 ApplicationThread 的调用过程
    3. ActivityThread 启动 Activity
  9. 参考

思维导图

image

概述

Activity 即用户界面。

生命周期

说到生命周期,最经典的一张图:

image

不过还是说一下实例会更好理解一下:

情况 回调
第一次启动 onCreate 、onStart、onResume
从 A 跳转到不透明的 B A_onPause、B_onCreate、B_onStart、B_onResume、A_onStop
从 A 跳转到透明的 B A_onPause、B_onCreate、B_onStart、B_onResume
从不透明的 B 再次回到 A B_onPause、A_onRestart、A_onStart、A_onResume、B_onStop、B_onDestory
从透明的 B 再次回到 A B_onPause、A_onResume、B_onStop、B_onDestory
用户按 home 键 onPause、onStop
按 home 键回到应用 onRestart、onStart、onResume
用户按 back 键会退 onPause、onStop、onDestory

透明 Activity 和 DialogActivity 类似,在跳到 DialogActivity 也不会回调前一个 Activity 的 onStop 方法。

启动模式

LaunchMode
启动模式 说明
standard 标准模式,也是系统的默认模式,每次启动一个 Activity 都会重新创建一个实例
singleTop 栈顶复用,如果新 Activity 已经位于任务栈的栈顶,那么此 Activity 不会被重新创建,同时它的 onNewIntent 方法会被回调
singleTask 栈内复用,只要 Activity 在一个栈中存在,那么多次启动此 Activity 都不会重新创建实例,和 singleTop 一样,系统也会回调其 onNewIntent
singleInstance 单例模式,启动的 Activity 会创建一个新的任务栈并压入栈中,由于栈内复用的特性,后续的请求均不会创建新的 Activity,除非这个任务栈被系统销毁了
情况 回调
从 A(singleTask)startActivity B(standard),B startActivity A 第一步:A_onPause、B_onCreate、B_onStart、B_onResume、A_onStop 第二步:B_onPause、A_onNewIntent、A_onRestart、A_onStart、A_onResume、B_onStop、B_onDestory
在 A (singleTask)startActivity A A_onPause、A_onNewIntent、A_Resume
在 A (singleTop)startActivity A A_onPause、A_onNewIntent、A_Resume

在 singleTask 启动模式中,多次提到某个 Activity 所需的任务栈,什么是 Activity 所需要的任务栈呢?这就要从一个参数说起:taskAffinity,任务相关性。这个参数标识了一个 Activity 所需要的任务栈的名字,默认情况下,所有 Activity 所需的任务栈的名字为应用的包名。当然,我们可以为每个 Activity 都单独指定 taskAffinity 属性,这个属性值必须不能和包名相同,否则相当于没有设置。taskAffinity 属性主要和 singleTask 启动模式和 allowTaskReparenting 属性配对使用,在其他情况下没有意义。

taskAffinity 与 singleTask 配对使用:

如果启动了设置了这两个属性的 Activity,这个 Activity 就会在 taskAffinity 设置的任务栈中。

taskAffinity 与 allowTaskReparenting 配对使用:

当一个应用 A 启动了应用 B 的某个 Activity 后,如果这个 Activity 的 allowTaskReparenting 属性为 true 的话,那么当应用 B 被启动后,此 Activity 会直接从应用 A 的任务栈转移到应用 B 的任务栈中。这个属性主要作用就是将这个 Activity 转移到它所属的任务栈中,例如一个短信应用收到一个带有网络链接的短信,点击链接会跳到浏览器,这时候如果 allowTaskReparenting 设置为 true 的话,打开浏览器应用就会直接显示刚才打开的网页页面,而打开短信应用后这个浏览器界面就会消失。

启动模式了解之后,那是如何指定启动模式的方式呢?

有两种:一种是在 AndroidMenifet 文件设置 launchMode 属性,一种是给 Intent 设置 Flag。

如果两者都存在,后者优先级更高。

Activity 中的 Flags
标记位 说明
FLAG_ACTIVITY_NEW_TASK 为 Activity 指定 singleTask 启动模式,其效果和在 xml 中指定启动模式相同
FLAG_ACTIVITY_SINGLE_TOP 为 Activity 指定 singleTop 启动模式,其效果和在 xml 中指定启动模式相同
FLAG_ACTIVITY_CLEAR_TOP 具有此标记位的 Activity,当启动它时,同一个任务栈中所有位于它上面的 Activity 都要出栈。这个模式一般需要和 FLAG_ACTIVITY_NEW_TASK 配合使用,在这种情况下,被启动的 Activity 实例如果已经存在,那么系统就会调用它的 onNewIntent。如果被启动的 Activity 采用 standard 模式启动,那么它连同它之上的 Activity 都要出栈,系统会创建新的 Activity 实例并放入栈顶
FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS 具有这个标记的 Activity 不会出现在历史的 Activity 的列表中,当某些情况下我们不希望用户通过历史列表回到我们的 Activity 的时候这个标记比较有用。它等同于在 xml 中指定 Activity 的属性 android:excludeFormRecents="true"

IntentFilter 的匹配规则

启动 Activity 分为两种,显式调用和隐式调用。显式调用就不多说了,隐式调用需要 Intent 能够匹配目标组件的 IntentFilter 中所设置的过滤信息,如果不匹配将无法启动目标 Activity。IntentFilter 的过滤信息有 action、category、data。

IntentFilter 需要注意的地方有以下:

  1. 一个 Activity 中可以有多个 intent-filter

  2. 一个 intent-filter 同时可以有多个 action、category、data

  3. 一个 Intent 只要能匹配任何一组 intent-filter 即可启动对应 Activity

  4. 新建的 Activity 必须加上以下这句,代表能够接收隐式调用

    <category android:name="android.intent.category.DEFAULT" />
    
action 的匹配规则

action 的匹配规则就是只要满足其中一个 action 就可以启动成功。

        <activity android:name=".activity.SecondActivity" >
            <intent-filter>
                <action android:name="demo"/>
                <action android:name="demo2"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </activity>
Intent intent = new Intent();
intent.setAction("demo");
startActivity(intent);

注意,action 是区分大小写的。

category 匹配规则

category 在代码设置如下:

intent.addCategory("")

这句可以添加也可以不添加,因为代码默认会为我们匹配 “android:intent.category.DEFAULT”。

data 匹配规则

data 主要是由 URI 和 mimeType 组成的。URI 的结构如下:

<scheme>://<host>:<port>[<path>|<pathPrefix>|<pathPattern>]

语法如下:

<data android:scheme="string"
      android:host="string"
      android:port="string"
      android:path="string"
      android:pathPattern="string"
      android:pathPrefix="string"
      android:mimeType="string" />

实例:

        <activity android:name=".activity.SecondActivity" >
            <intent-filter>
                <action android:name="android.intent.action.VIEW"/>
                <category android:name="android.intent.category.DEFAULT"/>
                <data
                    android:host="omooo"
                    android:port="80"
                    android:scheme="demo"/>
            </intent-filter>
        </activity>
        Intent intent = new Intent();
        intent.setData(Uri.parse("demo://omooo:80/data?age=18&sex=girl"));
        if (getPackageManager().resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) != null) {
            startActivity(intent);
        } else {
            Toast.makeText(this, "Activity Not Found", Toast.LENGTH_SHORT).show();
        }

这里,注意我用了 PackageManager#resolverActivity() 来判断是否存在该隐式 Activity,这样就避免找不到 Activity 而导致应用 Crash。同时注意,第二个参数必须传 PackageManager.MATH_DEFAULT_ONLY,因为只有 category 为 DEFAULT 的 Activity 才能接收隐式启动。

        if (getIntent().getData() != null) {
            Uri uri = getIntent().getData();
            String scheme = uri.getScheme();
            Log.i(TAG, "scheme: " + scheme);
            String host = uri.getHost();
            Log.i(TAG, "host: " + host);
            int port = uri.getPort();
            Log.i(TAG, "port: " + port);
            String path = uri.getPath();
            Log.i(TAG, "path: " + path);
            String query = uri.getQuery();
            Log.i(TAG, "query: " + query);
            Set<String> params = uri.getQueryParameterNames();
            for (String item : params) {
                Log.i(TAG, "item: " + item + " value: " + uri.getQueryParameter(item));
            }
        }
//输出:
Demo_SecondActivity: scheme: demo
Demo_SecondActivity: host: omooo
Demo_SecondActivity: port: 80
Demo_SecondActivity: path: /data
Demo_SecondActivity: query: age=18&sex=girl
Demo_SecondActivity: item: age value: 18
Demo_SecondActivity: item: sex value: girl

说完 URI,剩下的 mimeType 表示要传递的数据类型,通常是 text/plain 或 image/jpeg 等等。

intent.setType("text/plain");

不过需要注意的是,如果同时设置了 URI 和 mimeType 的话就必须使用以下代码才能跳转:

intent.setDataAndType(Uri.parse("demo://omooo"),"text/plain")

因为如果使用 setData() 或者 setType() 的话,分别会将相应的 type 和 data 置为 null。

重要方法

  1. onSaveInstanceState()、onRestoreInstanceState()

    这两个方法只有在应用遇到意外情况下才会触发,比如横竖屏切换,可以用于保存一些临时性数据。

    在横竖屏切换的时候,Activity 会重建,在 onStop 之前,和 onPause 没有既定的时序关系,会调用 onSaveInstanceState(Bundle bundle) 方法,其中有一个 Bundle 对象可以用来存储数据,该对象便是 onCreate 中的 Bundle 对象 savedInstancesState。在 onCreate 取得时候要注意判空,而在 onRestoreInstanceState 里面则不需要判空,onRestoreInstanceState 方法调用的时机是在 onStart 之后。

    同时,我们知道,在 onSaveInstanceState 和 onRestoreInstanceState 方法中,系统自动为我们做了一定的恢复工作。当 Activity 在异常情况下需要重新创建,系统会默认为我们保持当前 Activity 的视图结构,并且在 Activity 重启后为我们恢复这些数据,比如文本框中用户输入的数据、ListView 滚动的位置等等。具体针对某一个特定的 View 系统能为什么恢复那些数据,可以查看 View 的源码。和 Activity 一样,每个 View 都有 onSaveInstanceState 和 onRestoreInstanceState 这两个方法。

    关于保存和恢复 View 层次结构,系统的工作流程是这样的:首先 Activity 被意外终止时,Activity 会调用 onSaveInstanceState 去保存数据,然后 Activity 会委托 Window 去保存数据,接着 Window 在委托它上面的顶级容器再去一一通知它的子元素来保存数据,这样整个数据保存过程就完成了。

        @Override
        public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
            super.onSaveInstanceState(outState, outPersistentState);
        }
    
        @Override
        protected void onRestoreInstanceState(Bundle savedInstanceState) {
            super.onRestoreInstanceState(savedInstanceState);
        }
    
  2. startActivityForResult

    FirstActivity:

        public void skip(View view) {
            Intent intent = new Intent(this, SecondActivity.class);
            startActivityForResult(intent, 0x01);
        }
    
        @Override
        protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
            super.onActivityResult(requestCode, resultCode, data);
            if (requestCode == 0x01 && resultCode == 0x02
                    && data != null) {
                Toast.makeText(this, data.getStringExtra("name"), Toast.LENGTH_SHORT).show();
            }
        }
    

    SecondActivity:

        public void goBack(View view) {
            Intent intent = new Intent();
            intent.putExtra("name", "Omooo");
            setResult(0x02, intent);
            finish();
        }
    

    这里需要注意的是,requestCode 一定要为正数,看源码便知。其实 startActivity 内部也是调用 startActivityForResult,然后在 startActivityForResult 的第二个参数 requestCode 传 -1。

  3. onNewIntent

        @Override
        protected void onNewIntent(Intent intent) {
            super.onNewIntent(intent);
        }
    

    在启动模式那块说过,当启动模式是 singleTask 和 singleTop 的时候,系统不需要重新创建 Activity 的时候会回调 onNewIntent 方法。

  4. onConfigurationChanged

    前面说过,当 Activity 横竖屏切换的时候会导致 Activity 销毁并重建,哪有什么方法能避免呢?其实可以在 AndroidManifest 里面指定 android:configChanges="orientation/screenSize" 来避免重建,这时就会调用 onConfigurationChanged 方法。

    如果按上面的配置,当字体发生变化时,也会销毁重建,但是不会回调 onConfigurationChanged 方法,所以说想要监听的变化必须要包含之内。

        @Override
        public void onConfigurationChanged(Configuration newConfig) {
            super.onConfigurationChanged(newConfig);
            if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
                //竖屏
            } else {
                //横屏
            }
        }
    
  5. onTrimMemory

    @Override
    public void onTrimMemory(int level) {
     super.onTrimMemory(level);
        if (level == TRIM_MEMORY_UI_HIDDEN) {
    
        }
    }
    

    当内存紧张时会回调,它在 onStop 回调之前。指导应用程序在不同的情况下进行自身的内存释放,以避免被系统直接杀掉,提高应用程序的用户体验。它和 onLowMemory 相比,它有一个 level 评级,onLowMemory 能兼容更低的版本。

转场动画

  1. overridePendingTransition(int enterAnim,int exitAnim)

    需要注意,在 startActivity 和 finish 之后调用。

  2. 设置 Application style

  3. ActivityOptions

启动流程

Activity 的启动流程可以分为三个部分:

  1. Launcher 请求 AMS 过程
  2. AMS 到 ApplicationThread 的调用过程
  3. ActivityThread 启动 Activity

参考

《Android 开发艺术探索》

Android面试官装逼失败之:Activity的启动模式

Github项目解析(九)-->实现Activity跳转动画的五种方式

https://github.com/HellForGate/TransitionDemo

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容