Activity基础知识点

1. 生命周期

1.1 常见方法

1) onCreate(): Activity正在创建,进行相关的初始化工作,如加载布局等;
2) onStart(): Activity可见但没有焦点,即不可交互;
3) onResume(): Activity获取焦点,可交互;
4) onPause(): Activity可见但不在前台,和onResume()是配对的;
5) onStop(): Activity不可见,如退到后台,和onStart()是配对的;
6) onDestroy(): Activity被销毁,资源回收、释放,和onCreate() 是配对的

7) onRestart(): Activity重新启动,如退到后台之后,再次启动;
8) onNewIntent(): 跟启动模式、Task任务栈有关,后面会讲;

9) onSaveInstanceState(): 由于系统原因(如横竖屏切换)导致activity需要被销毁和重建时,在销毁前调用,保存被销毁Activity的状态;
10) onRestoreInstanceState(): 和onSaveInstanceState()对应,在Activity重建时调用,带之前保存的状态信息;

1.2 常规启动

1) 从桌面启动(之前进程已死,创建新进程): onCreate() → onStart() → onResume()
在这里插入图片描述
2) 按HOME键返回桌面,再从桌面启动: onPause() → onStop() → onNewIntent() → onRestart() → onStart() → onResume()
在这里插入图片描述
3) 按BACK键: onPause() → onStop() → onDestroy()
在这里插入图片描述
4) Activity1界面进行锁屏,然后解锁: onPause() → onStop() → onRestart() → onStart() → onResume()
在这里插入图片描述

1.3 启动另一个activity

在Activity1中跳转到Activity2: Activity1的onPause() → Activity2的onCreate() → onStart() → onResume() → Activity1的onStop()
在这里插入图片描述

1.4 有Dialog弹框

大部分人的误解: 在Activity中弹出Dialog,会触发onPause(),不会触发onStop(),因为这个时候Activity被覆盖不在前台,但部分可见;
其实不然: 在Activity中弹出一般的Dialog,既不会触发onPause(),也不会触发onStop();
Google官方对onPause的解释:

/**
 * Called as part of the activity lifecycle when an activity is going into
 * the background, but has not (yet) been killed.  The counterpart to
 * {@link #onResume}.
 *
 * <p>When activity B is launched in front of activity A, this callback will
 * be invoked on A.  B will not be created until A's {@link #onPause} returns,
 * so be sure to not do anything lengthy here.
 */

即当Activity B在Activity A前面时,会触发Activity A中的onPause方法。 B直到A的onPause方法执行完后才会被创建,所以建议不要在onPause方法中做耗时操作。
而且,被覆盖指的是被其他Activity不完全覆盖,并不是其他其他东西(如一般的Dialog)。

那到底什么时候只触发onPause(),不会触发onStop()呢?
1) 启动另外一个Activity2,并将Activity2的主题设置为Dialog

<activity android:name=".Activity2"
          android:theme="@android:style/Theme.Dialog">

效果如下图所示:
在这里插入图片描述
生命周期如下,Activity1只触发onPause(),不会触发onStop():
在这里插入图片描述
2) 启动另外一个Activity2,并将Activity2的主题设置为半透明

<activity android:name=".Activity2"
          android:theme="@android:style/Theme.Translucent">

生命周期如下,Activity1只触发onPause(),不会触发onStop():
在这里插入图片描述

1.5 横竖屏切换

1) 先销毁,再重建(默认): onPause() → onStop() → onDestroy() → onCreate() → onStart() → onResume()
在这里插入图片描述
2) 那有没有办法不销毁不重建Activity呢?
答案是有的。比如视频播放界面就是这样做的,否则横竖屏旋转一下视频就会从播放状态回到初始的未播放状态。
对activity进行配置,这里要注意orientation和screenSize必须一起使用才有效果:

<activity android:name=".MainActivity"
          android:configChanges="orientation|screenSize">

这样Activity就不会重走生命周期,而系统会通过回调onConfigurationChanged()方法,可以复写此方法进行相关操作:
在这里插入图片描述

2. 任务栈(Task Stack)

2.1 概念

  1) Task其实就是一组有相互关联的Activity的一个集合,是一个后进先出的栈,有个Task id作为唯一的标识;
  2) Task Stack表示任务栈,任务栈的基本单位就是Task;Stack以链表的形式管理Task;

2.2 taskAffinity

  1) 简单说就是指定任务栈的名称,相关的Activity会入此栈;
  2) 该属性只有singleTask模式下有用(或配合allowTaskReparenting使用,后面会讲);
  3) 默认为应用包名;如果第一个入栈的activity设置了此属性,则taskAffinity值为该activity的taskAffinity;

  ① 如下图所示,虽然设置了Activity2的taskAffinity,但Activity2为standard模式,所以taskAffinity为包名:

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".Activity2"
            android:taskAffinity="www.www">

