Android基础之Activity launchMode详解

简介:   本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! 讲到实战,就不得不拿两个例子来说明,本篇想拿的是应用最广泛的两个:Ams和Wms,一个管理activity,一个管理窗口,而前面我们已经讲了不少,本篇不再赘述。


  本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处!


讲到实战,就不得不拿两个例子来说明,本篇想拿的是应用最广泛的两个:Ams和Wms,一个管理activity,一个管理窗口,而前面我们已经讲了不少,本篇不再赘述。

关于Ams对activity的管理,无非这几个方面:启动哪个activity、物理按键对activity处理、内存骤减时activity的回收规则,以及暂停activity的一系列操作

先说如何启动activity?有哪些知识点。

Ams调度activity,运行某activity需要通过Ams决定,一般情况下仅限允许均可执行;记录运行的页面,保存activity一些状态,以便下次启动更加快速,一般放在ActivityRecord,以前叫HistoryRecord;做进程管理,清楚运行哪些进程,各自的状态和信息集合

当然Ams除了管理activity,还会管理其他两个主要数据类,如Process和Task;Process同样也有个ProcessRecord用来记录进程运行信息和状态以及内部有哪些组件(指service、provider这样的),多个apk可以运行在同一进程中,可以通过android:process来设置,一般情况下还是不要,因为手机默认64M内存(不同分辨率不同),内存一般也仅够一个apk使用;而TaskRecord记录activity所在的栈的id、intent和activity的数量,用于对栈进行管理。

AMS常见的默认系统常量:

1、MAX_ACTIVITIES=20: 通过硬件设置,当前页面仅有一个activity存活,其他20个被缓存起来,超过则杀死优先低的

2、MAX_RECENT_TASKS=20:通过硬件设置,栈最多存储20个,超过后最先舍弃最早的栈

3、PAUSE_TIMEOUT=500:启动activity最长时间为500ms,否则关闭该页面,一般表现是“屏幕黑一下”

4、LAUNCH_TIMEOUT=10*1000:启动进程最长10s,否则舍弃

5、PROC_START_TIMEOUT=10*1000:启动进程后10s内必须报告AMS启动状态,否则AMS不承认此进程的存在

以上是最基础的变量,相信在多年开发中,大家对其引起的现象已经历历在目

其次在启动和暂停activity过程中,需要一些变量来保存状态,主要因为AMS是通过Service机制动作,比如

1、mPendingActivityLaunched:即将启动的activity列表

2、mStoppingActivities:A跳入B,B启动后A即被加入此队列,区别于mHistory的A跳入即加入

3、mHistory:所有后台activity,A跳入B即A被加入,或按Home键B被加入,回来按回退键则B被清除;finishing=true

4、mFinishingActivities:上面列表中执行完finish方法的activity,此时并未完全杀死等待被回收

5、mLRUActivities:最近使用的activity,包含mHistory里的activity和已经清除的activity

此外还有HistroyRecord记录mPausingActivity(record对象,记录执行onPause方法的activity),mResumeActivity、mFocusedActvity和mLastPausedActivity等。

上面讲完基础,接下来进程流程,讲讲如何启动Activity?

一般调用方式有四种,点击手机图标、执行startActivityForResult(startActivity同理)、注册硬件监听启动、被action启动

以下是启动Activity的过程和startActivityForResult的启动(startActivity传一个默认的requestCode-1,最后仍然调用startActivityForResult方法,所以放在一起讲)


启动前的权限检查和准备工作

启动Activity的时候往往还需要进行权限检查,以查看其是否符合启动条件。查询不满足条件则返回,否则继续;检查Component是否存在,不满足返回,满足继续,启动startActivityLocked(caller,intent)方法,完成以下几件事

1、检查如果启动自己则直接返回

2、会加入INTENT_FLAG_FORWARD_RESULT标志,用于从C直接返回结果给A,使用方法A调用startActivityForResut->B调用startActivity->C调用setResult直接回到A

3、检查call-ActivityRecord是否存在且有指定权限

4、如果Ams中有IActivityController对象,则通知Controller进行相应控制

5、创建临时HistoryRecord对象,不一定加入mHistory列表,如不关闭则加入

6、检查是否允许切换activity,否则加入mPendingActivityLaunched

7、判断pendingActivity是否有要启动的activity,有则先执行,无则执行该intent

如想了解launchMode,请移步: 

Android基础之Activity launchMode详解

如想了解Intent,请移步: 
 

Intent中的四个重要属性——Action、Data、Category、Extras


