首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >在应用程序onCreate之前调用服务onCreate

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

Stack Overflow用户
提问于 2019-06-16 23:56:16
回答 4查看 2.3K关注 0票数 3

在我最新版本的应用程序中,开发人员控制台中报告了一些崩溃,这让我抓狂。它们以java.lang.IllegalStateException的形式出现,并且似乎在Service.onCreate之前没有调用Application.onCreate

只有大约0.3%的用户会发生这种情况,而且只有Android 8设备会发生这种情况。我无法在我的设备上重现它。

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

代码语言:javascript
复制
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从未被调用过。

代码语言:javascript
复制
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)

代码语言:javascript
复制
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)

下面是清单的摘录:

代码语言:javascript
复制
<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>

有没有人知道会发生什么,以及我如何修复这个bug?

EN

回答 4

Stack Overflow用户

发布于 2019-06-20 06:17:31

我不能告诉你为什么你的服务是在ApplicationonCreate被调用之前启动的,特别是因为你实现服务的过程中缺少了很多代码。你什么时候/如何启动它?你的目标应用程序接口是什么?是不是有某个设备/制造商专门导致了这种情况?但这并不是我在安卓上见过的最奇怪的事情。如果只有安卓8,而其他版本没有出现,这很可能也是操作系统的一个错误。

也许我们可以做些什么来修复它:

方法A

首先,当你的应用程序启动时,Application不是第一个被创建的东西(有点违反直觉)。据我所知,ContentProviders是你的应用程序启动时创建的第一个组件(这就是为什么像Firebase这样的某些服务使用它们来设置东西,例如崩溃报告)。

当你的Application被创建时,onCreate也不是第一个被调用的东西(有点违反直觉)。在创建之后,立即调用init函数/构造函数。任何不需要Context的工作都可以在默认构造函数中完成。第二个最好的调用是attachBaseContext函数。这是使用Context运行初始化的最早时间。此时,创建了ContentProviders (构造器+ onCreate)。只有现在ApplicationonCreate才会被调用。

看看这个小示例:

代码语言:javascript
复制
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")
    }
}
代码语言:javascript
复制
class MyContentProvider : ContentProvider() {

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

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

    ...

}

如果你启动这个应用程序,你会得到这样的输出:

代码语言:javascript
复制
I/MyApp: init
I/MyApp: attachBaseContext
I/MyContentProvider: init
I/MyContentProvider: onCreate
I/MyApp: onCreate

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

方法B

另一个想法是在出现错误的情况下手动延迟Service初始化。由于这种情况只会发生在所有情况的0.3%,我认为稍微延迟一下就可以防止崩溃。你可以这样做(为我的Kotlin道歉):

代码语言:javascript
复制
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远程配置或通过它控制最大尝试次数来实现这一点也可能是一个好主意。获取云值可能会失败,因为您的应用程序处于奇怪的初始化状态...

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

Approach B v2为了解决你从评论到这个答案的担忧,这里是一个处理意图的更新版本。

在你的Service中做同样的检查,但是缓存你不能处理的Intent。该服务将自行停止

代码语言:javascript
复制
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中,检查服务是否遇到问题,如果遇到问题,请手动重新启动:

代码语言:javascript
复制
fun onCreate() {
    super.onCreate()
    // init app
    if (MyService.hasPendingIntents()) {
       startService(Intent(this, MyService::class).putExtra("RECOVER_AFTER_FAILURE", true)
    }
}

方法C

这不是一个解决方案,但可能会让你更接近一个“干净”的解决方案:你可以尝试在磁盘上缓存最后100个日志(应用程序创建、活动状态等)当服务启动时,将这100个日志附加到崩溃报告中。这可能会提供有价值的见解,例如应用程序是否最近使用或空闲/关闭了很长一段时间。也许你发现了一些指引你走向正确方向的东西。了解更多发生这种行为的情况将是一件有趣的事情。

票数 4
EN

Stack Overflow用户

发布于 2019-06-17 00:08:18

您的服务如下所示:

代码语言:javascript
复制
android:stopWithTask="false"

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

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

票数 0
EN

Stack Overflow用户

发布于 2019-06-26 12:10:51

后台执行限制

Android 8.0 Behavior Changes

作为Android 8.0 (API level 26)引入的提高电池寿命的更改之一,当您的应用程序进入缓存状态时,如果没有活动组件,系统将释放应用程序持有的所有唤醒锁。

此外,为了提高设备性能,系统会限制不在前台运行的应用程序的某些行为。具体地说:

在后台运行的应用程序现在对访问后台服务的自由程度有限制。应用程序不能使用其清单来注册大多数隐式广播(即,不是专门针对应用程序的广播)。默认情况下,这些限制仅适用于以O为目标的应用程序。但是,用户可以从设置屏幕为任何应用程序启用这些限制,即使该应用程序没有以O为目标。

安卓8.0

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

现在,如果针对Android8.0的应用程序试图在不允许创建后台服务的情况下使用该方法,startService()方法将抛出IllegalStateException。新的Context.startForegroundService()方法启动一个前台服务。该系统允许应用程序调用Context.startForegroundService(),即使应用程序在后台。但是,在创建服务后,应用程序必须在five seconds内调用该服务的startForeground()方法。有关更多信息,请参见后台执行限制。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/56620451

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档