【问题标题】:Is there a way to handle an Android app getting killed in the Paused state to make the system restart the activity automatically?有没有办法处理在暂停状态下被杀死的 Android 应用程序以使系统自动重新启动活动?
【发布时间】:2019-12-17 05:14:37
【问题描述】:

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

如Android Activity Lifecycle docs 文档和Cordova's Lifecycle Docs for the Android Platform 中所述,当摄像头进入前台时,应用的主要活动在后台进入paused 或stopped 状态。

虽然主要活动处于这两种状态中的任何一种,但如果它需要释放内存,它很容易被系统杀死,这种情况在这个应用程序中经常发生,足以让用户抱怨。

在测试中,我发现有时应用程序会在相机关闭时自行重启,而其他时候活动似乎已经消失在相机后面,因此当用户关闭它时,应用程序似乎已经崩溃。

阅读上述文档后,我在 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 状态之前将其杀死,并且没有保存重新启动它所需的任何引用。然后,当我们尝试返回该活动时,系统不知道要加载什么,所以就退出应用程序。

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

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

如果活动应该在没有退出应用程序的情况下返回,我如何找出导致崩溃的原因?我的异常处理程序永远不会被调用,我已经遍历了logcat 中的所有行,但看不到任何可以为我提供任何关于问题所在的线索的内容。

我已经使用分析器运行了该应用程序,虽然内存使用率高达 350mb 左右,但似乎没有任何泄漏会随着拍摄的照片越来越多而将其推得越来越高。

此外,对于需要拍摄多少张照片来重现问题,似乎没有任何规律的模式,有时它会一遍又一遍地重新启动,有时它会一遍又一遍地退出,而其他时候我得到了混合物。

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

编辑

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

所以用户点击应用中的相机按钮,主活动进入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

在这之后有一大堆相机设置日志,但其中主要活动死亡,大概是被系统杀死以释放资源......

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 会尝试将重新创建的活动移到最前面...

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 抱怨任务有多个活动,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

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

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

所以,问题出在某个地方。系统似乎在尝试重新启动我的活动,而相机仍然可见,这搞砸了,但我仍然不确定发生了什么或我能做些什么。

有人有什么想法吗?

【问题讨论】:

  • onSaveInstanceState 的目的是让您保存足够的状态,以便在重新启动并调用 onRestoreInstanceState 时可以重新初始化到该状态。如果你做得好,这不太可能成为问题。
  • 另外,这不是崩溃,这是您需要考虑的 Android 应用程序的预期行为。它甚至不是关于节省您的应用程序使用的内存,而是将其回收以供系统和其他应用程序使用,因此任何分析和保存都无济于事(尽管 350 mb 非常高,如果这是 Java 堆,它会在大多数手机上崩溃)。
  • 所以预期的行为包括一个活动被杀死但在用户导航到它时不会自动重新启动?问题似乎是在某些情况下我不会重新启动,无论保存状态如何
  • 预计当前未显示的任何活动或应用程序可能会因资源而被杀死并稍后重新初始化。这是 Android 的一个框架特性。如果用户通过后退按钮、最近的活动页面或其他机制导航回您的应用程序,它将使用 saveInstanceState/restoreInstanceState 机制重新启动。

标签: android cordova ionic-framework kill kill-process


【解决方案1】:

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

在我的应用程序中,我使用了一个名为 konva.js 的绘图库,它允许用户用手指在 HTML 画布上创建绘图和图表。

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

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

我注意到我没有明确地释放保存对层等的引用的属性,因为我只是希望垃圾收集能够对其进行排序。

因此,对于每个属性,我确保在调用 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();
}

我不知道究竟是哪个项目导致了问题,但是一旦我检查并确保一开始创建的所有内容最终都被删除,我的应用程序就开始正常工作了。

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

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

【讨论】:

  • 非常感谢您提供的详细信息。很亲切:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-05-18
  • 1970-01-01
  • 2016-09-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多