项目疑难杂症记录(四):Activity被重新创建的原因分析

本文记录了一起Android项目中因configChanges导致Activity被重新创建的问题。当用户点击升级框的放弃按钮时,Activity被销毁并重新创建,引发崩溃。通过对系统源码的分析,发现是键盘类型变化导致的configuration更新,从而触发了Activity的重启。解决方案是在AndroidManifest.xml中针对键盘变化进行配置处理,以避免不必要的Activity重建。
部署运行你感兴趣的模型镜像

在项目中遇到一个奇怪的Bug,插上带有升级包固件的U盘,选择升级框中的放弃按钮,Activity被onDestroy,随后又重新onCreate,相应的图片和日志如下:
【一】 现象和日志

1、升级框
在这里插入图片描述
2、点击放弃按钮

在这里插入图片描述
3.日志如下:

     1528 D MainActivity: onCreate
	 1528  1528 D MainActivity: onResume
     1528  1528 D MainActivity: surfaceChanged
	 1528  1528 D MainActivity: onStop
	 1528  1528 D MainActivity: onDestroy
	  1528 D MainActivity: onCreate

从日志中看,PID为1528,没有变化,说明进程还是在的,只是Activity被重新创建了,由于Activity重新创建,底层so资源释放存在一点问题,所以如图二所见,崩溃了。

【二】、原因分析

有一些Android开发经验的朋友都知道,如果Activity被销毁,一般ActivityManager会打印出一些比如,force stop - destroy XXActivity之类的,很可惜我打印出来的日志没有。
没办法,先提出几个可能导致的原因,然后逐一排查。
(1)、和升级框会不会有必然的关系?为什么点击放弃之后,Activity就创建呢?升级框是不是干掉我们呢?
(2)、是不是系统底层的某种机制或者策略导致了Activity重新创建?

好,顺着这个思路,先把RkUpdateService.apk pull出来,反编译,查看其到底做了什么,为了保护原作者的创作,这里我就不贴出来截图了,结果是他们 do Nothing !!