在这里插入图片描述
  如果将MainAcitivity的taskAffinity设置为www.www,则这个TACK的taskAffinity的就是www.www;

  ② 如下所示,App2的Activity2设置launchMode为singleTask,且设置taskAffinity为www.app2.affinity2:

      <activity android:name=".Activity2"
            android:launchMode="singleTask"
            android:taskAffinity="www.app2.affinity2">

  操作:App2的MainAcitivity启动Activity2再启动Activity3,测试的栈情况为:
在这里插入图片描述
在这里插入图片描述
  可以看出,Activity2根据自己的affinity开辟了自己的Task,后续启动的Activity3也入此栈。

2.3 Task相关属性

  1) allowTaskReparenting
  ① 标记此属性的Activity实例所在的当前应用在退居后台后,决定是否将标记的Activity从启动的Task移动到相同affinity的Task中;
  ② 配合taskAffinity使用;
  ③ 默认为false;
  ④ 引用网上比较生动的比喻:你捡到一条狗,在家里喂养几天觉得不错,当自己家的了;但是突然有一天他的主人找上门来了,小狗还是乖乖和主人走了;

  2) alwaysRetainTaskState
  ① 只对根Activity生效;
  ② App如果长期在后台,系统会对应用的Task进行清理,而清理过程中,如果对根Activity标记了这个属性,那么他会保留根Activity的状态;

  3) clearTaskOnLaunch
  ① 只对根Activity生效;
  ② 唤醒应用时,是否清除根Activity之上的Activity;

        <activity android:name=".MainActivity"
                  android:clearTaskOnLaunch="true">

  验证:App2的MainActivity设置clearTaskOnLaunch=“true”,启动Activity2,接着启动Activity3,然后按HOME键退到后台,然后再重新启动,会发现Activity2和Activity3都被销毁,进入的是MainActivity:
在这里插入图片描述

  4) finishOnTaskLaunch
  这个属性跟clearTaskLaunch很像,只不过他只是销毁标记的Activity,而clearTaskOnLaunch是除了根Activity全部销毁;

  4) noHistory
  和Intent.FLAG_ACTIVITY_NO_HISTORY一样:带此Flag启动Activity,在切换到其他任务(或back返回时)时会被销毁,即没有在Task中留下历史记录;

3. 启动模式

3.1 Activity的显示启动与隐式启动

1) 显示启动
  直接指定Activity的类名,一般用于同一个应用内,因为可以确切地知道要启动的组件名称。如:

            Intent intent = new Intent(MainActivity.this, Activity2.class);
            startActivity(intent);

  也可以使用setClass()/setClassName/setComponent(),其实最终都是构建ComponentName的。

    public Intent(Context packageContext, Class<?> cls) {
        mComponent = new ComponentName(packageContext, cls);
    }
    
    public @NonNull Intent setClassName(@NonNull String packageName, @NonNull String className) {
        mComponent = new ComponentName(packageName, className);
        return this;
    }
    
    //注意以下这个方法的Context指的是要启动应用的context,因此该方法一般用于本应用内启动,因为其他应用的Context获取较复杂,
    //还不一定能获取;而且最终构建ComponentName时,这个context是用来获取包名的。
    public @NonNull Intent setClassName(@NonNull Context packageContext, @NonNull String className) {
        mComponent = new ComponentName(packageContext, className);
        return this;
    } 

2) 隐式启动
  无需指定要启动组件的具体名称,使用intent-filter进行匹配,一般在启动其他应用的组件时使用,因为它能较好地实现应用间解耦;如:

        <activity android:name=".Activity2">
            <intent-filter> 
                <action android:name="com.www.app2.android.intent.test"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </activity>
        
        Intent intent = new Intent("com.www.app2.android.intent.test");
        if (intent.resolveActivity(getPackageManager()) != null) {
            startActivity(intent);
        }

  注意:隐式启动必须得设置category为DEFAULT,否则组件会找不到,导致应用崩溃(所以建议本应用最好做好防呆措施,try catch或者用if (intent.resolveActivity(getPackageManager()) != null) 事先判断指定Intent的Activity是否存在,尤其是启动其他应用的组件时,因为其他应用是不可控的,有一定的崩溃风险),包括显示启动时也需要做好防呆措施;

3.2 静态配置四种启动模式

<activity android:name=".MainActivity"
          android:launchMode="standard | singleTop | singleTask | singleInstance" >  <!--四个里面其中一个-->

1) standard(默认): 无论启动多少次,每一次都创建新的Activity
在这里插入图片描述
2) singleTop: 栈顶复用
在这里插入图片描述
3) singleTask: 栈内复用
在这里插入图片描述
4) singleInstance:独占Task
在这里插入图片描述
  注:以上四张图片是从网上借鉴来的,暂时没找到原创作者;并不是本人画的;且这四张图已经画得非常形象生动了,非常感谢原创。

