四大组件—Activity

Posted <天各一方>

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了四大组件—Activity相关的知识,希望对你有一定的参考价值。

文章目录

Activity

1.什么是Activity

Activity是android四大组件之一,是负责展示界面、与用户交互的一种组件,Activity是四大组件中唯一用户可以感知的组件。

2.进程优先级

Android平台的App,通常都是单进程的,根据App栈顶的活动状态,可将App分为以下五大进程模式:

  • 前台进程:当前进程Activity正在与用户交互;当前Service正在与Activity进行交互,或者调用了startForground()或者此Service正在执行生命周期。
  • 可见进程:此Activity失去焦点,但是处于可见状态;此Service与一个可见的Activity进行绑定。
  • 服用进程:当前开启startService()启动一个Service服务就可以认为此进程是一个服务进程。
  • 后台进程:Activity的onStop()被调用,但是onDestroy()没有调用的状态。该进程属于后台进程。
  • 空进程:该进程没有任何数据,但是保留了内存空间,并没有被系统kill掉。

进程的优先级就是根据这五大进程模式逐一递减。当系统内存资源不足时,会根据此优先级进行回收操作。回收针对的是App,而不是App中的某个组件。

3.Activity的启动模式

在了解启动模式之前,我们有必要对Activity的两个概念拎清楚,稍后对启动模式的分析也会基于这两个概念:任务、返回栈。

这两个概念我在刚接触Android开发时接触过,当时看郭神那本第一行代码在讲解启动模式时提到过返回栈的概念,书中只是粗浅的介绍了一个Activity的启动(standard),伴随在入栈操作,这个栈就是返回栈。现在看来,介绍的不是很完整,当然对于当初学Android的我来说,介绍的很浅显易懂了。接下来就谈一谈对其的补充。

当我们按下手机中查看最近运行应用时,其实看到的是一个一个的任务(Task),任务是用户在执行某项工作时与之互动的一系列 Activity 的集合。这些 Activity 按照每个 Activity 打开的顺序排列在一个返回堆栈中(Google官方介绍)。每个任务都对应这一个返回堆栈,而任务是可以叠加的,这个叠加只针对于前台App,当App进入后台时,或者当按下查看最近任务列表键,前台App中Task的叠加就会被拆开。(稍后介绍如何叠加)。

Activity总共有四大启动模式:

  • 标准模式(standard)
  • 栈顶复用(singleTop)
  • 栈内复用(singleTask)
  • 单例模式(singleInstance)

前两种模式是最好理解的,网上许多博客也介绍的相当清楚:

标准模式

是Activity默认的启动模式,每当启动一个Activity时,就会在返回栈栈顶创建一个Activity的实例,不论这个Activity在返回栈中是否有实例。我们在开发中大多都用这种模式。

栈顶复用

如果这个Activity已经存在于返回栈的栈顶,那么当重新打开这个Activity时,并不会重新创建它的实例,而是去回调onNewIntent方法,接着执行Activity的onRestart->onStart->onResume。

这两种启动模式都不涉及两个App之间调用对方Activity的情况。即使打开了另一个App的Activity,也还是在当前App的Task里,返回栈还是当前Task的返回栈。如果多个App同时打开了一个App中的Activity,他们是不会相互影响的。

接下来介绍的两种启动模式,会更加有针对性的去考虑在一个App里打开另一个App中的Activity的情况。

栈内复用

在郭神的第一行代码中,是这样介绍这种启动模式的:每次启动Activity时,都会在返回栈内检查是否有该Activity的实例,如果发现有则直接使用该实例,并把在这个Activity之上的所有Activity统统出栈。如果没有,就会新创建一个Activity。

在我们的测试中发现情况确实如此,但是这个描述是不太准确的。我们看singleTask这个名字,一定是与Task有一定关系的。我们考虑一种情况。在App-B中有一个ActivityB,它的启动模式是singleTask,那么,我们在App-A中启动这个ActivityB,这个ActivityB在哪里创建?在App-A上面吗?我们在App-A上面确实看到了这个ActivityB。但是,如果我们按下回退键,如果App-B之前被你启动过,里面还有其他Activity,我们会看到回退到了App-B的Activity上。那…这个ActivityB是在App-B上?事实确实如此,当我们从App-A启动App-B的ActivityB时(前提是singleTask模式),ActivityB会在App-B的任务中入栈,然后将App-B的任务压在App-A的上面,这样,给我们造成的体验就是App-A上面打开了App-B的ActivityB。在这种情况下,一旦我们按home键返回桌面或者查看最近任务列表,两个压在一起的Task就会被拆开,这时再回到App-A就不会看到ActivityB了。(当作者使用微信向QQ转发文件时,发现QQ选择联系人页面使用的就是这个启动逻辑,有兴趣的小伙伴可以试一试)。

