有没有办法处理 Android 应用程序在暂停状态下被杀死,使系统自动重启 activity?

Is there a way to handle an Android app getting killed in the Paused state to make the system restart the activity automatically?

我有一个 Ionic/Cordova 应用程序,它大量使用相机插件,并且在 运行 上 Android 时出现问题。

Android Activity Lifecycle docs docs and also Cordova's Lifecycle Docs for the Android Platform 中所述,当相机进入前台时,应用程序的主要 activity 会进入 pausedstopped 状态背景。

当主要 activity 处于这两种状态时,如果它需要释放内存,它很容易被系统杀死,这种情况在这个应用程序中经常发生,足以让用户抱怨。

在测试中,我看到有时应用程序会在相机关闭时自行重启,而其他时候 activity 似乎已经消失在相机后面,所以当用户关闭它时,应用程序会出现崩溃了。

阅读上述文档后,我在 Cordova 生成的 MainActivty android 代码中添加了一些日志记录,这样我就可以在使用相机时观察状态变化,并且还添加了一个未捕获的异常处理程序onCreate:

public class MainActivity extends CordovaActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        Log.e("com.xxx.test", "onCreate");
        super.onCreate(savedInstanceState);

        // enable Cordova apps to be started in the background
        Bundle extras = getIntent().getExtras();

        if (extras != null && extras.getBoolean("cdvStartInBackground", false)) {
            moveTaskToBack(true);
        }

        // Set by <content src="index.html" /> in config.xml
        loadUrl(launchUrl);


        Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread paramThread, Throwable paramThrowable) {
                Log.e("com.xxx.test", "onCreate, UNCAUGHT EXCEPTION!!!");
                System.exit(2);
            }
        });
    }

    @Override
    public void onStart() {
        Log.e("com.xxx.test", "onStart");
        super.onStart();

    }

    @Override
    public void onResume() {
        Log.e("com.xxx.test", "onResume");
        super.onResume();

    }

    @Override
    public void onPause() {
        Log.e("com.xxx.test", "onPause");
        super.onPause();
    }

    @Override
    public void onStop() {
        Log.e("com.xxx.test", "onStop");
        super.onStop();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.e("com.xxx.test", "onDestroy");

    }

    @Override
    public void onSaveInstanceState(Bundle savedInstanceState) {
        Log.e("com.xxx.test", "onSaveInstanceState");
        super.onSaveInstanceState(savedInstanceState);
    }

    @Override
    public void onRestoreInstanceState(Bundle savedInstanceState) {
        Log.e("com.xxx.test", "onRestoreInstanceState");
        super.onRestoreInstanceState(savedInstanceState);
    }
}

我 运行 应用程序并过滤 logcat 包含 "com.xxx.test" 的行,结果如下:

正常相机操作

//camera button tapped
onPause
onSaveInstanceState
onStop

//user takes photo and dismisses camera
onResume

应用重启

到目前为止一切顺利。现在,如果应用程序在相机关闭时自行重启,我会得到:

//camera button tapped
onPause
onSaveInstanceState
onStop

//user takes photo and dismisses camera, app restarts itself here
onCreate
onStart
onRestoreInstanceState
onResume

这是我希望在应用程序在后台终止 activity 时看到的内容 - 当用户导航回它时,系统会重新创建 activity 并加载任何已保存的状态等.这很好,我可以处理中断。

崩溃

但是,有时日志会这样做:

//camera button tapped
onPause

//sometimes it stops here, sometimes I also get
onSaveInstanceState

//boom! - the app seems to have crashed and the user is taken back to the home screen when dismissing the camera

所以,我怀疑发生的事情是系统在进入 stopped 状态之前杀死了 activity,并且没有保存重新启动它所需的任何引用。然后,当我们尝试返回那个 activity 时,系统不知道要加载什么,所以只退出应用程序。

虽然我不是 100% 肯定是这样。也许应用程序在其他地方崩溃了,我只是不知道。

所以我的问题是,如果系统在 Android 上杀死处于 paused 状态的 activity,我们是否能够按需再次导航回它,或者这是应用程序刚刚退出并且用户必须重新启动它的标准行为?如果是这样,是否有任何解决方法?

如果 activity 应该在不退出应用程序的情况下返回,我如何找出导致崩溃的原因?我的异常处理程序从未被调用过,我已经遍历了 logcat 中的所有行,但看不到任何可以给我任何关于问题所在的线索。

我已经 运行 带有分析器的应用程序,虽然内存使用率很高,约为 350mb,但似乎没有任何泄漏会随着拍摄的照片越来越多。

此外,关于需要拍摄多少张照片才能重现问题,似乎没有任何规律可循,有时它会一遍又一遍地重新启动,有时它会一遍又一遍地退出,有时又会退出我得到一个混合物。

非常感谢任何关于如何阻止这种崩溃行为的想法。

编辑

我一直在浏览日志,试图拼凑出正在发生的事情的时间表:

