繁体   English   中英

在应用程序onCreate之前调用服务onCreate

[英]Service onCreate called before Application onCreate

在最新版本的App中,开发人员控制台中报告了一些崩溃,我对此感到疯狂。 它们显示为java.lang.IllegalStateException,似乎在Service.onCreate之前未调用Application.onCreate。

它仅在Android 8设备上发生,大约占用户的0.3%。 我无法在设备上复制它。

我将更好地解释会发生什么。 该应用程序通过以下方式扩展应用程序:

public class MySpecificApp  extends MyBaseApp
{
    static
    {
        AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
    }

    public void onCreate()
    {
        super.onCreate();

        ...  // Specific initializations
    }

    ...
}

public class MyBaseApp  extends Application
{

    private static MyBaseApp smApplication;

    public void onCreate()
    {
        super.onCreate();

        // Fabric (Crashlitics) initialization. It SHOULD NOT BE needed, just to be sure (tried also without this line)
        Fabric.with(this, new Crashlytics());

        MyBaseApp.smApplication = this;             // smApplication is initialized here and NEVER modified

        ... // Base initializations     
    }

    public static MyBaseApp getApp() {
        return MyBaseApp.smApplication;
    }

    ...
}

在MyService.onCreate方法中,对MyBaseApp.smApplication的检查显示,从未调用过MyBaseClass.onCreate。

public class MyService extends Service
{

    public MyService()
    {
    }

    @Override
    public void onCreate()
    {
        try
        {
            if( MySpecificApp.getApp() == null )
                Crashlytics.log("MyService.onCreate: probably Application.onCreate not yet called");        <=== Here is raised the Exception
            // The line over is the line 617


            ... // Service initializations

        }
        catch( Exception e )
        {
            e.printStackTrace();

            throw e;
        }
    }

    ...
}

实际上,Crashlytics崩溃是因为它尚未初始化,而是因为应用程序尚未初始化。 Crashlytics不是线索。 代码是这样的,因为我有几个IllegalStateException怪异的错误,并且我认为缺少应用程序初始化:我正在尝试对其进行调查。

此处,堆栈是从开发人员控制台记录的(崩溃不会到达Crashlytics)

java.lang.RuntimeException: 
  at android.app.ActivityThread.handleCreateService (ActivityThread.java:3554)
  at android.app.ActivityThread.-wrap4 (Unknown Source)
  at android.app.ActivityThread$H.handleMessage (ActivityThread.java:1786)
  at android.os.Handler.dispatchMessage (Handler.java:105)
  at android.os.Looper.loop (Looper.java:164)
  at android.app.ActivityThread.main (ActivityThread.java:6944)
  at java.lang.reflect.Method.invoke (Method.java)
  at com.android.internal.os.Zygote$MethodAndArgsCaller.run (Zygote.java:327)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1374)
Caused by: java.lang.IllegalStateException: 
  at io.fabric.sdk.android.Fabric.singleton (Fabric.java:301)
  at io.fabric.sdk.android.Fabric.getKit (Fabric.java:551)
  at com.crashlytics.android.Crashlytics.getInstance (Crashlytics.java:191)
  at com.crashlytics.android.Crashlytics.checkInitialized (Crashlytics.java:390)
  at com.crashlytics.android.Crashlytics.log (Crashlytics.java:221)
  at com.xxx.MyService.onCreate (MyService.java:617)                                <==== Here is the line that is reached only if Application.onCreate is not called
  at android.app.ActivityThread.handleCreateService (ActivityThread.java:3544)
  at android.app.ActivityThread.-wrap4 (Unknown Source)
  at android.app.ActivityThread$H.handleMessage (ActivityThread.java:1786)
  at android.os.Handler.dispatchMessage (Handler.java:105)
  at android.os.Looper.loop (Looper.java:164)
  at android.app.ActivityThread.main (ActivityThread.java:6944)
  at java.lang.reflect.Method.invoke (Method.java)
  at com.android.internal.os.Zygote$MethodAndArgsCaller.run (Zygote.java:327)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1374)

这里是清单的摘录:

<application
    android:name=".MySpecificApp"
    android:allowBackup="true"
    android:fullBackupContent="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:theme="@style/AppTheme"
    android:largeHeap="true">

    ...

    <service
        android:name=".MyService"
        android:enabled="true"
        android:exported="false"
        android:stopWithTask="false">
    </service>

    ...
</application>

有谁知道会发生什么以及如何解决该错误?

您的服务如下:

android:stopWithTask="false"

如果设置为true,则当用户删除根于应用程序拥有的活动的任务时,该服务将自动停止。 默认为false。

我建议将其设置为true,因为即使关闭了应用程序拥有的任务,您也不想运行该服务。 还要检查您的服务是否为STICKY

我无法告诉您为什么在调用ApplicationonCreate之前启动您的服务,尤其是因为实现服务的方式缺少很多代码。 您何时/如何开始? 您的目标API是什么? 是否有某些设备/制造商专门造成这种情况? 不过,这并不是我在Android上看到的最奇怪的事情。 如果它仅是Android 8,并且在其他版本中出现的次数为零,则很可能也是OS中的错误。

也许我们可以采取一些措施来解决它:

方法A

首先, Application不是在您的应用程序启动时首先创建的(有点反常)。 据我所知, ContentProviders是您的应用程序启动时首先创建的组件(这就是Firebase之类的某些服务会使用它们来进行设置的原因,例如崩溃报告)。

创建Application时, onCreate并不是第一个被调用的东西(更加直观)。 创建后立即调用init函数/构造函数。 不需要Context任何工作都可以在默认构造函数中完成。 接下来要调用的最好的方法是attachBaseContext函数。 这是最早可以使用Context运行初始化的方法。 此时,将创建ContentProviders (构造函数+ onCreate )。 直到现在, ApplicationonCreate才被调用。

检查这个小样本:

class MyApp : Application() {

    init {
        Log.i("MyApp", "init")
    }

    override fun attachBaseContext(base: Context?) {
        super.attachBaseContext(base)
        Log.i("MyApp", "attachBaseContext")
    }

    override fun onCreate() {
        super.onCreate()
        Log.i("MyApp", "onCreate")
    }
}
class MyContentProvider : ContentProvider() {

    init {
        Log.i("MyContentProvider", "init")
    }

    override fun onCreate(): Boolean {
        Log.i("MyContentProvider", "onCreate")
        return true
    }

    ...

}

如果启动此应用程序,则会得到以下输出:

I/MyApp: init
I/MyApp: attachBaseContext
I/MyContentProvider: init
I/MyContentProvider: onCreate
I/MyApp: onCreate

您可以使用此事实将关键的初始化移动到例如ContentProviderattachBaseContext方法,并尝试使早期创建的Service实例运行。

方法B

另一个想法是在出现错误的情况下手动延迟Service初始化。 由于只有0.3%的情况会发生这种情况,因此我认为应稍加延迟以防止崩溃。 您可以执行以下操作(对不起,我的Kotlin):

fun onCreate() {
    super.onCreate()
    onCreate0()
}

private fun onCreate0() {
    if (MySpecificApp.getApp() == null) {
        Log.w("MyService", "Delaying service initialization due to race condition")
        Handler(Looper.getMainLooper()).postDelayed(this::onCreate0, 100)
        return
    } 

    Log.i("MyService", "Application init verified, initializing service now")
    // Do your stuff here. Application is initialized
}


因此,基本上,您会进行检查,如果应用尚未准备就绪,请在100毫秒后重试。 这并不完美,但是比崩溃更好。 我建议仍以此行为收集数据给A。查看需要进行多少次循环,以及B.这种循环发生的频率以及在哪些设备上进行。 通过Firebase Remote Config之类的方法进行控制或控制通过它的最大尝试次数也是一个好主意。 但是,获取云值可能会失败,因为您的应用处于奇怪的初始化状态。

通过这样做,您还可以收集有关此行为的更多见解。 应用程序是否已初始化(这就是为什么我限制循环数的原因)?

方法B v2为了解决您从注释到答案的疑虑,这是处理意图的更新版本。

在您的Service执行相同的检查,但是缓存您无法处理的Intent 该服务将自行停止

var isInitialized = false

// static in Java
companion object {
    private val pendingIntents = mutableListOf<Intent>()

    fun hasPendingIntents() = pendingIntents.size > 0
}