那么问题来了,为什么App-B的ActivityB会在App-B上启动,我们明明是在App-A里启动Activity的呀?这是因为每个Activity都会有一个taskAffinity属性,这个属性可以在AndroidManifest.xml中设置(设置的字符串中必须加.,否则会报错),这个属性时Task相似、相近的意思,taskAffinity属性相同的Activity会归属同一个Task栈。(如过没设置这个属性,Activity的taskAffinity默认取自Application的taskAffinity,而Application的taskAffinity默认取自包名,而Task的taskAffinity的值取自Task栈底Activity的taskAffinity)。如果启动模式是standard或singleTop,会忽略taskAffinity属性,而singleTask或singleInstance则会检查这个属性,把启动的Activity归属于属于它的Task中。这样,我们遇到的问题就迎刃而解了。

同时这个启动模式有ClearTop属性,它会清空此Activity上的所有Activity,以便此Activity的复用。

单例模式

单例模式和栈内复用的工作机制是相同的,但是它比栈内复用做的更绝,它不仅要求该Task中仅有此Activity的一个实例,还要此Activity独占一个Task。那么这种启动模式就具有很强的跨App交互意图。

总结:在同一个App中,往往使用前两种启动模式,当你得App想要对外提供服务,可以考虑单例模式,而栈内复用是兼容派,既可以在一个App中使用,也可以跨App启动。总之,根据具体场景具体分析。

启动模式的设置方式

<activity    
	android:name=".MainActivity"    
	android:launchMode="standard" /> 

4.Activity生命周期

针对Activity的生命周期,我们可以分两类进行讨论:一种是正常情况下的生命周期,一种是异常情况下的生命周期。

正常情况下的生命周期:

分析生命周期,先要了解Activity的生命周期函数:

  • onCreate:表示Activity正在被创建,这时可以进行一些初始化工作,如我们的界面布局的加载,控件的绑定。onCreate方法不是一个常驻方法,完成工作后就会进入onStart方法。
  • onRestart:Activity由onStop重新变为可见状态时调用。
  • onStart:在Activity由不可见变为即将可见时调用。
  • onResume:在Activity准备好与用户进行交互时调用。此时的Activity一定位于返回栈的栈顶,并且处于运行状态。
  • onPause:当系统准备去启动或者恢复另一个Activity时调用,在这个方法中将一些消耗CPU的资源都释放掉,以及保存一些关键的数据,这个方法执行的速度一定要快,不然会影响栈顶活动的使用。
  • onStop:在Activity完全不可见时调用。
  • onDestroy:在Activity被销毁前调用,之后活动的状态变为销毁状态。

情景分析:

Activity A 是 singleTask 模式,Activity B 启动模式为常规模式时 , 多种不同情况下执行方法:

  1. Activity A 跳转到 Activity B:

    A.onCreat() -> A.onStart() -> A.onResume() -> A.onPause() -> B.onCreat() -> B.onStart() -> B.onResume() -> A.onStop()
    
  2. Activity B 跳转到 Activity A:

    B.onPause() ->  A.onRestart() -> A.onStart() -> A.onResume() -> B.onStop()
    
  3. Activity B 点 击 back 键 :

    B.onPause() -> A.onRestart() -> A.onStart() -> A.onResume() ->B.onStop() -> B.onDestroy()
    
  4. Activity B 按 home 键,然后打开应用:

    B.onPause() -> B.onStop() -> B.onRestart() ->B.onStart() -> B.onResume()
    
  5. Activity A 启动一个完全透明的 Activity B 时会执行:

    A.onCreat() -> A.onStart() -> A.onResume()-> A.onPause() -> B.onCreat() -> B.onStart() -> B.onResume()
    

Activity A 是常规模式,Activity B 启动模式为常规模式时,多种不同情况下执行方法:

  1. Activity A 跳转到 Activity B:

    A.onCreat() -> A.onStart() -> A.onResume() -> A.onPause() ->B.onCreat() -> B.onStart() -> B.onResume() -> A.onStop()
    
  2. Activity B 跳转到 Activity A:

    B.onPause() -> A.onCreat() -> A.onStart() -> A.onResume() -> B.onStop()
    
  3. Activity B 点 击 back 键 :

    B.onPause() -> A.onRestart() -> A.onStart() -> A.onResume() -> B.onStop() -> B.onDestroy()
    
  4. Activity B 按 home 键,然后打开应用:

    B.onPause() -> B.onStop() -> B.onRestart() -> B.onStart() -> B.onResume()
    

异常情况下的生命周期

为什么会有异常情况?

当系统资源回收或者配置发生改变时,就会导致Activity重建。系统资源回收通常发生在内存不足,配置发生变化通常为屏幕方向发生变化、系统语言更改等情况。

为什么要重建?

对于资源回收的情况,保存状态到使用时再恢复,远比后台保留进程所占的资源小的多。

对于配置情况的改变,只有重建,才能加载不同的布局。

重建机制:onSaveInstanceState & onRestoreInstanceState

重建机制就是当Activity因为异常情况需要重建时,通过onSaveInstanceState方法保留活动当前的状态,如:EditText文本内容、CheckBox选项框状态等,当重建Activity时,通过onRestoreInstanceState方法恢复其状态。

onSaveInstanceState方法在onStop方法之前被调用,但是和onPause方法没有时序关系。

onRestoreInstanceState方法执行在onStart之后。

重建流程

Activity 在 onSaveInstanceState 时,会 自动收集 View Hierachy 中每一个 “实现了状态保存和恢复方法” 的 View 的状态,这些状态数据会在 onRestoreInstanceState 时回传给每一个 View。并且回传时是依据 View 的 id 来逐一匹配的。

状态保存和恢复的注意事项

  1. 为了成功保存状态,要求在view内部实现保存和恢复方法

    原生的 View 都有做到;如果是自定义 View,务必记住这一点;如果第三方 View 没做到,可以通过继承其来实现保存和恢复方法。

  2. 为了成功的恢复状态,需要给View赋予对应的ID

  3. 如果需要保存Activity的成员变量,需要手动重写onSaveInstanceState 和 onRestoreInstanceState 方法。

    重写仅仅是为了额外地保存成员变量。重写方法时,记得要保留基类(super)的实现,Activity 正是在基类中完成 View 状态的保存和恢复。

5.Activity启动流程

分析Activity的启动流程,我们先看如何启动一个Activity

Intent intent = new Intent(ActivityA.this,ActivityB.class);
startActivity(intent);

那我们就进入startActivity这个方法一探究竟。

通过对源码的阅读,我发现通过方法调用链最终调用startActivityForResult这个方法:

public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
            @Nullable Bundle options) 
        if (mParent == null) 
            options = transferSpringboardActivityOptions(options);
            //重点
            Instrumentation.ActivityResult ar =
                mInstrumentation.execStartActivity(
                    this, mMainThread.getApplicationThread(), mToken, this,
                    intent, requestCode, options);
            if (ar != null) 
                mMainThread.sendActivityResult(
                    mToken, mEmbeddedID, requestCode, ar.getResultCode(),
                    ar.getResultData());
            
            if (requestCode >= 0) 
                mStartedActivity = true;
            
            cancelInputsAndStartExitTransition(options);
         else 
            if (options != null) 
                mParent.startActivityFromChild(this, intent, requestCode, options);
             else 
                mParent.startActivityFromChild(this, intent, requestCode);
            
        
    

核心逻辑就是调用Instrumentation.execStartActivity:

public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) 
        ......
        try 
            intent.migrateExtraStreamToClipData(who);
            intent.prepareToLeaveProcess(who);
            //重点
            int result = ActivityTaskManager.getService().startActivity(whoThread,
                    who.getBasePackageName(), who.getAttributionTag(), intent,
                    intent.resolveTypeIfNeeded(who.getContentResolver()), token,
                    target != null ? target.mEmbeddedID : null, requestCode, 0, null, options);
            checkStartActivityResult(result, intent);
         catch (RemoteException e) 
            throw new RuntimeException("Failure from system", e);
        
        return null;
    

看这个方法名,猜测是得到ActivityTaskManagerService,然后调用其的startActivity方法:

注:网上大部分博客讲解Activity启动流程中这里介绍为AMS,这是Android10之前的写法,从Android10,将Activity启动服务的逻辑转移到ActivityTaskManagerService中,没有本质差别。

public final int startActivity(IApplicationThread caller, String callingPackage,
            String callingFeatureId, Intent intent, String resolvedType, IBinder resultTo,
            String resultWho, int requestCode, int startFlags, ProfilerInfo profilerInfo,
            Bundle bOptions) 
        return startActivityAsUser(caller, callingPackage, callingFeatureId, intent, resolvedType,
                resultTo, resultWho, requestCode, startFlags, profilerInfo, bOptions,
                UserHandle.getCallingUserId());
    

startActivityAsUser:

private int startActivityAsUser(IApplicationThread caller, String callingPackage,
        @Nullable String callingFeatureId, Intent intent, String resolvedType,
        IBinder resultTo, String resultWho, int requestCode, int startFlags,
        ProfilerInfo profilerInfo, Bundle bOptions, int userId, boolean validateIncomingUser) 
    assertPackageMatchesCallingUid(callingPackage);
    enforceNotIsolatedCaller("startActivityAsUser");

    userId = getActivityStartController().checkTargetUser(userId, validateIncomingUser,
            Binder.getCallingPid(), Binder.getCallingUid(), "startActivityAsUser");

    // TODO: Switch to user app stacks here.
    return getActivityStartController().obtainStarter(intent, "startActivityAsUser")
            .setCaller(caller)
            .setCallingPackage(callingPackage)
            .setCallingFeatureId(callingFeatureId)
            .setResolvedType(resolvedType)
            .setResultTo(resultTo)
            .setResultWho(resultWho)
            .setRequestCode(requestCode)
            .setStartFlags(startFlags)
            .setProfilerInfo(profilerInfo)
            .setActivityOptions(bOptions)
            .setUserId(userId)
            .execute();


这里通过工厂模式创建ActivityStarter实例。

ActivityStarter obtainStarter(Intent intent, String reason) 
        return mFactory.obtain().setIntent(intent).setReason(reason);
    

通过obtain进行内存复用,避免频繁发创建ActivityStarter对象,导致内存不足触发GC造成的内存抖动。

ActivityStarter.execute:

int execute() 
        try 
            if (mRequest.intent != null && mRequest.intent.hasFileDescriptors()) 
                throw new IllegalArgumentException("File descriptors passed in Intent");
            

            final LaunchingState launchingState;
            synchronized (mService.mGlobalLock) 
                final ActivityRecord caller = ActivityRecord.forTokenLocked(mRequest.resultTo);
                launchingState = mSupervisor.getActivityMetricsLogger().notifyActivityLaunching(
                        mRequest.intent, caller);
            

            if (mRequest.activityInfo == null) 
                mRequest.resolveActivity(mSupervisor);
            

            int res;
            synchronized (mService.mGlobalLock) 
                final boolean globalConfigWillChange = mRequest.globalConfig != null
                        && mService.getGlobalConfiguration().diff(mRequest.globalConfig) != 0;
                final ActivityStack stack = mRootWindowContainer.getTopDisplayFocusedStack();
                if (stack != null) 
                    stack.mConfigWillChange = globalConfigWillChange;
                
                if (DEBUG_CONFIGURATION) 
                    Slog.v(TAG_CONFIGURATION, "Starting activity when config will change = "
                            + globalConfigWillChange);
                

                final long origId = Binder.clearCallingIdentity();

                res = resolveToHeavyWeightSwitcherIfNeeded();
                if (res != START_SUCCESS) 
                    return res;
                
                //开始请求
                res = executeRequest(mRequest);

                Binder.restoreCallingIdentity(origId);

                if (globalConfigWillChange) 

                    mService.mAmInternal.enforceCallingPermission(
                            android.Manifest.permission.CHANGE_CONFIGURATION,
                            "updateConfiguration()");
                    if (stack != null) 
                        stack.mConfigWillChange = false;
                    
                    if (DEBUG_CONFIGURATION) 
                        Slog.v(TAG_CONFIGURATION,
                                "Updating to new configuration after starting activity.");
                    
                    mService.updateConfigurationLocked(mRequest.globalConfig, null, false);
                

                mSupervisor.getActivityMetricsLogger().notifyActivityLaunched(launchingState, res,
                        mLastStartActivityRecord);
                return getExternalResult(mRequest.waitResult == null ? res
                        : waitForResult(res, mLastStartActivityRecord));
            
         finally 
            onExecutionComplete();
        
    

executeRequest:

private int executeRequest(Request request) 
    
    ......
	//每个Activity都对应一个ActivityRecord,存放于ActivityStack中
    final ActivityRecord r = new ActivityRecord(mService, callerApp, callingPid, callingUid,
            callingPackage, callingFeatureId, intent, resolvedType, aInfo,
            mService.getGlobalConfiguration(), resultRecord, resultWho, requestCode,
            request.componentSpecified, voiceSession != null, mSupervisor, c

以上是关于四大组件—Activity的主要内容,如果未能解决你的问题,请参考以下文章

Android基础之四大组件---Activity

Android的四大组件是哪些,它们的作用

Android - Activity解析

Android - Activity解析

Android - Activity解析

Android四大组件——Activity