以上是对intent的介绍,接下来会再介绍一下task,也就是如何启动,以什么样的规则启动和退出。以下均指launchFlag,标记均以FLAG_ACTIVITY_开头,介绍时会忽略,请注意一下;启动时会依次判断如下标识

1、NO_USER_ACTION:含义无用户交互;基本不用,主要防止一段时间后执行onUserLeaving方法;接下来如果立即启动,就把r.delay.Resume设为true

2、PREVIOUS_IS_TOP:含义上个intent是否位于栈顶,基本不用;然后为activity赋予权限加入缓存;此时区别于launchMode和launchFlag,前者指activity自己声明的启动方式,后者是明显启动者想让activity如何启动,能过intent设置,但两者有相通性

3、NEW_TASK、SINGLE_TASK、SINGLE_INSTANCE:使用这三者均不建议使用startActivityForResut,而只限于使用startActivity即r.resultTo=0,不需要回传数据的情况下;第1个是会新起一个task,即两个task之间最好不进行数据回传;2和3的相同在于,如果已经存在这样的task和component以及其他相同数据如intent,则均跳到相应栈中,不存在则声明一个新的task;不同点在于:2的task里可以包含多个activity,而3仅能包含一个;可能跟每个task内存大小有关,不同功用的activity可以申请不同的task使用,这一点也可以看上面“Android基本之Activity LaunchMode详解

4、CLEAR_TOP、REORDER_TO_FRONT:前者如自己存在,则清除该栈上面的其他activity;后者仅把自己放在最上面;举例A1->A2->A3,前者启动A2则变成A1->A2,后者启动A2则变成A1->A3->A2

5、NO_HISTORY:不要保存自己,设置A3,则A1->A2->A3->A4,执行完还是A1->A2,皮之不存,毛将焉附。

一般情况下,以上如果调整栈的顺序,那么是可以执行的;如A、B、C三个栈,分别有2个activity,如果从C切换到B,那么是这样的A1->A2->B1->B2->C1->C2,调整后A1->A2->C1->C2->B1->B2。

系统还提供另外一种办法来设置activity的启动方式,那就是

1、android:clearTaskOnLaunch=true/false:是否清除task里的其他activity

2、android:finishOnTaskLaunch:是否关闭task中已有的此activity

3、android:allowTaskReparent:是否将自己带入启动的task中

4、android:alwaysRetainTaskState:是否由系统维护activity状态;一般应用在根activity,每次可以打开最后打开的页面

最近讲的几个东西之间的关系,画了一张图,如下所示


正式启动工作(主要流程均为ActivityThread执行-在attach时初始化)

一、暂停当前activity

1、判断该activity是否存在;然后执行onUserLeaving方法,避免此activity再与物理按键交互,如后退键

2、调用performPauseActivity(告知暂停而非超时等情况,将prev指向自己),先onSaveInstanceState,再执行onPause

·3、报告AMS暂停完毕,通过IPC调用AMS的completePauseActivity方法

二、调用resumeTopActivity方法:

1、如果mHistory有记录且直接启动,否则执行startHomeActivityLocked方法启动主界面程序;

2、系统处于睡眠状态或当前activity未被暂停,则停止;

3、从mStoppingActivities和mWarningVisibleActivities里移除目标对象;

4、将被停止的activity设置为不可见状态,通知activty或task切换

5、判断目录进程是否存在并且activityThread存活,否则执行startSpecificActivityLocked方法;如果进程不存在,则调用

Process类启动新进程,设置pid加入ProcessRecord,启动完之后再通知Ams启动目标Activity,至于启动Process的过程跟调用

暂停或启用activity的过程无异,仅仅参数发生变化而已,以及变量不同和意义不同。当然我们再讲一点,启动进程毕

竟是个重要流程,提取odex文件,前面几篇文章有过介绍,指已经优化过的dex文件,通过Service、Provider和

Broadcast加入引用,创建完成。

6、最终调用performLaunchActivity,执行attach,执行setTheme,几个on方法,拿到DecorView加入Wms,设

置可见。

接下来咱们讲讲停止工作:一般情况下是长时间不使用,或者应用内存紧张,或者启动时设置no_history才会执行,执行过程

而应用与上面生命周期无异,也就是设置不可见,加个mStoppingActivities,异步通知Ams停止,最后调用onStop方法等等,

关闭activity也同样如此;至于关闭的优先级前面似乎没讲,接下来会着重介绍一下。

Android系统如何管理自己内存的?同样可以预习一下,原理协同,再做补充。

一般情况下优化级分为-16到15级,而android默认仅使用0-15级,越小优先级越高,当前可见activity最高为0。

为什么会产生这个优化级,主要android采用的是关闭而不退出,退出而不清理的原则,主要为了二次加载更加快速;

其次是上面停止activity的原因。而这个规则由Ams加权得出,有这样几个变量:是否展示在当前页或是持久化

activity、相应的配合组件、剩余内存量、HOME进程、活跃activity数量和组件等,就像JVM的内存管理规则一样,

详见:Java高级之虚拟机垃圾回收机制

由于Ams无法预知内存的变动(OOM除外),因而采用这套机制:最先是空进程(无activity进程),其次是

activity,再次是前台的配合组件,如Service、Receiver、Provider,最后才是前台activity;而这些操作由OOM Killer

进程直接管理,关于activity回收需要满足以下几种情况:

1、finish操作或crash或anr,通过updateOomAdjLocked方法让其指定优先级,动态调整

2、如果运行的activity超过20个,必须是已经stop的、不可见的、非常驻进程

因而不合理的手机架构也会造成系统的崩溃。PS:持久化对象的在ProcessRecord的persistent变量是否为true

LocalActivityManager存在于Activity内部,用来装载和管理activity以及各种状态,维持一个最低的内存消耗;核

心在于它使用UI线程的activityThread来装载指定的 activity;有同学可能会问task越少或者histroyRecord越小,内存

会占用越小吗?这个问题跟手机里装一个app和装n个app重量是否增加是一样的道理,但是越过限制以后(task允许)

内存占用确实会变大,直到出现OOM。

以下是按键方面的内容(锁屏下,基本均不操作)

1、后退键:Activity里监听到onBackPressed方法;一般执行的是finish动作,执行performDestroyActivity方法

2、Home键:Acitivty的onKey方法无法截取它,属于设计原因;Wms中使用PhoneWindowManager类中的

interceptKeyTi截取消息,发现是它,再执行launchHomeFromHotKey;2.0之后添加另一个Home键执行

startDockOrHome方法来监听硬件。

与普通的启动区别在于,能启动特殊的intent,方法在ContextImpl里。而长按震动、关闭所有窗口、会弹出LRT-

lasted recent task,可以调用Ams的getRecentTask来取出,通过弹出的窗体的点击事件,进入相应的应用。


总结一下:

进程启动:1、内核创建进程数据结构,指出其地址总线

2、装载函数,读取代码,拿到数据总线

3、将程序指针指向目标地址入口


虚拟机启动:首次是从Zygote进程fork出来一个子进程,用来加载资源,然后启动SystemSever,用来监

控手机按键和触摸事件,以及管理Ams、Pms、Wms等,最终根据配置文件的HomeActivity,启动它。


Activity启动,然后触发init.rc文件执行main函数,启动一个ActivityThread

这就是所谓的UI线程,由Looper声明一个MessageQueue,接着创建Activity对象,初始化ViewRoot和token,用来分

发消息和接收转换为本地消息,为后面执行Activity的生命周期,创建PhoneWindow,执行attach方法,初始化内部组

件,根据Wms返回的消息,适时的执行生命周期,执行setContentView创建DecorView(View内部也有ViewRoot来设置View的各种属性),由Wms加入到窗口中,设置Visiable,加载结束。


注:每个应用仅有一个ActivityThread来异步处理内部事务,如果出错则App Crash;具体应用启动后,会创建ApplicationThread和ActivityThread,分别用来处理应用事务和Activity事务,并且创建MessageQueue,不停的轮循;AMS通过判断加载某个Activity和资源的加载情况,将消息从手机端通过Binder发给当前应用的ViewRoot对象,通过handler把消息放入消息队列,轮循出来的消息处理,即推动Activity的生命周期执行。


问题1:在应用运行过程中Window有多个吗?是的,每个Activity都会创建Window(见1)。

问题2:在应用运行过程中WindowManager有多个吗?Activity的WindowManager通过系统的WindowManager创建(见2、3、4)

Activity类

    final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window) {
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);
1、   mWindow = new PhoneWindow(this, window);
mWindow.setWindowControllerCallback(this); mWindow.setCallback(this); mWindow.setOnWindowDismissedCallback(this); mWindow.getLayoutInflater().setPrivateFactory(this); if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) { mWindow.setSoftInputMode(info.softInputMode); } if (info.uiOptions != 0) { mWindow.setUiOptions(info.uiOptions); } mUiThread = Thread.currentThread(); mMainThread = aThread; mInstrumentation = instr; mToken = token; mIdent = ident; mApplication = application; mIntent = intent; mReferrer = referrer; mComponent = intent.getComponent(); mActivityInfo = info; mTitle = title; mParent = parent; mEmbeddedID = id; mLastNonConfigurationInstances = lastNonConfigurationInstances; if (voiceInteractor != null) { if (lastNonConfigurationInstances != null) { mVoiceInteractor = lastNonConfigurationInstances.voiceInteractor; } else { mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this, Looper.myLooper()); } }
    2、mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE), mToken, mComponent.flattenToString(), (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0); if (mParent != null) { mWindow.setContainer(mParent.getWindow()); }
    3、mWindowManager = mWindow.getWindowManager();