因此用户点击应用中的相机按钮,主 activity 进入 paused 状态并调用 onSaveInstanceState()...

16:23:10.244 505-505/com.xxx.test I/chromium: [INFO:CONSOLE(24978)] "photo button tapped", source: file:///android_asset/www/build/main.js (24978)
16:23:10.315 505-505/com.xxx.test E/com.xxx.test: onPause
16:23:10.315 505-505/com.xxx.test D/CordovaActivity: Paused the activity.
16:23:10.322 505-505/com.xxx.test E/com.xxx.test: onSaveInstanceState

在此之后有一大堆相机设置日志,但在所有这些日志中,主要的 activity 死了,大概是被系统杀死以释放资源...

16:23:11.415 3696-6128/? I/WindowManager: WIN DEATH: Window{2ae98e1d0 u0 com.xxx.test/com.xxx.test.MainActivity}
16:23:11.415 3696-4312/? I/ActivityManager: Process com.xxx.test (pid 505) has died(377,151)

尽管调用了onSaveInstanceState(),但系统声称没有保存状态...

16:23:11.419 3696-4312/? W/ActivityManager: Force removing ActivityRecord{c76b13bd0 u0 com.xxx.test/.MainActivity t2117}: app died, no saved state

然后大约半秒后,系统在相机仍然可见的情况下为我的包名称启动一个新进程...

16:23:12.047 3696-9816/? D/MountService: getExternalStorageMountMode : 3
    getExternalStorageMountMode : 3
    getExternalStorageMountMode : final mountMode=3, uid : 10298, packageName : com.xxx.test
16:23:12.061 3696-9816/? I/ActivityManager: Start proc 6426:com.xxx.test/u0a298 for content provider com.xxx.test/android.support.v4.content.FileProvider

尽管进程 6426...

没有记录太多其他内容
16:23:12.062 6426-6426/? E/Zygote: v2
16:23:12.062 6426-6426/? I/libpersona: KNOX_SDCARD checking this for 10298
16:23:12.063 6426-6426/? I/libpersona: KNOX_SDCARD not a persona
16:23:12.063 6426-6426/? E/Zygote: accessInfo : 0
16:23:12.064 6426-6426/? W/SELinux: SELinux selinux_android_compute_policy_index : Policy Index[2],  Con:u:r:zygote:s0 RAM:SEPF_SECMOBILE_7.0_0007, [-1 -1 -1 -1 0 1]
16:23:12.064 6426-6426/? I/SELinux: SELinux: seapp_context_lookup: seinfo=untrusted, level=s0:c512,c768, pkgname=com.xxx.test
16:23:12.069 6426-6426/? I/art: Late-enabling -Xcheck:jni
16:23:12.098 6426-6426/? D/TimaKeyStoreProvider: TimaKeyStore is not enabled: cannot add TimaSignature Service and generateKeyPair Service

就是这样。不过很快,ActivityManager 试图将重新创建的 activity 移到前面...

16:23:12.212 3696-5435/? D/ActivityManager: moveToFront() : reason=startedActivity setFocusedActivity isAttached=true TaskRecord{d952617d0 #2117 A=com.xxx.test U=0 StackId=1 sz=2}
16:23:12.213 3696-5435/? D/InputDispatcher: Focused application set to: xxxx

然后相机暂停,即使它仍然对用户可见...

16:23:12.239 30891-30891/? V/Camera6: onPause

ActivityManager又提到了我的包名...

16:23:12.558 3696-6838/? D/ActivityManager: resumeTopActivityInnerLocked() : #1 prevTask=TaskRecord{d952617d0 #2117 A=com.xxx.test U=0 StackId=1 sz=2} next=ActivityRecord{bc76899d0 u0 com.sec.android.app.camera/.AttachActivity t2117} mFocusedStack=ActivityStack{33a525ad0 stackId=1, 2 tasks}

不久之后,MultiScreenManagerService 抱怨任务有多个 activity 并且 ActivityManager 开始做更多的事情...

16:23:14.529 3696-13852/? W/MultiScreenManagerService: moveTaskBackToDisplayIfNeeded(): The task has more than one activity
16:23:14.530 3696-13852/? D/ActivityManager: moveToFront() : reason=finishActivity adjustFocus setFocusedActivity isAttached=true TaskRecord{d952617d0 #2117 A=com.xxx.test U=0 StackId=1 sz=2}
16:23:14.535 3696-13852/? D/InputDispatcher: Focused application set to: xxxx
16:23:14.536 3696-13852/? D/ActivityTrigger: ActivityTrigger activityPauseTrigger
16:23:14.544 30891-30891/? V/AttachActivity: onPause
16:23:14.545 3696-5485/? D/ActivityManager: setAppIconInfo(), x : 0, y : 0, width : 0, height : 0, isHomeItem : false
    resumeTopActivityInnerLocked() : #1 prevTask=TaskRecord{d952617d0 #2117 A=com.xxx.test U=0 StackId=1 sz=2} next=ActivityRecord{1da4823d0 u0 com.sec.android.app.camera/.Camera t2117} mFocusedStack=ActivityStack{33a525ad0 stackId=1, 2 tasks}

最终,相机被用户关闭了...

16:23:14.634 30891-30891/? V/Camera6: finish

此时我们收到了更多来自 MultiScreenManagerService 的投诉,现在 ActivityManager 想将 com.sec.android.app.launcher 移到前面,我猜这是主屏幕.. .

16:23:14.637 3696-9814/? W/MultiScreenManagerService: moveTaskBackToDisplayIfNeeded(): root is not base activity
16:23:14.642 3696-9814/? D/ActivityManager: moveToFront() : reason=finishActivity adjustFocus setFocusedActivity isAttached=true TaskRecord{3965df5d0 #2072 A=com.sec.android.app.launcher U=0 StackId=0 sz=1}
16:23:14.642 3696-9814/? W/MultiScreenManagerService: moveTaskBackToDisplayIfNeeded(): root activity or app is null

ActivityManager 中还有另一条日志谈到相机的重复完成请求...

16:23:14.663 3696-6002/? W/ActivityManager: Duplicate finish request for ActivityRecord{1da4823d0 u0 com.sec.android.app.camera/.Camera t2117 f}

此时我假设正在显示主屏幕,但 ActivityManager 与我的包裹一起使用...

16:23:14.715 3696-5436/? D/ActivityManager: resumeTopActivityInnerLocked() : #0 prevTask=TaskRecord{d952617d0 #2117 A=com.xxx.test U=0 StackId=1 sz=2} next=ActivityRecord{eed8739d0 u0 com.sec.android.app.launcher/.activities.LauncherActivity t2072} mFocusedStack=ActivityStack{514789d0 stackId=0, 2 tasks}
    applyOptionsLocked(), pendingOptions : null

但无济于事,之后唯一对我的 activity 的引用是偶尔的日志组,例如...

16:23:14.844 3696-3707/? D/PackageManager: getComponentMetadataForIconTray : com.xxx.test.MainActivity does not exist in mServices
    getComponentMetadataForIconTray : com.xxx.test.MainActivity does not exist in mProviders
    getComponentMetadataForIconTray : com.xxx.test.MainActivity does not exist in mReceivers
16:23:14.846 4439-4439/? I/ApplicationPackageManager: load=com.xxx.test, bg=192-192, dr=192-192, forDefault=true
    reset dr=192,192, bg=192,192

所以,问题就出在这里。系统似乎尝试重新启动我的 activity,而相机仍然可见,这把事情搞砸了,但我仍然不确定发生了什么或我能做些什么。

有人有什么想法吗?

对于@mushishi78 和其他可能遇到此问题的人,我最终确实找出了我的问题所在。

在我的应用程序中,我使用了一个名为 konva.js 的绘图库,它允许用户用手指在 HTML canvas.

上创建绘图和图表

我使用这个库制作的组件具有绘图各个方面的属性,即在初始化时实例化的主舞台、一些层、节点等。

经过大量测试后,我意识到相机的问题仅在用户与此图表组件交互后才会发生,因此开始查看那里。

我注意到我没有显式释放保存对层等的引用的属性,因为我只是希望垃圾回收将其整理出来。

因此,对于每个 属性,我确保在调用 ionViewWillLeave() 时删除了所有可能存在的引用:

@Component({
  templateUrl: 'chamber-sketch-page.html'
})
    
export class ChamberSketchPage {

  // properties declared here

  @ViewChild('konvaStage') stageElementRef: ElementRef;

    stage: any;
    backgroundLayer: any;

  ...
}

...

ngAfterViewInit() {

  // properties instantiated here and listeners set up

  this.stage = new Konva.Stage({
    container: "konvaStage",
    width: window.innerWidth,
    height: window.innerWidth
  });

    this.backgroundLayer = new Konva.Layer({
        name: "background"
    });

  this.platform.ready().then(() => {
        window.addEventListener('native.keyboardshow', this.keyboardShowHandler);
        window.addEventListener('native.keyboardhide', this.keyboardHideHandler);
    });
}

...

ionViewWillLeave() : void {

  // clean up everything possible here - listeners, properties etc

  window.removeEventListener('native.keyboardshow', this.keyboardShowHandler);
  window.removeEventListener('native.keyboardhide', this.keyboardHideHandler);

  this.stage.destroy()
  this.stage = null

  this.backgroundLayer.destroy()
  this.backgroundLayer = null

  this.stageElementRef.nativeElement.remove();
}

我不知道到底是哪个项目导致了问题,但是在我完成检查并确保最后删除了开始时创建的所有内容后,我的应用程序开始正常运行。

我的猜测是,在组件被关闭后,周围留下了一些不应该存在的引用,这可能在系统某处造成了一些奇怪的记忆巫术,可能是通过其中一个插件,这就是导致崩溃。

我们永远不会知道,但我对遇到此问题的任何人的建议是确保在不再需要该组件后清理您实例化的所有对象。