IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Activity的初级,太厉害了 -> 正文阅读

[移动开发]Activity的初级,太厉害了

在使用Activity的startActivityForResult启动新界面时,在Api20以下调整时会直接返回Activity.RESULT_CANCELED,官方觉得不应该在两个任务之间setResult。在Api20及以上,对于非startActivity跳转,也就是reqeusetCode>=0,singleTask和SingleInstance模式启动的Activity都不会新建一个任务,还是在原来的栈中。同时官方也建议:

虽然所有 API 级别的 Activity 类均提供底层 [startActivityForResult()](developer.android.com/reference/a…, int)) 和 [onActivityResult()](developer.android.com/reference/a…, int, android.content.Intent)) API,但我们强烈建议您使用 AndroidX Activity 1.2.0-alpha02Fragment 1.3.0-alpha02 中引入的 Activity Result API。

5.清除返回栈(Clearing the back stack)的一些概念

如果用户离开任务较长时间,系统会清除任务中除根 Activity 以外的所有 Activity。当用户再次返回到该任务时,只有根 Activity 会恢复。系统之所以采取这种行为方式是因为,经过一段时间后,用户可能已经放弃了之前执行的操作,现在返回任务是为了开始某项新的操作。

您可以使用一些 Activity 属性来修改此行为:

  • alwaysRetainTaskState

    如果在任务的根 Activity 中将该属性设为 "true",则不会发生上述默认行为。即使经过很长一段时间后,任务仍会在其堆栈中保留所有 Activity。

  • clearTaskOnLaunch

    如果在任务的根 Activity 中将该属性设为 "true",那么只要用户离开任务再返回,堆栈就会被清除到只剩根 Activity。也就是说,它与 alwaysRetainTaskState 正好相反。用户始终会返回到任务的初始状态,即便只是短暂离开任务也是如此。

  • finishOnTaskLaunch

    该属性与 clearTaskOnLaunch 类似,但它只会作用于单个 Activity 而非整个任务。它还可导致任何 Activity 消失,包括根 Activity。如果将该属性设为 "true",则 Activity 仅在当前会话中归属于任务。如果用户离开任务再返回,则该任务将不再存在。

6.allowTaskReparenting的使用

Activity 默认情况下只会归属于一个 Task,不会在多个 Task 之间跳来跳去,但你可以通过设置来改变这个逻辑。把它的 allowTaskReparenting 属性设置为 true。如果未设置该属性,则由 <Activity> 元素的相应 allowTaskReparenting 属性所设置的值。默认值为“false”。

正常情况下,Activity 启动时会与启动它的任务关联,并在其整个生命周期中一直留在该任务处。当不再显示现有任务时,您可以使用该属性强制 Activity 将其父项更改为与其有相似性的任务。该属性通常用于将应用的 Activity 转移至与该应用关联的主任务。

例如,如果电子邮件消息包含网页链接,则点击该链接会调出可显示该网页的 Activity。该 Activity 由浏览器应用定义,但作为电子邮件任务的一部分启动。如果将该 Activity 的父项更改为浏览器任务,则它会在浏览器下一次转至前台时显示,在电子邮件任务再次转至前台时消失

7.Activity的隐式启动

Activity分为显示启动和隐式启动,显示启动就是我们平时调用的一些startActivityXXX()方法,隐式启动可以通过action来启动,启动时调用如下,同时要记得添加category为"android.intent.category.DEFAULT"

Intent implicitIntent = new Intent();
implicitIntent.setAction("com.test.image");
implicitIntent.addCategory("android.intent.category.DEFAULT");
MainActivity.this.startActivity(implicitIntent); 

具体界面的配置如下:

<activity android:name=".ImageActivity"
    android:exported="true">
    <intent-filter>

        <action android:name="android.intent.action.MAIN" />
        <action android:name="com.test.image" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity> 

注意如果是其他App的Activity,需要添加android:exported="true"才能被调用。

8.Activity的启动流程(中级问题)

对很多开发者来说,这可能都是个很沉重的问题,原因很简单,因为回答不好,毕竟里面涉及到的东西很多,需要你拥有很大知识存储量。下面来尝试回答这个问题(基于源码9.0)

