简体   繁体   English

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

[英]Service onCreate called before Application onCreate

I'm getting crazy with some crashes reported in the Developer Console, in my most recent version of the App. 在最新版本的App中,开发人员控制台中报告了一些崩溃,我对此感到疯狂。 They appear as java.lang.IllegalStateException and it seems that Application.onCreate is not called before Service.onCreate 它们显示为java.lang.IllegalStateException,似乎在Service.onCreate之前未调用Application.onCreate。

It happens for about 0.3% percentage of the users and it happens ONLY on Android 8 devices. 它仅在Android 8设备上发生,大约占用户的0.3%。 I failed to reproduce it on my devices. 我无法在设备上复制它。

I'm going to explain better what happens. 我将更好地解释会发生什么。 The App extends Application in this way: 该应用程序通过以下方式扩展应用程序:

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;
    }

    ...
}

In the MyService.onCreate method a check to the MyBaseApp.smApplication reveals that the MyBaseClass.onCreate has never been called. 在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;
        }
    }

    ...
}

In fact Crashlytics crashes because it has not been initialized, but it's because the Application has not been initialized. 实际上,Crashlytics崩溃是因为它尚未初始化,而是因为应用程序尚未初始化。 Crashlytics is not the clue. Crashlytics不是线索。 The code is in this way, because I had several weird bugs of IllegalStateException and I supposed the lack of Application initialization: I was trying to investigate it. 代码是这样的,因为我有几个IllegalStateException怪异的错误,并且我认为缺少应用程序初始化:我正在尝试对其进行调查。

Here the stack logs from the Developer Console (the crash does not reach Crashlytics) 此处,堆栈是从开发人员控制台记录的(崩溃不会到达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)

Here an extract of the manifest: 这里是清单的摘录:

<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>

Does anyone have any idea of what happens and how I could fix the bug? 有谁知道会发生什么以及如何解决该错误?

You have below line your service: 您的服务如下:

android:stopWithTask="false"

If set to true, this service with be automatically stopped when the user removes a task rooted in an activity owned by the application. 如果设置为true,则当用户删除根于应用程序拥有的活动的任务时,该服务将自动停止。 The default is false. 默认为false。

I would suggest setting it true because you don't want to run the service even if the task owned by the application is closed. 我建议将其设置为true,因为即使关闭了应用程序拥有的任务,您也不想运行该服务。 Also check if your Service is STICKY . 还要检查您的服务是否为STICKY

I can't tell you why your service is started before the Application 's onCreate was called, especially because there is a lot of code missing from how you implemented the service. 我无法告诉您为什么在调用ApplicationonCreate之前启动您的服务,尤其是因为实现服务的方式缺少很多代码。 When/how do you start it? 您何时/如何开始? What's your target API? 您的目标API是什么? Is there a certain device/manufacturer which causes this exclusively? 是否有某些设备/制造商专门造成这种情况? It's not the weirdest thing I've seen on Android, though. 不过,这并不是我在Android上看到的最奇怪的事情。 It might very well also be a bug in the OS if it's only Android 8 and zero occurrences on other versions. 如果它仅是Android 8,并且在其他版本中出现的次数为零,则很可能也是OS中的错误。

Maybe there's something we can do to fix it: 也许我们可以采取一些措施来解决它:

Approach A 方法A

Firstly, the Application is not the first thing being created when your app starts (a little counter intuitive). 首先, Application不是在您的应用程序启动时首先创建的(有点反常)。 As far as I'm aware, ContentProviders are the first components being created when your app starts (that's why certain services like Firebase use them to set things up, eg crash reporting). 据我所知, ContentProviders是您的应用程序启动时首先创建的组件(这就是Firebase之类的某些服务会使用它们来进行设置的原因,例如崩溃报告)。

onCreate is also not the first thing being called when you Application is created (a little more counter intuitive). 创建Application时, onCreate并不是第一个被调用的东西(更加直观)。 Right after creation the init function / constructor is invoked. 创建后立即调用init函数/构造函数。 Any work which does not require a Context can already be done in the default constructor. 不需要Context任何工作都可以在默认构造函数中完成。 The next best thing being called is the attachBaseContext function. 接下来要调用的最好的方法是attachBaseContext函数。 That's the earliest you can use a Context to run initializations. 这是最早可以使用Context运行初始化的方法。 At this point ContentProviders get created (constructor + onCreate ). 此时,将创建ContentProviders (构造函数+ onCreate )。 And only now the Application 's onCreate get called. 直到现在, ApplicationonCreate才被调用。

