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

标签 android android-service

在我最新版本的应用程序中,开发者控制台中报告的一些崩溃让我感到抓狂。
它们显示为 java.lang.IllegalStateException 并且似乎 Application.onCreate 在 Service.onCreate 之前没有被调用

它发生在大约 0.3% 的用户中,并且仅发生在 Android 8 设备上。
我未能在我的设备上重现它。

我将更好地解释发生了什么。
该应用程序以这种方式扩展应用程序:

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崩溃是因为它没有被初始化,而是因为Application没有被初始化。
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)

这是 list 的摘录:
<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>

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

最佳答案

我无法告诉您为什么您的服务在 Application 之前启动。的onCreate被调用,特别是因为您实现服务的方式缺少很多代码。你什么时候/如何开始?你的目标 API 是什么?是否有某个设备/制造商专门导致此问题?不过,这并不是我在 Android 上见过的最奇怪的事情。如果它只是 Android 8 并且在其他版本上出现为零,它也很可能是操作系统中的一个错误。

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

方法 A

首先,Application不是在您的应用程序启动时创建的第一件事(有点反直觉)。据我所知,ContentProviders是您的应用启动时创建的第一个组件(这就是 Firebase 等某些服务使用它们进行设置的原因,例如崩溃报告)。
onCreate也不是当您 Application 时被调用的第一件事被创建(有点反直觉)。创建后立即调用 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

您可以使用这个事实将您的关键初始化移动到例如一个 ContentProvider或至attachBaseContext方法并尝试运行早期创建的 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
为了解决您对此答案的评论的担忧,这里是处理 Intent 的更新版本。

在您的 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 个日志(应用程序创建、 Activity 状态等),并在服务启动时将这 100 个日志附加到崩溃报告。这可能会提供有值(value)的见解,例如该应用程序是最近使用过还是长时间闲置/关闭。也许你会发现一些东西引导你走向正确的方向。了解更多关于这种行为发生的情况会很有趣。

关于android - 在应用程序 onCreate 之前调用服务 onCreate,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56620451/

相关文章:

android - 如何在 Android 中检索 JSON 值?

android - IntentService 中的 registerReceiver 未命中 BroadcastReceiver

安卓服务 : START_STICKY does not work on Kitkat

应用从最近应用列表中刷出后,Android 服务崩溃

java - 如何将 Activity 的弱接口(interface)类型传递给单例类

android - x 和 y 之间的一组随机数,需要是唯一的,使用 Kotlin

使用 Parse 和 PubNub 的 Android 聊天应用指南

android - 确定移动设备的最大浏览器高度(本地址栏未显示时)

android - 使用 canvas.drawText() 时如何使用 ios 表情符号替换 Android 表情符号

安卓棉花糖 : Calling a method in the system process without a qualified user: