likes
comments
collection
share

十三、 梦开始的地方-startActivity

作者站长头像
站长
· 阅读数 5

概述

对于安卓工程师来说,startActivity就如同初恋一般,熟悉又陌生。在写startActivity代码时,有一些比较重要的问题,同时也是可能的面试点。

骚气的TaskAffinity

回顾一下,activity的四种启动模式,一个标准模式(standard),3个复用模式(SingleTop,SingleTask,SingleIntance),都是在不同的场景下尽可能减少activity的创建,以及尽可能回收不再需要的activity,以保证内存的最大利用率。

而在 启动模式之外,还有一个重要参数,也能影响到activity栈结构。每一个Activity都有一个affinity属性。如果不在 manifest中静态注册activity时指定 affinity,那么就默认是当前 packageName.

单词 Affinity,本意是:亲密感、吸引力或相似性。它可以描述人之间或事物之间的紧密联系或吸引力。例如,你可能会说你有某人的亲密感或有一种对某个活动的亲近感。 在安卓中,taskAffinity 可以理解为 一个 改变任务栈之间关联关系的属性。

理论上来说,只要我们改变了一个Activity的taskAffinity属性,那么它的任务栈就有可能发生变化

实验阶段一 原始状态

创建 MainActivity 和 MainActivity2 ,这两个Activity,做一个简单的startActivity跳转,从 MainActivity 到 MainActivity2,没有其他任何特别设定。

完成跳转之后,使用命令 adb shell dumpsys activity activities

可以看到,这两个Activity处于同一个任务栈中。

十三、  梦开始的地方-startActivity

实验阶段二 仅将MainActivity2的taskAffinity改变为my.affinity

<activity
    android:name=".MainActivity2"
    android:theme="@style/Theme.AppCompat"
    android:taskAffinity="my.affinity"
    />

重复从MainActivity跳转到MainActivity2的操作,完成跳转之后,使用命令 adb shell dumpsys activity activities

十三、  梦开始的地方-startActivity

注意黄色标识,两个Activity的taskAffinity虽然已经不同,但是MainActivity2仍然和MainActivity在同一个任务栈之下。 MainActivity的taskAffinity是默认的包名,MainActivity2的taskAffinity现在变成了 my.affinity

由此可见,仅变更 taskAffinity 达不到变更任务栈结构的目的。

实验阶段3 再将 MainActivity的launchMode改成 singleTask或者SingleInstance

同样的,完成跳转之后,使用命令 adb shell dumpsys activity activities

十三、  梦开始的地方-startActivity

此时,才看到,两个Activity分别处在两个不同的activity中。

结论:taskAffinity单独使用不会改变任务栈结构,必须结合SingleTask/SingleInstance,才能达成单独启动一个任务栈的效果。

从手机上的Recent中也能看出来:

十三、  梦开始的地方-startActivity

实验阶段4 taskAffinity allowTaskReparenting

allowTaskReparenting是Android中的一个属性,用于控制活动(Activity)与任务(Task)之间的关联性和重组。当设置为true时,允许活动从一个任务移动到另一个任务,这在某些多任务场景下非常有用。

以下是一些具体的场景和用法:

  • 分离活动到新的任务:当用户从一个任务转到另一个任务时,可以使用allowTaskReparenting属性将活动从当前任务分离出来,并放置到一个新的任务中。这对于应用程序内的导航、多窗口模式或分屏模式下的应用场景非常有用。
  • 合并任务中的活动:在某些情况下,您可能需要将两个任务中的活动合并为一个。通过设置allowTaskReparenting属性为true,您可以将一个活动从一个任务移动到另一个任务,并在一个任务中汇总显示。
  • 多窗口模式下的任务管理:在支持多窗口模式的设备上,使用allowTaskReparenting属性可以更好地管理任务和活动。您可以在不同的窗口或任务中显示和移动活动,以提供更灵活的用户体验。

受限的Binder传递数据

activity之间通信,通常是使用的Intent。

十三、  梦开始的地方-startActivity

以下代码, 在运行时会崩溃。

十三、  梦开始的地方-startActivity

原因是,安卓系统中,对用Binder传递数据有大小限制。通常情况下是1M,但是国内厂商对安卓系统进行了定制,这个值就不一定是1M了。

解决办法

  • 将不必要的数据通过 transient 修饰。使之不会被序列化。 十三、  梦开始的地方-startActivity

  • 将对象转化成json字符串,减少部分体积 JVM保存一个class,往往会使用额外空间来保存类相关信息。将类中数据转化成json字符串可以减少数据大小。 比如,使用Gson.toJson

  • 使用本地数据持久化来实现数据共享 或者 引入EventBus这种组件 在转化成json仍然超出binder大小的情况下,我们就得自建一个参数仓库,让两个Activity到仓库中去读写,并控制参数对象的生命周期,尽量保持内存干净,不要有冗余对象。

容易忽略的多Process

我们在自定义Application中,会做很多启动动作。比如,网络模块初始化,读取本地配置文件,同步网络配置文件,推送SDK初始化,图片加载器参数初始化。

但是实际上,我们的app是支持多进程的。我们可以手动指定一个activity在子进程中运行。而当我们启动一个独立子进程的activity时,它也会创建出一个application来执行我们所有的启动动作。这显然不是我们要的结果,子进程不需要这么多初始化的东西。

十三、  梦开始的地方-startActivity

解决方案

  • 在onCreate中判断当前进程名称,只对主进程执行必要的初始化动作
  • 抽象出一个与Application同生命周期的类,根据不同的进程创建出相应的Application类,使得不同的进程执行不同的初始化过程。

注意:对于安卓应用中的主进程和子进程,在动态权限申请方面是相互独立的,它们不会自动共享权限。

当您在应用的主进程中申请一个动态权限时,该权限只适用于主进程的上下文和功能。如果您的应用中存在子进程(通过android:process属性指定),子进程默认是不具备与主进程相同的权限的。

如果您希望子进程也能够使用某个动态权限,您需要在子进程中重新申请该权限。这意味着,每个进程(主进程和子进程)的权限申请都是独立的。

需要注意的是,根据安卓系统的版本和权限模型的不同,某些权限可能会自动被分配给应用的所有进程,例如网络访问权限。但是,对于敏感权限(如相机、联系人等),每个进程仍然需要独立申请。

如果您希望在主进程和子进程之间共享权限信息,您可以考虑使用进程间通信(IPC)机制,将权限信息从主进程传递给子进程。例如,您可以使用Binder、AIDL或广播等方式进行通信,并在子进程中重新申请相应的权限。

综上所述,主进程和子进程在动态权限申请方面是相互独立的,它们不会自动共享权限。您需要在每个进程中单独申请所需的权限,并可以通过进程间通信机制将权限信息共享给子进程。

后台启动Activity失效的解决方案

从安卓10(sdk 29)开始,安卓系统对后台进程启动Activity做了一定的限制。 比如,我们的app有内部更新机制,我们把app退到后台,后台有个service在定期检索新版本,当发现新版本时,会开启一个线程去下载apk,并且在下载完毕之后,调用 installer 去安装apk,弹出安装界面,它实际上是利用我们的app进程去打开新的Activity。注意,此时我们的app是在后台的。

在安卓10(含)以后,这种搞法行不通了,现在的解决方案是:

利用通知的方式来代替启动Activity的操作。

具体做法是,在下载完成之后,通过NotificationManager发送一个通知到状态栏,这样,用户既不会被打扰到当前正在进行的操作,又可以感知到后台应用的更新情况。