通過ActivityThread獲取Context

背景

為了保存一個全局可用的ApplicationContext對象,通過反射ActivityThread.currentActivityThread()來實現。近期在分析線上錯誤日志時,偶有發現這里會小概率死鎖,分析堆棧后發現問題出在“切換至主線程反射調用currentActivityThread()”時加的同步鎖這里,雖然最直接的方向是如何避免死鎖場景的出現,也就是不要用容易產生死鎖的調用方式,但可惜在我們的應用場景下這種調用方式是無法避免的,所以只能從別的方向入手,那為什么這里一定要切換至主線程調用,如果沒有這步操作,就不會有死鎖問題了,所以就從這里著手調查。

理論講解

https://zhuanlan.zhihu.com/p/26285030

根據上文中的分析,在低版本系統中,由于ActivityThread對象被保存在ThreadLocal變量中,所以必須在主線程調用 currentActivityThread() 方法才能獲取到,子線程下context為null。

通過閱讀 android.app.ActivityThread 的源碼,確認了從 API 18(Android 4.3)開始,ActivityThread對象由ThreadLocal變量改為普通的全局變量保存。經過測試,確實在API 18以下的機器上,子線程無法獲取到context,必須切換主線程,而從API 18開始,子線程也能正常獲取context,因此最優方案是在API 18以下的系統才做子線程到主線程的切換,其他情況下不用在意是否是主線程,而現在市面上用4.3以下系統的設備很少,這樣可以最大限度避免死鎖的隱患。

API 17的ActivityThread:

public static ActivityThread currentActivityThread() {
    return sThreadLocal.get();
}

API 18的ActivityThread:

public static ActivityThread currentActivityThread() {
    return sCurrentActivityThread;
}

示例代碼

private Context context;
public static Context getContext() {
    if (context == null) {
        try {
            Object actThread = currentActivityThread();
            if (actThread != null) {
                Context app = ReflectHelper.invokeInstanceMethod(actThread, "getApplication");
                if (app != null) {
                    context = app;
                }
            }
        } catch (Throwable t) {
            t.printStacktrace();
        }
    }
    return context;
}

/** 獲取當前進程的ActivityThread對象 */
public static Object currentActivityThread() {
    Object activityThread;
    final ReflectHelper.ReflectRunnable<Void, Object> mainThreadAct = new ReflectHelper.ReflectRunnable<Void, Object>() {
        public Object run(Void arg) {
            try {
                String clzName = ReflectHelper.importClass("android.app.ActivityThread");
                return ReflectHelper.invokeStaticMethod(clzName, "currentActivityThread");
            } catch (Throwable t) {
                t.printStacktrace();
            }
            return null;
        }
    };
    // 當前在主線程,或者系統版本>=18(Android 4.3)的子線程上,就直接在當前線程中獲取ActivityThread對象
    if (Thread.currentThread().getId() == Looper.getMainLooper().getThread().getId() || Build.VERSION.SDK_INT >= 18) {
        activityThread = mainThreadAct.run(null);
        if (activityThread != null) {
            return activityThread;
        }
    }
    // 如果是在系統版本<18的子線程上,必須切換到主線程獲取ActivityThread對象
    final Object lock = new Object();
    final Object[] output = new Object[1];
    synchronized (lock) {
        UIHandler.sendEmptyMessage(0, new Handler.Callback() {
            public boolean handleMessage(Message msg) {
                synchronized (lock) {
                    try {
                        output[0] = mainThreadAct.run(null);
                    } catch (Throwable t) {
                        t.printStacktrace();
                    } finally {
                        try {
                            lock.notify();
                        } catch (Throwable t) {
                            t.printStacktrace();
                        }
                    }
                }
                return false;
            }
        });
        try {
            lock.wait();
        } catch (Throwable t) {
            t.printStacktrace();
        }
    }
    return output[0];
}
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • [TOC] 1 JAVA: String為什么這么設計 在源碼中string是用final 進行修飾,它是不可更改...
    寄浮生閱讀 855評論 0 0
  • 描述清點擊 Android Studio 的 build 按鈕后發生了什么 build[https://jueji...
    CHSmile閱讀 603評論 0 1
  • 表情是什么,我認為表情就是表現出來的情緒。表情可以傳達很多信息。高興了當然就笑了,難過就哭了。兩者是相互影響密不可...
    Persistenc_6aea閱讀 125,890評論 2 7
  • 16宿命:用概率思維提高你的勝算 以前的我是風險厭惡者,不喜歡去冒險,但是人生放棄了冒險,也就放棄了無數的可能。 ...
    yichen大刀閱讀 6,098評論 0 4