fun onCreate() {
    super.onCreate()
    if (MySpecificApp.getApp() == null) {
        Log.w("MyService", "Application not ready")
        stopSelf()
        return
    } 

    Log.i("MyService", "Application init verified, initializing service now")
    isInitialized = true
    // Do your stuff here. Application is initialized
}

fun onStartCommand(intent: Intent, flags: Int, startId: Int) {
    pendingIntents.add(intent)

    if (!isInitialized) {
        return   
    }

    pendingIntents.forEach {
        // Handle intents which could not be satisfied before including the latest
    }

    pendingIntents.clear()

    return super.onStartCommand(intent, flags, startId)
}

在您的Application检查服务是否遇到问题,如果是,请手动重新启动:


fun onCreate() {
    super.onCreate()
    // init app
    if (MyService.hasPendingIntents()) {
       startService(Intent(this, MyService::class).putExtra("RECOVER_AFTER_FAILURE", true)
    }
}

方法C

这不是解决方案,但可能使您更接近“干净”的解决方案:您可以尝试将磁盘上的最后100条日志(应用程序创建,活动状态等)缓存起来,并在服务启动时将这100条日志附加到崩溃报告。 这可能会提供有价值的见解,例如,该应用程序是最近使用还是长时间闲置/关闭。 也许您找到了一些可以引导您走向正确方向的东西。 了解有关此行为发生情况的更多信息将很有趣。

从Android 8开始,在后台运行超过几分钟(通常为1分钟)后,所有不粘滞服务都会被销毁。 https://developer.android.com/about/versions/oreo/background#services中所述 ,一旦用户再次回到前台,它将被重新创建(将调用服务的onCreate)

当应用程序处于前台时,它可以自由创建和运行前台和后台服务。 当应用进入后台时,它会有几分钟的窗口,仍允许其创建和使用服务。 在该窗口结束时,该应用程序被认为是空闲的。 此时,系统停止应用程序的后台服务,就像该应用程序已调用服务的Service.stopSelf()方法一样。

我不记得是因为服务中断还是整个应用程序停止运行,无论是在后台运行1分钟以上并且服务被销毁之后,您的应用程序可能已经因为其他任何应用程序的请求而死亡更多的内存RAM。 而且,尽管您的应用被杀死了,但仍保留在最近的应用列表中。

当用户回到您的应用程序时,将调用应用程序类的onCreate(),并且还将调用您服务的onCreate来重新创建它。 我不能说将首先触发两个onCreate()方法。

如果您不知道如何在后台杀死应用,请使用: 在此处输入图片说明

无论如何,如果这不是您的问题,则可以使用getApplicationContext()从Service访问您的应用程序并将其转换为MyBaseApp。 无需访问MyBaseApp上的静态方法。 这种方法不会失败,因为服务扩展了上下文,因此您可以始终将其强制转换为应用程序

public class MyService extends Service {
    private MyBaseApp application;
    @Override
    public void onCreate() {
        application = ((MyBaseApp) getApplicationContext())
    }
    ...
}

后台执行限制

Android 8.0行为更改

作为Android 8.0 (API level 26)引入的旨在延长电池寿命的更改之一,当您的应用进入缓存状态且没有活动组件时,系统会释放该应用持有的所有唤醒锁。

此外,为了提高设备性能,系统会限制未在前台运行的应用程序的某些行为。 特别:

现在,在后台运行的应用程序在访问后台服务的自由度方面受到限制。 应用程序无法使用其清单来注册大多数隐式广播(即,并非专门针对应用程序的广播)。 默认情况下,这些限制仅适用于面向O的应用程序。但是,即使该应用程序未针对O,用户也可以从“设置”屏幕为任何应用程序启用这些限制。

Android 8.0

(API level 26)还包括对特定方法的以下更改:

如果针对Android 8.0的应用尝试在不允许创建后台服务的情况下尝试使用该方法,则startService()方法现在将引发IllegalStateException 新的Context.startForegroundService()方法启动前台服务。 该系统允许应用程序在后台运行时调用Context.startForegroundService() 但是,应用程序必须在创建服务后五秒钟内调用该服务的startForeground()方法。 有关更多信息,请参见后台执行限制。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM