IllegalThreadStateException OneSignal 崩溃解决方法

IllegalThreadStateException OneSignal crash workaround

根据 OneSignal 的 github issues.

中提到的错误,我们的应用每天会收到大约 1000 次崩溃

错误解释:

很遗憾,我无法重现此问题。所有的崩溃都来自 Crashlytics 报告。 SDK 版本 3.12.4

设备:

1) Samsung: Galaxy A5(2017), Galaxy S8, Galaxy A50, Galaxy S10+, Galaxy S10  
2) Xiaomi: Mi A2, Mi A2 lite, Mi A1, Mi A3, Redmi Note 5 Pro 
3) Oneplus: ONEPLUS A6010, OnePlus5T, GM191011, GM19008, OnePlus58

堆栈跟踪:

Caused by java.lang.IllegalThreadStateException
       at java.lang.Thread.start(Thread.java:724)
       at com.onesignal.OneSignalPrefs$WritePrefHandlerThread.startDelayedWrite(OneSignalPrefs.java:117)
       at com.onesignal.OneSignalPrefs.startDelayedWrite(OneSignalPrefs.java:183)
       at com.onesignal.OneSignal.setAppContext(OneSignal.java:601)
       at com.onesignal.OneSignalSyncServiceUtils.doBackgroundSync(OneSignalSyncServiceUtils.java:175)
       at com.onesignal.SyncJobService.onStartJob(SyncJobService.java:40)
       at android.app.job.JobService.onStartJob(JobService.java:62)
       at android.app.job.JobServiceEngine$JobHandler.handleMessage(JobServiceEngine.java:108)
       at android.os.Handler.dispatchMessage(Handler.java:106)
       at android.os.Looper.loop(Looper.java:280)
       at android.app.ActivityThread.main(ActivityThread.java:6748)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

问题:

主要问题是他们将其标记为中等优先级并且错误存在大约 3 个月。就因为这个问题,我们的生命体征要折腾了。这让我们付出了很多代价。

是否存在可以暂时解决问题的解决方法?


P.S:

如果需要,我准备提供更多相关信息。提前致谢!

您可能(如果不太可能)使用旧版本的 OneSignal,其中 synchronized void startDelayedWrite() 不是 synchronized。在这种情况下,我会更新,看看是否能解决问题。如果没有看到以下内容:

此处出现异常:

synchronized void startDelayedWrite() {
            if (mHandler == null) {
                start();
                mHandler = new Handler(getLooper());
            }
//rest of function irrelevant.

Thread.start 的 Javadoc 声明如下:

It is never legal to start a thread more than once. In particular, a thread may not be restarted once it has completed execution.

从这个和Thread.start的实现,我们可以推断出有问题的线程被启动了两次。

为了thisstartDelayedWrite中启动两次,new Handler(getLooper())需要抛出一个异常,否则不会再进入这个if语句。我看不出 getLooper() 有任何方法可以抛出异常,但是 new Handler 如果 getLooper() 为 null,当然可以。

getLooper()的实现如下:

public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }
//rest omitted b/c irrelevant

有问题的线程退出的唯一方法是 Thread.run 提前退出。

线程的 运行 实现如下所示:

@Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }

Looper.loop,旨在成为一个从消息队列中读取的半无限循环。

注意Looper.loop中的以下内容:

for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

所以如果msg == null,那么我们的线程很快退出,可能导致mHandler = new Handler(getLooper());出现异常,导致Thread.start被调用了两次

还有其他合理的解释。例如,Looper.loop 可能在较早的某个时间点崩溃。

为了缓解这种情况,我会在 mHandler = new Handler(getLooper()); 周围添加一个 try{} finally{} 块,以处理 Looper 已经退出的情况。或者我可能是错的,这种竞争条件可能是由完全不同的东西引起的。

错误已修复


The IllegalThreadStateException will no longer be thrown in the 3.13.0 Release. You will now see the root exception being thrown instead so these crashes can be diagnosed further.