3.3 常用的Intent Flag

1) Intent.FLAG_ACTIVITY_NEW_TASK
  简单来说,就是根据taskAffinity来决定需不需要创建新的Task来存放目标Activity;

  验证1:从App1(包名为com.www.myapplication2)的MainActivity中启动App2的Activity2
  ① 没有Flag
在这里插入图片描述
在这里插入图片描述
  可以看出,App2的Activity2并没有单独在一个Task中,而是和App1的主Activity在一个Task中,近期任务中也没有App2;

  ② App2的Activity2设置singleTask
在这里插入图片描述
在这里插入图片描述
  可以看出,App2的Activity2单独成栈,近期任务中也有App2。

  ③ 去掉singleTask,Intent加FLAG_ACTIVITY_NEW_TASK
在这里插入图片描述
   可以看出,效果和singleTask一样,App2的Activity2也是单独成栈。

   那FLAG_ACTIVITY_NEW_TASK和singleTask有什么区别呢?
   验证2:App2的MainActivity启动App2的Activity2,接着启动Activity3,此时App2的Task栈为:
在这里插入图片描述
  ① 在App1中启动App2的Activity2,Intent带FLAG_ACTIVITY_NEW_TASK,发现测试的Task栈为:
在这里插入图片描述
在这里插入图片描述
   可以看出,Activity2重新创建了实例,没有复用之前的实例。这也能很好的诠释FLAG_ACTIVITY_NEW_TASK的含义:如果已经有taskAffinity的Task存在,则往里面压入目标Activity。

  ② Activity2设置launchMode为singleTask,在App1中启动App2的Activity2,此时App2的栈情况为:
在这里插入图片描述
在这里插入图片描述
   可以看出,Activity3被出栈,Activity2至于栈顶,即singleTask的栈内复用效果;此时有没有FLAG_ACTIVITY_NEW_TASK效果都一样。

2) Intent.FLAG_ACTIVITY_SINGLE_TOP
  该Flag与静态设置中的singleTop效果相同。

3) Intent.FLAG_ACTIVITY_CLEAR_TOP
  ① 测试:MainActivity 启动Activity2 启动Activity3,然后在 Activity3中带CLEAR_TOP启动Activity2,发现Activity2被销毁并重建:
在这里插入图片描述
  ② 测试:MainActivity 启动Activity2 启动Activity3,然后在 Activity3中带CLEAR_TOP加SINGLE_TOP启动Activity2,发现Activity2被复用,即CLEAR_TOP加SINGLE_TOP就相当于静态配置的singleTask:
在这里插入图片描述

4) Intent.FLAG_ACTIVITY_CLEAR_TASK
在这里插入图片描述
  ① 此Flag启动的目标Activity会清空与之相关的Task,并重新创建Task,将目标Activity重建后压入栈,作为新栈的根Activity;
  ② 只能与FLAG_ACTIVITY_NEW_TASK一起使用;
在这里插入图片描述
  还未用Flag启动App2的Activity2前:
在这里插入图片描述
  带FLAG_ACTIVITY_CLEAR_TASK和FLAG_ACTIVITY_NEW_TASK启动后:
在这里插入图片描述

5) Intent.FLAG_ACTIVITY_REORDER_TO_FRONT
  带该Flag的Intent会把目标Activity调到栈顶,不会销毁之上的Actitivty。
  验证:App2的Activity1启动Activity2启动Activity3,再启动Activity2(带_REORDER_TO_FRONT)
在这里插入图片描述
在这里插入图片描述

6) Intent.FLAG_ACTIVITY_NO_HISTORY
   带此Flag启动Activity,在切换到其他任务(或back返回时)时会被销毁,即没有在Task中留下历史记录;
   验证:Activity1启动Activity2(带FLAG_ACTIVITY_NO_HISTORY),再启动Activity3,按返回键,发现此时返回的是 Activity1,而Activity2以被出栈销毁:
在这里插入图片描述

7) Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
   在后台管理中,不显示该Activity的近期任务。

4. 其他拓展点

4.1 Intent

1) 概念
  ① 是传递消息的对象;
  ② 是连接Activity、Service、Broadcast之间的桥梁;
  ③ 是进程内/进程间组件通信的媒介;

2) 相关属性
  ① component(组件):目标组件(3.1小结中已经讲了显示启动与隐式启动)
  ② action(动作):用来表现意图的行动(隐式启动,通过intent-filter来匹配)
  ③ category(类别):用来表现动作的类别(隐式启动,通过intent-filter来匹配)
  ④ data(数据):表示与动作要操纵的数据
  ⑤ type(数据类型):对于data范例的描写
  ⑥ extras(扩展信息):扩展信息
  ⑦ Flags(标志位):期望这个意图的运行模式(参考3.3小结中的“常用的Intent Flag”)

 推介文章:https://www.cnblogs.com/qianguyihao/p/3959204.html

4.2 intent-filter匹配规则

 原则:只有action、category、data三个属性都匹配,Intent才算匹配成功。
1) action
  ① action是字符串,区分大小写;
  ② intent-filter中可以声明多个action,Intent中的action与其中的任一个action在字符串形式上完全相同即可匹配成功;
  ③ 隐式Intent必须指定action(如不指定action则必须指定data或mimetype。这种情况下,只要Intent-Filter 至少含有一个action就可以匹配),如果没有指定,则匹配失败;

2) category
  ① category也是字符串,也区分大小写;
  ② intent中带的category必须都要在intent-filter中声明;而intent-filter中声明的category,intent中可以不带;
  ③ 系统在启动activity时,会默认加上category “android.intent.category.DEFAULT”,所以我们在隐式启动时,必须在intent-filter中声明category android:name=“android.intent.category.DEFAULT” ,否则会异常;

3) data
  ① data由mimeType和URI两部分组成;
  ② mimeType指定媒体类型,如image/jepeg、video/等;
  ③ URI格式为:<scheme>://<host>:<port>/[<path>|<pathPrefix>|<pathPattern>]
    scheme:URI的模式,比如http、file、content等;
    host:URI的主机名,比如 www.baidu.com;
    port:URI中的端口号;
    path:完整的路径信息;
    pathPattern:路劲信息,正则表达式,比如有通配符 “
”;
    pathPrefix:路径的前缀信息;
  ④ Intent的URI可通过setData方法设置,mimetype可通过setType方法设置,但是这两个方法是互斥的,即互相擦除:设置了其中一个,另一个就会被置为null;如果想两个一起设置,则可以使用setDataAndType;
  ⑤ 类似action,Intent的data只要与Intent-Filter中的任一个data声明完全相同,data就能匹配成功;
    如:

        <activity android:name=".Activity2">
            <intent-filter>
                <action android:name="com.www.app2.android.intent.test" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.MY" />
                <data android:mimeType="video/*" android:scheme="http" android:host="www.baidu.com"/>
                <data android:mimeType="image/jepeg" android:scheme="http" android:host="adc"/>
            </intent-filter>
        </activity>

            Intent intent = new Intent();
            intent.setAction("com.www.app2.android.intent.test");
            intent.setDataAndType(Uri.parse("http://www.baidu.com"),"video/*");
            startActivity(intent);

4.3 组件安全

1) 组件暴露
  当隐式启动组件时,组件原本默认为false的android:exported就会自动变为true,即该组件对外暴露,表示该组件允许外部的应用调用该组件,那么随之而来的就会有相关的安全隐患,比如:

App2隐式启动配置:
        <activity android:name=".Activity2">
            <intent-filter>
                <action android:name="com.www.app2.android.intent.test" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
代码里面有获取数据:
        CharSequence label = getIntent().getStringExtra("label");
        Log.i(TAG, "-----onCreate-----label: " + label.toString());

App1隐式启动App2的Activity2,不带label数据:
        Intent intent = new Intent("com.www.app2.android.intent.test");
        if (intent.resolveActivity(getPackageManager()) != null) {
            startActivity(intent);
        }

结果就是,因为label为空,而导致app2因空指针而崩溃:
在这里插入图片描述
因此,其他应用就可以通过这个漏洞疯狂的攻击App2,使其崩溃。当然这里有两个问题:
  ① 代码严谨性问题:一般来说,在使用外部数据时,一定要做好校验等措施,比如以上例子的label,应该先对其进行判空处理,就算是App2内部自己使用,也需要进行判空;
  ② 组件暴露问题:如果组件只是App2自己内部使用,则最好不要对外暴露(android:exported=“false”):

        <activity android:name=".Activity2"
            android:exported="false">
            <intent-filter>
                <action android:name="com.www.app2.android.intent.test" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>

  这样,其他应用就会无法启动App2的该组件:
在这里插入图片描述

2) 权限限制
  ① 如果组件确实需要对外暴露,那么一方面需要做好完备的校验措施,保证代码的健壮性;一方面则需要加入权限限制;
  ② 权限设置,如针对以上例子:

自定义权限:
    <permission
        android:name="com.www.app2.permission.Activity2"
        android:protectionLevel="signature"
        android:label="permission for activity2" />
限制权限:
        <activity android:name=".Activity2"
            android:permission="com.www.app2.permission.Activity2">
            <intent-filter>
                <action android:name="com.www.app2.android.intent.test" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>

  如此一来,只有签名一样且有com.www.app2.permission.Activity2权限的才能启动Activity2。除了signature,权限等级protectionLevel还有很多其他等级,如system、signatureOrSystem、dangerous等;

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值