mCurrentConfig = config; }Window类:
   public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
            boolean hardwareAccelerated) {
        mAppToken = appToken;
        mAppName = appName;
        mHardwareAccelerated = hardwareAccelerated
                || SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
        if (wm == null) {
            wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
        }
    4、   mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}WindowManagerImpl类:
 * Provides low-level communication with the system window manager for
 * operations that are bound to a particular context, display or parent window.
 * Instances of this object are sensitive to the compatibility info associated
 * with the running application.
提供一个跟系统WindowManager低等级的沟通方式,用于特别的Context、Display或者父Window。
WindowManagerGlobal类的解释同上。

public final class WindowManagerImpl implements WindowManager {
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }

    @Override
    public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.updateViewLayout(view, params);
    }

    private void applyDefaultToken(@NonNull ViewGroup.LayoutParams params) {
        // Only use the default token if we don't have a parent window.
        if (mDefaultToken != null && mParentWindow == null) {
            if (!(params instanceof WindowManager.LayoutParams)) {
                throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
            }

            // Only use the default token if we don't already have a token.
            final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
            if (wparams.token == null) {
                wparams.token = mDefaultToken;
            }
        }
    }

    @Override
    public void removeView(View view) {
        mGlobal.removeView(view, false);
    }




目录
相关文章
|
3月前
|
Android开发 开发者
Android UI设计: 请解释Activity的Theme是什么,如何更改应用程序的主题?
Android UI设计: 请解释Activity的Theme是什么,如何更改应用程序的主题?
42 1
|
3月前
|
数据库 Android开发 开发者
Android基础知识:请解释Activity的生命周期。
Android基础知识:请解释Activity的生命周期。
43 2
|
2月前
|
Android开发
[Android 四大组件] --- Activity
[Android 四大组件] --- Activity
22 1
|
3月前
|
Android开发
Android基础知识:什么是Fragment?与Activity的区别是什么?
Android基础知识:什么是Fragment?与Activity的区别是什么?
281 54
|
4月前
|
XML 安全 Java
Android Studio App开发入门之活动Activity中为活动补充附加信息讲解及实战(附源码 超详细必看)
Android Studio App开发入门之活动Activity中为活动补充附加信息讲解及实战(附源码 超详细必看)
35 0
|
4月前
|
Android开发
Android Studio App开发入门之在活动之间传递消息(附源码 超详细必看)(包括显示和隐式Intent,向上一个和下一个Activity发送数据)
Android Studio App开发入门之在活动之间传递消息(附源码 超详细必看)(包括显示和隐式Intent,向上一个和下一个Activity发送数据)
44 0
|
4月前
|
Android开发
Android Studio APP开发入门之活动Activity中启停活动页面的讲解及实战(附源码,包括Activity的启动结束、生命周期、跳转等)
Android Studio APP开发入门之活动Activity中启停活动页面的讲解及实战(附源码,包括Activity的启动结束、生命周期、跳转等)
41 0
|
Android开发 数据安全/隐私保护
Android 基础课堂 - Lesson.5
#### Android 基础课堂 上周我们已经写完了登录的界面啦,界面布局挺简单的,学起来不难吧,对了,我们也说了,要实现相同的布局效果,还有很多其他的实现方式,怎么样,有没有尝试用其他的写法去实现看看呢。 既然我们已经写完登录的界面了,那么本周我们该干点啥呢?没错,当然是
4835 0
|
Java 开发工具 Android开发
Android 基础课堂 - Lesson.1
#### Android 基础课堂 - 之所以叫Android基础课堂嘛,是因为本部分主要的内容是关于Android开发的一些小知识也可以算是一个入门教程吧,我们志在让读者了解Android开发,清楚Android开发的过程~并能更好的看懂我们的周刊。 - 基础课堂将会从最基础的Androi
15570 0
|
Android开发
Android 基础课堂 - Lesson.2
#### Android 基础课堂 在上周的基础课堂中,我们说过了,本系列的基础课堂将会结合一个应用的开发过程慢慢带大家慢慢深入理解Android开发的过程,故该基础课堂和正常的Android入门教程不太一样,不会像正常的Android入门教程那样,巴拉巴拉的从四大组件、各种控件等等开
10460 0