Check this little sample out: 检查这个小样本:

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
    }

    ...

}

If you start this app, you get this output: 如果启动此应用程序,则会得到以下输出:

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

You could use this fact to move your critical initializations to eg a ContentProvider or to the attachBaseContext method and try to out run the early created Service instances. 您可以使用此事实将关键的初始化移动到例如ContentProviderattachBaseContext方法,并尝试使早期创建的Service实例运行。

Approach B 方法B

An other idea would be to delay the Service initialization manually in case of an error. 另一个想法是在出现错误的情况下手动延迟Service初始化。 As this would only happen in 0.3% of all cases, I think a little delay should be fine to prevent a crash. 由于只有0.3%的情况会发生这种情况,因此我认为应稍加延迟以防止崩溃。 You could do something like this (sorry for my Kotlin): 您可以执行以下操作(对不起,我的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
}


So basically you do your check and if the app is not ready you try again after 100ms. 因此,基本上,您会进行检查,如果应用尚未准备就绪,请在100毫秒后重试。 This is not perfect but better than a crash. 这并不完美,但是比崩溃更好。 I'd recommend to still collect data on this behviour to A. see how many loops are required and B. how often this occurs and on which devices. 我建议仍以此行为收集数据给A。查看需要进行多少次循环,以及B.这种循环发生的频率以及在哪些设备上进行。 It's maybe also a good idea to gate this by something like Firebase Remote Config or control the maximum number of attempts through it. 通过Firebase Remote Config之类的方法进行控制或控制通过它的最大尝试次数也是一个好主意。 Getting the cloud values may fail because your app is in a weird init state, though... 但是,获取云值可能会失败,因为您的应用处于奇怪的初始化状态。

By doing this you can also collect more insights about this behavior. 通过这样做,您还可以收集有关此行为的更多见解。 Is the application initialized at all (that's why I'd limit the number of loops)? 应用程序是否已初始化(这就是为什么我限制循环数的原因)?

Approach B v2 To address your concerns from the comments to this answer, here is a updated version handling the intents. 方法B v2为了解决您从注释到答案的疑虑,这是处理意图的更新版本。

In your Service do the same check but cache Intent s you could not handle. 在您的Service执行相同的检查,但是缓存您无法处理的Intent The service will stop itself 该服务将自行停止

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)
}

In your Application check whether the service encountered an issue and manually start it again if so: 在您的Application检查服务是否遇到问题,如果是,请手动重新启动:


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

Approach C 方法C

This is not a solution but might bring you closer to a "clean" solutions: You can try to cache the last 100 logs on disk (app creation, activity states, ...) and when the service is started append those 100 logs to the crash report. 这不是解决方案,但可能使您更接近“干净”的解决方案:您可以尝试将磁盘上的最后100条日志(应用程序创建,活动状态等)缓存起来,并在服务启动时将这100条日志附加到崩溃报告。 This might give valuable insights, eg whether the app was recently used or was idle/closed for a long time. 这可能会提供有价值的见解,例如,该应用程序是最近使用还是长时间闲置/关闭。 Maybe you find something leading you into the right direction. 也许您找到了一些可以引导您走向正确方向的东西。 It would be interesting to know more about the circumstances in which this behavior occurs. 了解有关此行为发生情况的更多信息将很有趣。

Starting on Android 8, any Not Sticky Service will be destroyed after going to background for more than few minutes (tipically 1 minute). 从Android 8开始,在后台运行超过几分钟(通常为1分钟)后,所有不粘滞服务都会被销毁。 Once the user cames back again to foreground as explained in https://developer.android.com/about/versions/oreo/background#services it will be recreated (service's onCreate will be called) https://developer.android.com/about/versions/oreo/background#services中所述 ,一旦用户再次回到前台,它将被重新创建(将调用服务的onCreate)

While an app is in the foreground, it can create and run both foreground and background services freely. 当应用程序处于前台时,它可以自由创建和运行前台和后台服务。 When an app goes into the background, it has a window of several minutes in which it is still allowed to create and use services. 当应用进入后台时,它会有几分钟的窗口,仍允许其创建和使用服务。 At the end of that window, the app is considered to be idle. 在该窗口结束时,该应用程序被认为是空闲的。 At this time, the system stops the app's background services, just as if the app had called the services' Service.stopSelf() methods. 此时,系统停止应用程序的后台服务,就像该应用程序已调用服务的Service.stopSelf()方法一样。

I can't remember if its just the service which is killed or the entire app, anyway after going to background for more than 1 minute and service becomes destroyed, there's already a change your app might die as a consequence of any other app requesting for more memory RAM. 我不记得是因为服务中断还是整个应用程序停止运行,无论是在后台运行1分钟以上并且服务被销毁之后,您的应用程序可能已经因为其他任何应用程序的请求而死亡更多的内存RAM。 And although your app is killed it stays in recent app list. 而且,尽管您的应用被杀死了,但仍保留在最近的应用列表中。

When user comes back to your app, application class's onCreate() will be called and your service's onCreate will also be called to recreate it. 当用户回到您的应用程序时,将调用应用程序类的onCreate(),并且还将调用您服务的onCreate来重新创建它。 I can't say which of both onCreate() methods will be fired first. 我不能说将首先触发两个onCreate()方法。

If you don't know how to kill an app IN BACKGROUND, just use: 如果您不知道如何在后台杀死应用,请使用: 在此处输入图片说明

Regardless of all that, if this weren't your problem you can access you application from Service by using getApplicationContext() and cast it to MyBaseApp. 无论如何,如果这不是您的问题,则可以使用getApplicationContext()从Service访问您的应用程序并将其转换为MyBaseApp。 No need to access an static method on MyBaseApp. 无需访问MyBaseApp上的静态方法。 This way won't fail, as Service extends Context and therefore you can always cast it to your Application 这种方法不会失败,因为服务扩展了上下文,因此您可以始终将其强制转换为应用程序

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

Background execution limits 后台执行限制

Android 8.0 Behavior Changes Android 8.0行为更改

As one of the changes that Android 8.0 (API level 26) introduces to improve battery life, when your app enters the cached state, with no active components, the system releases any wakelocks that the app holds. 作为Android 8.0 (API level 26)引入的旨在延长电池寿命的更改之一,当您的应用进入缓存状态且没有活动组件时,系统会释放该应用持有的所有唤醒锁。

In addition, to improve device performance, the system limits certain behaviors by apps that are not running in the foreground. 此外,为了提高设备性能,系统会限制未在前台运行的应用程序的某些行为。 Specifically: 特别:

Apps that are running in the background now have limits on how freely they can access background services. 现在,在后台运行的应用程序在访问后台服务的自由度方面受到限制。 Apps cannot use their manifests to register for most implicit broadcasts (that is, broadcasts that are not targeted specifically at the app). 应用程序无法使用其清单来注册大多数隐式广播(即,并非专门针对应用程序的广播)。 By default, these restrictions only apply to apps that target O. However, users can enable these restrictions for any app from the Settings screen, even if the app has not targetted O. 默认情况下,这些限制仅适用于面向O的应用程序。但是,即使该应用程序未针对O,用户也可以从“设置”屏幕为任何应用程序启用这些限制。

Android 8.0 Android 8.0

(API level 26) also includes the following changes to specific methods: (API level 26)还包括对特定方法的以下更改:

The startService() method now throws an IllegalStateException if an app targeting Android 8.0 tries to use that method in a situation when it isn't permitted to create background services. 如果针对Android 8.0的应用尝试在不允许创建后台服务的情况下尝试使用该方法,则startService()方法现在将引发IllegalStateException The new Context.startForegroundService() method starts a foreground service. 新的Context.startForegroundService()方法启动前台服务。 The system allows apps to call Context.startForegroundService() even while the app is in the background. 该系统允许应用程序在后台运行时调用Context.startForegroundService() However, the app must call that service's startForeground() method within five seconds after the service is created. 但是,应用程序必须在创建服务后五秒钟内调用该服务的startForeground()方法。 For more information, see Background Execution Limits. 有关更多信息,请参见后台执行限制。

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

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