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