首先先普及一些常见的概念

Instrumentation

Android Instrumentation是Android系统中的一套控制方法或者“钩子”,这些钩子可以在正常的生命周期(正常是由操作系统控制的)之外控制Android控件的运行,其实指的就是Instrumentation类提供的各种流程控制方法

app->instrumentation->ams->app,自动化测试可以通过Instrumentation来操作Activity等,这个Instrumentation相当于设计了一个统一的入口

ActivityThread

ActivityThread不是线程类(Thread),只不过它会跑在ActivityThread.main()方法中,安卓程序的入口就是该方法,同时在该方法中一个Looper不断循环的在消息队列中处理消息。管理应用程序进程中主线程的执行,根据Activity管理者的请求调度和执行activities、broadcasts及其相关的操作。

public static void main(String[] args) {
    // 看源码很重要的一个能力就是‘眼中只有你’,认不到的都忽略,看认得到的
    ···
    // 创建主线程的Looper对象,发现和工作线程创建Looper对象调用的方法不一样,这里先记下,以后在详解。
    // 主线程原来也有Looper对象啊
    Looper.prepareMainLooper();

    //创建ActivityThread
    ActivityThread thread = new ActivityThread();
    thread.attach(false);

    // 如果主线程的Handler为空(可以看出,一个好的命名可读性是多么高),那就为主线程创建一个Handler。
    // 然后我们还可以在主线程创建Handler,说明一个线程对应多个Handler。多读源码,很多问题都得到了解决啊。
    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }
    Looper.loop();
    
    // 这里抛了个异常,主线程loop异常退出。说明主线程loop不能退出,这里和前面建立Looper对象的调用方法有关
    throw new RuntimeException("Main thread loop unexpectedly exited");
} 
ActivityManagerService

Android中最核心的服务,主要负责系统中四大组件的启动、切换、调度及应用程序的管理和调度等工作。

ActivityManager

该类提供与Activity、Service和Process相关的信息以及交互方法, 可以被看作是ActivityManagerService的辅助类。

ActivityStackSupervisor

负责所有Activity栈的管理。内部管理了mHomeStack、mFocusedStack和mLastFocusedStack三个Activity栈。其中,mHomeStack管理的是Launcher相关的Activity栈;mFocusedStack管理的是当前显示在前台Activity的Activity栈;mLastFocusedStack管理的是上一次显示在前台Activity的Activity栈。下面是大致的关系图,对于没有分屏功能以及虚拟屏的情况下,ActivityStackSupervisor与ActivityDisplay都是系统唯一。

ActivityStack

ActivityStack负责“Activity栈”的状态和管理,ActivityStack内部包含了多个任务栈(TaskRecord),TaskRecord内部维护了一个ArrayList<ActivityRecord>用来保存和管理ActivityRecord,ActivityRecord包含了一个Activity的所有信息

如果我们从桌面点击启动app,桌面就是一个Activity,点击app(按钮)启动我们的启动页Activity,从这里分析Activity的启动流程更加全面,而不是在app中去启动一个普通的Activity。可以分为如下几个流程

  1. Launcher通知AMS启动App的启动页Activity,AMS记录要启动的Activity信息,并且通知Launcher进入pause状态。

    Launcher进入pause状态后,通知AMS已经paused了,可以启动App了

  2. 如果App未开启过,AMS发送创建进程请求,Zogyte进程接受AMS请求并孵化应用进程,应用进程调用ActivityThread并调用mian()方法,并且main()方法中创建ActivityThread对象,activityThread.attach()方法中进行绑定(应用进程绑定到AMS),传入applicationThread以便通讯。

  3. AMS通知App绑定Application(bindApplication)并启动Activity,并且创建和关联Context,最后调用onCreate等方法。

灵魂拷问:AMS,Zogyte,App进程,Launcher如何通信?

这个问题一旦问出来,能干翻一大堆开发人员,下面来仔细讲讲:

App进程和AMS是如何通信的?

Zogyte去fork一个App进程,后面就是应用进程和AMS两者的事情了,我们知道Android的跨进程通信是通过Binder服务的,AMS所在的进程和应用进程在通过Binder互相通信时,实际上都是通过两者的代理类进行通信的。

ActivityManagerService(AMS)在手机开机后时就已经启动了,应用进程去调用AMS的方法,比如startActivity,很容易调用,因为AMS是一个有名称的Binder服务,在任意地方都可以通过在ServiceManger(SM)里面查询拿到代理类,调用代理类的对应方法,然后再去调用AMS的真正方法。

因为Binder通信是通过代理类来通信的,如果拿不到代理类,其他进程就不知道如何和我们的App通信,系统服务中的AMS也就不知道如何和我们App通信了,所以当App进程创建完成后,会进行设置代理,代理的设置过程如图

就是在ActivityThread.attach(false)方法中,AMS绑定ApplicationThread对象,即应用进程绑定到AMS,通过调用AMS的attachApplication来将ActivityThread的内部类ApplicationThread对象绑定至AMS,这样AMS就可以通过这个代理对象来控制应用进程

AMS和Launcher是怎么通信的?

其实Launcher也是一个App,调用startActivity方法,然后调用的是Instrumentation的execStartActivity方法

public ActivityResult execStartActivity(
        Context who, IBinder contextThread, IBinder token, Activity target,
        Intent intent, int requestCode, Bundle options) {
    ...
    try {
	   ...
	    //获取AMS的代理对象
        int result = ActivityManager.getService()
            .startActivity(whoThread, who.getBasePackageName(), 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;
} 

在这个方法会调用ActivityManager的getService方法来得到AMS的代理对象,然后调用这个代理对象的startActivity方法

@UnsupportedAppUsage
public static IActivityManager getService() {
    return IActivityManagerSingleton.get();
}

@UnsupportedAppUsage
private static final Singleton<IActivityManager> IActivityManagerSingleton =
        new Singleton<IActivityManager>() {
            @Override
            protected IActivityManager create() {
                //得到activity的service引用,即IBinder类型的AMS引用
                final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
				        //转换成IActivityManager对象
                final IActivityManager am = IActivityManager.Stub.asInterface(b);
                return am;
            }
        }; 

可以发现在Singleton中的create方法中由于b是AMS引用作为服务端处于SystemServer进程中,与当前Launcher进程作为客户端与服务端不在同一个进程,所以am返回的是IActivityManager.Stub的代理对象,此时如果要实现客户端与服务端进程间的通信,只需要在AMS继承了IActivityManager.Stub类并实现了相应的方法,而通过下面的代码可以发现AMS刚好是继承了IActivityManager.Stub类的,这样Launcher进程作为客户端就拥有了服务端AMS的代理对象,然后就可以调用AMS的方法来实现具体功能了,就这样Launcher的工作就交给AMS实现了。

public class ActivityManagerService extends IActivityManager.Stub
        implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {
} 

Zygote和AMS是如何通信的?

AMS和Zygote建立Socket连接,然后发送创建应用进程的请求。具体可以参考这里

最后我们再来看看流程图,看下方的App进程启动过程和Activity.startActivity这两个流程

这里还要提到一点,Hook Activity的启动流程是一个很重要的运用场景,我们需要欺骗AMS,然后启动真正的TargetActivity,Hook有起始点和终点。这里需要寻找两个地方的hook点,一个是对Intent中Activity的替换(hookIActivityTaskManager方法),一个是对Intent中Activity的还原(hookHandler)。

在回答Activity的启动流程时,具体的方法如何调用并不重要,所以我才会在最后放出整个流程,各个进程之间如何建立通信,如何通信很重要,同时一些Activity相关概念也很重要,熟悉这些,你就很容易把整个流程串起来了。

Activity深层次问题

1.Activity生命周期的变化对进程的优先级有什么影响?

这里先看一下官网上Activity生命周期上对onStart的一段描述,onStart时候Activity就对用户可见了

同时你也可以在《Android开发艺术探索》上看到类似的描述

但是了解Activity启动流程源码的朋友都知道,ActivityThread的handleResumeActivity方法中,首先调用Activity的onResume方法,接着会调用Activity.makeVisible()在该方法中,DecorView真正完成了添加和显示这两个过程,到这里Activity的视图才能被看到。DecoreView和Window进行关联。有兴趣可以看看我这篇文章的分析

void makeVisible() {
        if (!mWindowAdded) {
            ViewManager wm = getWindowManager();
          	//DecoreView和WindowManager进行关联。
            wm.addView(mDecor, getWindow().getAttributes());
            mWindowAdded = true;
        }
  			//设置DecorView可见
        mDecor.setVisibility(View.VISIBLE);
    } 

也就是说在onResume方法执行之后再调用Activity.makeVisible()方法,我们才能真正用肉眼看到我们的DecoreView,看到这里你这里不禁会产生一个疑问,那上面官网上的说法(onStart() 调用使 Activity 对用户可见)难道是错误的吗?

带着疑问我们继续在官网上找答案,在进程和生命周期这一章节上可以看到:

为了确定在内存不足时应该终止哪些进程,Android 会根据每个进程中运行的组件以及这些组件的状态,将它们放入“重要性层次结构”。这些进程类型包括(按重要性排序):

  1. 前台进程是用户目前执行操作所需的进程。在不同的情况下,进程可能会因为其所包含的各种应用组件而被视为前台进程。如果以下任一条件成立,则进程会被认为位于前台:
  • 它正在用户的互动屏幕上运行一个 Activity(其 onResume() 方法已被调用)。
  • 它有一个 BroadcastReceiver 目前正在运行(其 BroadcastReceiver.onReceive() 方法正在执行)。
  • 它有一个 Service 目前正在执行其某个回调(Service.onCreate()Service.onStart()Service.onDestroy())中的代码。
  1. 系统中只有少数此类进程,而且除非内存过低,导致连这些进程都无法继续运行,才会在最后一步终止这些进程。通常,此时设备已达到内存分页状态,因此必须执行此操作才能使用户界面保持响应。

  2. 可见进程正在进行用户当前知晓的任务,因此终止该进程会对用户体验造成明显的负面影响。在以下条件下,进程将被视为可见:

    • 它正在运行的 Activity 在屏幕上对用户可见,但不在前台(其 onPause() 方法已被调用)。举例来说,如果前台 Activity 显示为一个对话框,而这个对话框允许在其后面看到上一个 Activity,则可能会出现这种情况。
    • 它有一个 Service 正在通过 Service.startForeground()(要求系统将该服务视为用户知晓或基本上对用户可见的服务)作为前台服务运行。
    • 系统正在使用其托管的服务实现用户知晓的特定功能,例如动态壁纸、输入法服务等。

    相比前台进程,系统中运行的这些进程数量较不受限制,但仍相对受控。这些进程被认为非常重要,除非系统为了使所有前台进程保持运行而需要终止它们,否则不会这么做。

  3. 服务进程包含一个已使用 startService() 方法启动的 Service 。虽然用户无法直接看到这些进程,但它们通常正在执行用户关心的任务(例如后台网络数据上传或下载),因此系统会始终使此类进程保持运行,除非没有足够的内存来保留所有前台和可见进程。

    已经运行了很长时间(例如 30 分钟或更长时间)的服务的重要性可能会降位,以使其进程降至下文所述的缓存 LRU 列表。这有助于避免超长时间运行的服务因内存泄露或其他问题占用大量内存,进而妨碍系统有效利用缓存进程。

  4. 缓存进程是目前不需要的进程,因此,如果其他地方需要内存,系统可以根据需要自由地终止该进程。在正常运行的系统中,这些是内存管理中涉及的唯一进程:运行良好的系统将始终有多个缓存进程可用(为了更高效地切换应用),并根据需要定期终止最早的进程。只有在非常危急(且具有不良影响)的情况下,系统中的所有缓存进程才会被终止,此时系统必须开始终止服务进程。

    这些进程通常包含用户当前不可见的一个或多个 Activity 实例(onStop() 方法已被调用并返回)。只要它们正确实现其 Activity 生命周期(详情请见 Activity),那么当系统终止此类流程时,就不会影响用户返回该应用时的体验,因为当关联的 Activity 在新的进程中重新创建时,它可以恢复之前保存的状态。

    这些进程保存在伪 LRU 列表中,列表中的最后一个进程是为了回收内存而终止的第一个进程。此列表的确切排序政策是平台的实现细节,但它通常会先尝试保留更多有用的进程(比如托管用户的主屏幕应用、用户最后看到的 Activity 的进程等),再保留其他类型的进程。还可以针对终止进程应用其他政策:比如对允许的进程数量的硬限制,对进程可持续保持缓存状态的时间长短的限制等。

可以看到在屏幕上运行时一个Activity的onResume的方法已被调用,此时处于前台进程;可见进程的一个符合条件:它正在运行的 Activity 在屏幕上对用户可见,但不在前台,然后再对比上面对onStart的描述(onStart() 调用使 Activity 对用户可见,因为应用会为 Activity 进入前台并支持互动做准备),这下子你就豁然开朗了,这里的onStart的可见指的是可见进程的可见,而不是真正意义上的肉眼可见

“onPause此方法表示 Activity 不再位于前台(尽管在用户处于多窗口模式时 Activity 仍然可见)”,“如果您的 Activity 不再对用户可见,说明其已进入“已停止”状态,因此系统将调用 onStop() 回调”,以上都是官方的描述,我们可以打印一下手机中的这些进程,使用adb shell dumpsys meminfo命令,设备是android 10华为手机。

可以看到分别对应我们的前台进程,可见进程,服务进程和缓存进程,其中服务进程还分为A Services和B Services。其实远远不止这么多的进程级别区分,我自己的App打开后,然后点击home键退到后台,此时属于Previous进程(后台进程)级别(com.jackie.testdialog),如果我打开App后,点击返回键退出,这个时候我的App进程就变成了Cached进程级别了。

讲了这么多,你可能觉得一直没有一个量化的数字,进程的级别(oom_adj)的取值范围是多少,在Android7.0之后,ADJ采用100,200,300等数字。下面是基于android9的区分:

ADJ级别取值含义
NATIVE_ADJ-1000native进程
SYSTEM_ADJ-900仅指system_server进程
PERSISTENT_PROC_ADJ-800系统persistent进程
PERSISTENT_SERVICE_ADJ-700关联着系统或persistent进程
FOREGROUND_APP_ADJ0前台进程
VISIBLE_APP_ADJ100可见进程
PERCEPTIBLE_APP_ADJ200可感知进程,比如后台音乐播放
BACKUP_APP_ADJ300备份进程
HEAVY_WEIGHT_APP_ADJ400重量级进程
SERVICE_ADJ500服务进程
HOME_APP_ADJ600Home进程
PREVIOUS_APP_ADJ700上一个进程
SERVICE_B_ADJ800B List中的Service
CACHED_APP_MIN_ADJ900不可见进程的adj最小值
CACHED_APP_MAX_ADJ906不可见进程的adj最大值

开发者应该减少在保活上花心思,更应该在优化内存上下功夫,因为在相同ADJ级别的情况下,系统会选择优先杀内存占用的进程。当然你也可以手动去测试App的进程级别,不过过程可能有点麻烦,可以参考这篇文章

小结

当界面只有一个Activity时,它进入onStart和onPause时是可见进程,进入onResume时是前台进程,打开后点击Home键退到后台这个时候是Previous进程(后台进程),如果直接点击返回键退出Activity,这个时候是缓存进程;如果有多个Activity(注意这个时候只有app从后台任务进入前台,或者点击Home键退到后台这两种场景;因为app在前台运行时都是前台进程),栈顶的的Activity进入onStart和onPause时是可见进程,进入onResume后是前台进程,点击Home键退到后台时是Previous进程(大家常说的后台进程)。

2.如果App还存在缓存进程,这个时候启动App,应用Application的onCreate方法会执行吗?

如果你点击主界面MainActivity,点击返回键后系统执行MainActivity的onDestory方法,这个时候App进程为缓存进程,下次启动App你会发现Application的onCreate方法并不会执行,当然MainActivity的生命周期都会正常执行,这是因为从缓存进程启动App,系统已经缓存了很多信息,很多数据并不会被销毁,onCreate中初始化的那些内容还在,方便用户下次快速启动。利用这一特性,我们的App首次启动速度一般为500600ms,退出App后存在缓存进程的情况下,每次启动的速度一般为200300ms,算是某种程度上提升了App的启动时间。

需要注意的是,很多App在退出主界面的时候,会手动调用如下代码去退出App

System.exit(0); 

一旦调用了如下代码,就会彻底的退出并不会利用缓存进程的优势,也失去了系统提供给我们的优化了。

3.一个Activity A启动另一个Activity B,为何会先走A的onPause方法,等到B执行完onResume方法后,才会走A的onStop方法呢?

如果你看过前面两个问题,这个问题你可能已经有答案了。手机之所以进行进程的管理,用不同的优先级对进程进行区分,首先肯定是为了保证用户的流畅体验,对于优先级低且占用内存高的进程及时清理,保证前台进程有足够的运行空间。前面我们讲到处于前台的(获取焦点)界面只有一个,onPause时当前进程离开了前台,当然可能也要进行一些数据的保存,所以肯定需要先执行当前界面的某个方法,然后再执行B界面的onCreate,onStart,onResume是为了新的界面能够被快速呈现(获取焦点),然后再走旧界面A的onStop方法。

这里也需要注意,onPause方法中尽量不要去做耗时的操作,如果过于耗时,新界面会很久才能显示出来,尽量放在onStop方法中去做。当然onStop中也不能做过于耗时的操作中,前面我们也试过,点击Home键会执行onStop方法,此时App进程处于后台进程,此时进程的优先级的很低的,当内存不足时,onStop中保存数据的操作可能就未完成,然后App进程就被系统回收了。

关于状态保存和恢复,在API28之前,onSaveInstanceState执行在onStop之前,但不限于在onPause之前或之后;在API28之后,onSaveInstanceState 执行时机已确定为在 onStop 之后。而onRestoreInstanceState确定执行在onStart之后。

4.为什么要这么设计Activity生命周期

假如你自己设计界面的生命周期:

  • 界面启动时候用需要设计一个方法

  • 界面完全渲染完毕显示需要一个方法

  • 界面被部分遮盖时/跳到其他界面/退到后台需要一个方法

  • 界面完全退出销毁时需要一个方法

这么看来,我们好像只需要onCreate,onResume,onPause,onDestroy这四个方法,但是这只是一个很粗糙的界面创建~退出流程的回调,但是你看看IOS的UIViewController的生命周期,看起来就是个精致的猪猪女孩

这样一对比,连Android的生命周期显得有点粗糙了,其实不全是,Activity还有一系列的onPostXXX方法以及onContentChanged等,但还是没有IOS细腻。其实我觉得,这些生命周期的回调是基于一些场景设计的,从视图的显示到销毁,考虑到不同的需求,我们需要不同程度级别的设计,如果Android是一个非常简单的系统,也不会实现那么多的特殊需求,可能只需要前面我说的那四个方法就够了,我感觉在生命周期的设计方面,IOS做的更好一些,对开发者更加友好

也有一些人在回答生命周期为什么要这么设计时,可能会这么回答,因为界面需要有个创建/销毁过程,onCreate/onDestroy肯定需要,onStart时进程为可见进程,提升进程的优先级,或者做一些特殊场景的操作,onResume在界面启动完成或者恢复时需要,界面在被透明Activity的覆盖时会执行onPause(),需要有个方法在这个时候做状态保存或特殊操作等,onStop时可以进行状态保存。这样想问题完全是一种结果倒推的想法,经不起仔细的推敲,一定不要从具体的方法去推场景,而是应该从需求场景开始推导,切记,这一切都是需求或可能的需求引起的

第三方App中一些Activity的设置

今日头条极速版-新闻界面打开的一些限制

NewDetailActivity就是我们看到的普通新闻界面,最多只能打开四个,超过四个就会将之前最早的NewDetailActivity关闭,原因很简单,如果无限制的话Activity会越建越多,整个应用越来越卡,影响用户体验。

TaskRecord{8636d7b #6564 A=com.ss.android.article.lite U=0 StackId=282 sz=5}
        Run #4: ActivityRecord{8794744 u0 com.ss.android.article.lite/com.ss.android.article.base.feature.detail2.view.NewDetailActivity t6564}
        Run #3: ActivityRecord{8be5248 u0 com.ss.android.article.lite/com.ss.android.article.base.feature.detail2.view.NewDetailActivity t6564}
        Run #2: ActivityRecord{8bd6a09 u0 com.ss.android.article.lite/com.ss.android.article.base.feature.detail2.view.NewDetailActivity t6564}
        Run #1: ActivityRecord{87cc383 u0 com.ss.android.article.lite/com.ss.android.article.base.feature.detail2.view.NewDetailActivity t6564}
        Run #0: ActivityRecord{8bd6b44 u0 com.ss.android.article.lite/.activity.SplashActivity t6564} 

而且还可以发现这个今日头条极速版的主页叫SplashActivity,真他么牛逼~,估计是原来有个SplashActivity界面和MainActivity界面,为了优化快速启动,给用户一个秒开的感觉,移除原来的SplashActivity,直接把MainActivity改名为SplashActivity,然后做主题的替换。

然后我们看看它的启动模式,启动模式是standard。

~ ? adb shell dumpsys activity | grep SplashActivity                                                             jackie@JackieLindeMacBook-Pro
    baseIntent=Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.ss.android.article.lite/.activity.SplashActivity }
    baseIntent=Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.sina.weibo/.SplashActivit }
    baseIntent=Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=cmccwm.mobilemusic/.ui.base.SplashActivity }
    baseIntent=Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.daimajia.gold/im.juejin.android.ui.SplashActivity }
      Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.ss.android.article.lite/.activity.SplashActivity bnds=[544,149][796,458] }
      mActivityComponent=com.ss.android.article.lite/.activity.SplashActivity
    mIntent=Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.ss.android.article.lite/.activity.SplashActivity bnds=[544,149][796,458] }
     #0 ActivityRecord{8e43505 u0 com.ss.android.article.lite/.activity.SplashActivity t6684} type=standard mode=fullscreen override-mode=undefined  //启动模式是standard
      intent={act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.ss.android.article.lite/.activity.SplashActivity}
      mActivityComponent=com.ss.android.article.lite/.activity.SplashActivity
      Activities=[ActivityRecord{8e43505 u0 com.ss.android.article.lite/.activity.SplashActivity t6684}]
        Hist #0: ActivityRecord{8e43505 u0 com.ss.android.article.lite/.activity.SplashActivity t6684}
          Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.ss.android.article.lite/.activity.SplashActivity bnds=[544,149][796,458] }
        Run #0: ActivityRecord{8e43505 u0 com.ss.android.article.lite/.activity.SplashActivity t6684}
    mResumedActivity: ActivityRecord{8e43505 u0 com.ss.android.article.lite/.activity.SplashActivity t6684}
      intent={act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.sina.weibo/.SplashActivity}
      mActivityComponent=com.sina.weibo/.SplashActivity
      intent={act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=cmccwm.mobilemusic/.ui.base.SplashActivity}
      mActivityComponent=cmccwm.mobilemusic/.ui.base.SplashActivity
 ResumedActivity:ActivityRecord{8e43505 u0 com.ss.android.article.lite/.activity.SplashActivity t6684}
  ResumedActivity: ActivityRecord{8e43505 u0 com.ss.android.article.lite/.activity.SplashActivity t6684} 

再来看今日头条的首页的启动模式,它的首页叫MainActivity,用的也是standard。

adb shell dumpsys activity | grep MainActivity                                                                                                          jackie@JackieLindeMacBook-
      Intent { flg=0x24008000 cmp=com.ss.android.article.news/.activity.MainActivity (has extras) }
      mActivityComponent=com.ss.android.article.news/.activity.MainActivity
    mIntent=Intent { flg=0x24008000 cmp=com.ss.android.article.news/.activity.MainActivity (has extras) }
                                                                                       jackie@JackieLindeMacBook-
      Intent { flg=0x24008000 cmp=com.ss.android.article.news/.activity.MainActivity (has extras) }
      mActivityComponent=com.ss.android.article.news/.activity.MainActivity
    mIntent=Intent { flg=0x24008000 cmp=com.ss.android.article.news/.activity.MainActivity (has extras) }
 
  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-08-31 15:33:43  更:2021-08-31 15:34:03 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 13:41:48-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码