第一条思路排除了,那现在顺着第二条思路查找。
在frameworks/base/core/java/android/app/ActivityThread.java中找到了 重启相关的代码。

  private void handleRelaunchActivity(ActivityClientRecord tmp) {
        // If we are getting ready to gc after going to the background, well
        // we are back active so skip it.
        //省略了部分代码
        // 销毁Activity
        handleDestroyActivity(r.token, false, configChanges, true);
        // 继续省略部分代码
        // 创建Activity
        handleLaunchActivity(r, currentIntent, "handleRelaunchActivity");
        
        if (!tmp.onlyLocalRequest) {
            try {
                ActivityManagerNative.getDefault().activityRelaunched(r.token);
                if (r.window != null) {
                    r.window.reportActivityRelaunched();
                }
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
    }

这段方法,和我们日志中展示的一样,那ActivityThread的行为必定是从AMS中触发的,走,去那边找找看,必定有所收获。

在 /framework/base/service/core/java/android/server/am/ActivityStack的中找到了relaunchActivityLocked 方法。

private void relaunchActivityLocked(
            ActivityRecord r, int changes, boolean andResume, boolean preserveWindow) {
        try {
            if (DEBUG_SWITCH || DEBUG_STATES) Slog.i(TAG_SWITCH,
                    "Moving to " + (andResume ? "RESUMED" : "PAUSED") + " Relaunching " + r
                    + " callers=" + Debug.getCallers(6));
            r.forceNewConfig = false;
            mStackSupervisor.activityRelaunchingLocked(r);
            // 让客户端进程重启Activity,这个是重点
            r.app.thread.scheduleRelaunchActivity(r.appToken, results, newIntents, changes,
                    !andResume, new Configuration(mService.mConfiguration),
                    new Configuration(r.task.mOverrideConfig), preserveWindow);
            // Note: don't need to call pauseIfSleepingLocked() here, because
            // the caller will only pass in 'andResume' if this activity is
            // currently resumed, which implies we aren't sleeping.
        } catch (RemoteException e) {
            if (DEBUG_SWITCH || DEBUG_STATES) Slog.i(TAG_SWITCH, "Relaunch failed", e);
        }
    }

这个方法又是被谁调用的,继续查找;

在这里插入图片描述
有两个地方调用,completePauseLocked方法和ensureActivityConfigurationLocked方法,第一个方法是完成了Pause,woc(语气词),这个不就是博文开始处点击 放弃按钮,之后又重新回到Activity之后,就重启了吗?
另外这两个主动调用的方法有没有什么一些联系呢?
先看 第一个主动调用的方法。

    private void completePauseLocked(boolean resumeNext, ActivityRecord resuming) {
        ActivityRecord prev = mPausingActivity;
        if (prev != null) {
               // 此处省略大部分代码
               //这里是关键,如果这里为true,调用重启
                if (prev.deferRelaunchUntilPaused) {
                    relaunchActivityLocked(prev, prev.configChangeFlags, false,
                            prev.preserveWindowOnDeferredRelaunch);
                } else if (wasStopping) {

                }

从这个方法,如果deferRelaunchUntilPaused为true,就调用重启方法,这个标志位的名称就可以看出 等到pause状态完成重启。

这个标志位在哪里赋值的呢?我继续serach project.
找到了,就在刚才第二个调用的方法中 ensureActivityConfigurationLocked

  boolean ensureActivityConfigurationLocked(
            ActivityRecord r, int globalChanges, boolean preserveWindow) {
           
            //此处省略了大量的代码
        if (mConfigWillChange) {
        if ((changes&(~r.info.getRealConfigChanged())) != 0 || r.forceNewConfig)      {             
 
                destroyActivityLocked(r, true, "config");
            } else if (r.state == ActivityState.PAUSING) {
               //如果Activity此时为Pause状态,先设置一个标志位
                r.deferRelaunchUntilPaused = true;
                r.preserveWindowOnDeferredRelaunch = preserveWindow;
                return true;
            } else if (r.state == ActivityState.RESUMED) {
                 //如果Activity的状态是resume,也就是页面没有被遮挡,
                 那么就立即重启。
                relaunchActivityLocked(r, r.configChangeFlags, true, preserveWindow);
            }

到目前为止,我理一下分析过程

1、插上了带有升级包的U盘之后,系统检测到了配置变化,调用了ensureActivityConfigurationLocked方法,由于此时Activity是Pause状态,没有立即重启,而是设置deferRelaunchUntilPaused 为true 。
2、我点击放弃按钮之后,Activity回到Pause——>Resume状态过渡,此时走到completePauseLocked方法,因为刚刚deferRelaunchUntilPaused 为true,此时是重启的时候了,通过进程间AIDL,调用ActivityThread方法,完成重启。

此时基本可以确认是由于configuration变化导致了AMS启动重启,我突然一想,日志中到底有没有蛛丝马迹呢?回过头再去找找,果然被我找到了。

ActivityManager: Config changes=30 {1.0 ?mcc?mnc [zh] ldltr sw811dp w1442dp h811dp 213dpi xlrg long land -touch qwerty/v/v dpad/v s.4}

打印出的是new Configuration的信息,我经过和old configuration对比,发现是由于键盘类型发生变化导致的,由于现有的系统和U盘中的固件的键盘configuration不一致导致这个原因。

好了,知道原因了,解决办法就比较简单了。
在 AndroidManifest.xml 中配置一下

android:configChanges="keyboard|keyboardHidden"

搞定。

您可能感兴趣的与本文相关的镜像

Qwen3-8B

Qwen3-8B

文本生成
Qwen3

Qwen3 是 Qwen 系列中的最新一代大型语言模型,提供了一整套密集型和专家混合(MoE)模型。基于广泛的训练,Qwen3 在推理、指令执行、代理能力和多语言支持方面取得了突破性进展

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值