Android 異步UI

之前有分析過子線程中直接更新ui

眾所周知CalledFromWrongThreadException是檢查original thread,也就是創(chuàng)建ui的線程亥贸。那么在子線程中創(chuàng)建ui厌殉,自然也可以在此線程中更新ui。

要維護(hù)一個(gè)子線程,首先想到的就是HandlerThread

下面寫個(gè)demo

class MainActivity : AppCompatActivity() {
    private val handlerThread = HandlerThread("AsyncHandlerThread")

    class H(looper: Looper, private val weak: WeakReference<MainActivity>) : Handler(looper) {
        var tvMain: TextView? = null

        override fun handleMessage(msg: Message) {
            super.handleMessage(msg)
            when (msg.what) {
                10086 -> {
                    val root = LayoutInflater.from(weak.get())
                        .inflate(R.layout.activity_main, null)
                    val wm: WindowManager =
                        weak.get()?.getSystemService(Context.WINDOW_SERVICE) as WindowManager
                    val param = WindowManager.LayoutParams()
                    param.width = WindowManager.LayoutParams.MATCH_PARENT
                    param.height = 300
                    wm.addView(root, param)
                    tvMain = root.findViewById(R.id.tvMain)
                    tvMain?.setOnClickListener {
                        tvMain?.text = "${Thread.currentThread()}"
                    }
                }
            }
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        handlerThread.start()
        val handler = H(handlerThread.looper, WeakReference(this@MainActivity))
        handler.sendEmptyMessage(10086)
    }

    override fun onDestroy() {
        handlerThread.quitSafely()
        super.onDestroy()
    }
}

Run,點(diǎn)擊TextView驗(yàn)證一下



onClick回調(diào)在HandlerThread所在線程。通過這個(gè)思路逗堵,可以將部分ui挪到子線程中,減少主線程耗時(shí)眷昆。

追本溯源蜒秤,WindowManager的實(shí)現(xiàn)類是WindowManagerImpl汁咏,

WindowManagerImpl.addView

    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
                mContext.getUserId());
    }

WindowManagerGlobal.addView

        ...
        ViewRootImpl root;
        root = new ViewRootImpl(view.getContext(), display);
        root.setView(view, wparams, panelParentView, userId);
        ...

ViewRootImpl構(gòu)造方法中為mThread賦值、初始化Choreographer

        mThread = Thread.currentThread();
        mChoreographer = useSfChoreographer
                ? Choreographer.getSfInstance() : Choreographer.getInstance();

Choreographer.getSfInstance()作媚,從ThreadLocal中取對應(yīng)線程的Choreographer

    public static Choreographer getSfInstance() {
        return sSfThreadInstance.get();
    }

    private static final ThreadLocal<Choreographer> sSfThreadInstance =
            new ThreadLocal<Choreographer>() {
                @Override
                protected Choreographer initialValue() {
                    Looper looper = Looper.myLooper();
                    if (looper == null) {
                        throw new IllegalStateException("The current thread must have a looper!");
                    }
                    return new Choreographer(looper, VSYNC_SOURCE_SURFACE_FLINGER);
                }
            };

回看ViewRootImpl.setView()

        ...
        requestLayout();
        ...

ViewRootImpl.requestLayout()

    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

ViewRootImpl.scheduleTraversals()

    final ViewRootHandler mHandler = new ViewRootHandler();

    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }

mHandler調(diào)用Handler無參構(gòu)造方法初始化取的是當(dāng)前線程looper梆暖,如此一來mHandler、mChoreographer都在demo中HandlerThread線程所在的事件循環(huán)掂骏。

mTraversalRunnable

    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }

doTraversal大家都懂,后面就是繪制流程了厚掷。

繼續(xù)跟一下Choreographer相關(guān)邏輯弟灼,回看Choreographer.postCallback()

    public void postCallback(int callbackType, Runnable action, Object token) {
        postCallbackDelayed(callbackType, action, token, 0);
    }

    public void postCallbackDelayed(int callbackType,
            Runnable action, Object token, long delayMillis) {
        ...
        postCallbackDelayedInternal(callbackType, action, token, delayMillis);
    }

    private void postCallbackDelayedInternal(int callbackType,
            Object action, Object token, long delayMillis) {
        synchronized (mLock) {
            final long now = SystemClock.uptimeMillis();
            final long dueTime = now + delayMillis;
            mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);

            if (dueTime <= now) {
                scheduleFrameLocked(now);
            } else {
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
                msg.arg1 = callbackType;
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, dueTime);
            }
        }
    }

Choreographer.scheduleFrameLocked()

    private void scheduleFrameLocked(long now) {
        if (!mFrameScheduled) {
            mFrameScheduled = true;
            if (USE_VSYNC) {
                // If running on the Looper thread, then schedule the vsync immediately,
                // otherwise post a message to schedule the vsync from the UI thread
                // as soon as possible.
                if (isRunningOnLooperThreadLocked()) {
                    scheduleVsyncLocked();
                } else {
                    Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
                    msg.setAsynchronous(true);
                    mHandler.sendMessageAtFrontOfQueue(msg);
                }
            } else {
                Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, nextFrameTime);
            }
        }
    }

到這里就差不太多。上述注釋說明冒黑,在有Looper的線程立即發(fā)出vsync田绑;否則post 一個(gè)帶vsync的message到ui線程。我們的HandlerThread中當(dāng)然是有Looper事件循環(huán)的啦抡爹。

Choreographer.scheduleVsyncLocked()

    private final FrameDisplayEventReceiver mDisplayEventReceiver;

    private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable{}

    private void scheduleVsyncLocked() {
        mDisplayEventReceiver.scheduleVsync();
    }

調(diào)用父類DisplayEventReceiver.scheduleVsync()

    public void scheduleVsync() {
        if (mReceiverPtr == 0) {
            Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event "
                    + "receiver has already been disposed.");
        } else {
            nativeScheduleVsync(mReceiverPtr);
        }
    }

nativeScheduleVsync()掩驱,VSync信號(hào)最終調(diào)到C層。筆者比較懶冬竟,就不跟C層代碼了欧穴,搜索一番得到結(jié)論,VSync信號(hào)接受回調(diào)的方法是onVsync()

FrameDisplayEventReceiver.onVsync()

        public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
            ...
            mTimestampNanos = timestampNanos;
            mFrame = frame;
            Message msg = Message.obtain(mHandler, this);
            msg.setAsynchronous(true);
            mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
        }

Message.obtain(mHandler, this)泵殴,this也就是FrameDisplayEventReceiver這個(gè)Runnable涮帘,那么就調(diào)到了run方法。

FrameDisplayEventReceiver.run()

        public void run() {
            mHavePendingVsync = false;
            doFrame(mTimestampNanos, mFrame);
        }

doFrame()最終就是執(zhí)行前面ViewRootImpl.mTraversalRunnable

驗(yàn)證一下笑诅,profile運(yùn)行record一次看調(diào)用棧调缨。



繪制流程確實(shí)都在AsyncHandlerThread這個(gè)子線程中了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末吆你,一起剝皮案震驚了整個(gè)濱河市弦叶,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌妇多,老刑警劉巖伤哺,帶你破解...
    沈念sama閱讀 216,919評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異砌梆,居然都是意外死亡默责,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,567評論 3 392
  • 文/潘曉璐 我一進(jìn)店門咸包,熙熙樓的掌柜王于貴愁眉苦臉地迎上來桃序,“玉大人,你說我怎么就攤上這事烂瘫∶叫埽” “怎么了奇适?”我有些...
    開封第一講書人閱讀 163,316評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長芦鳍。 經(jīng)常有香客問我嚷往,道長,這世上最難降的妖魔是什么柠衅? 我笑而不...
    開封第一講書人閱讀 58,294評論 1 292
  • 正文 為了忘掉前任皮仁,我火速辦了婚禮,結(jié)果婚禮上菲宴,老公的妹妹穿的比我還像新娘贷祈。我一直安慰自己,他們只是感情好喝峦,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,318評論 6 390
  • 文/花漫 我一把揭開白布势誊。 她就那樣靜靜地躺著,像睡著了一般谣蠢。 火紅的嫁衣襯著肌膚如雪粟耻。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,245評論 1 299
  • 那天眉踱,我揣著相機(jī)與錄音挤忙,去河邊找鬼。 笑死勋锤,一個(gè)胖子當(dāng)著我的面吹牛饭玲,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播叁执,決...
    沈念sama閱讀 40,120評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼茄厘,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了谈宛?” 一聲冷哼從身側(cè)響起次哈,我...
    開封第一講書人閱讀 38,964評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎吆录,沒想到半個(gè)月后窑滞,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,376評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡恢筝,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,592評論 2 333
  • 正文 我和宋清朗相戀三年哀卫,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片撬槽。...
    茶點(diǎn)故事閱讀 39,764評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡此改,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出侄柔,到底是詐尸還是另有隱情共啃,我是刑警寧澤占调,帶...
    沈念sama閱讀 35,460評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站移剪,受9級(jí)特大地震影響究珊,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜纵苛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,070評論 3 327
  • 文/蒙蒙 一剿涮、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧攻人,春花似錦幔虏、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,697評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽陷谱。三九已至烙博,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間烟逊,已是汗流浹背渣窜。 一陣腳步聲響...
    開封第一講書人閱讀 32,846評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留宪躯,地道東北人乔宿。 一個(gè)月前我還...
    沈念sama閱讀 47,819評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像访雪,于是被迫代替她去往敵國和親详瑞。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,665評論 2 354

推薦閱讀更多精彩內(nèi)容

  • title: '深入理解android2-WMS,控件-圖床版'date: 2020-03-08 16:22:42...
    劉佳闊閱讀 993評論 0 0
  • 梳理流程和圖形參考Stan_Z的博客:Android圖形系統(tǒng)篇總結(jié):http://www.reibang.com...
    QGv閱讀 3,044評論 0 2
  • 我是黑夜里大雨紛飛的人啊 1 “又到一年六月臣缀,有人笑有人哭坝橡,有人歡樂有人憂愁,有人驚喜有人失落精置,有的覺得收獲滿滿有...
    陌忘宇閱讀 8,536評論 28 53
  • 信任包括信任自己和信任他人 很多時(shí)候计寇,很多事情,失敗脂倦、遺憾番宁、錯(cuò)過,源于不自信赖阻,不信任他人 覺得自己做不成蝶押,別人做不...
    吳氵晃閱讀 6,187評論 4 8
  • 步驟:發(fā)微博01-導(dǎo)航欄內(nèi)容 -> 發(fā)微博02-自定義TextView -> 發(fā)微博03-完善TextView和...
    dibadalu閱讀 3,134評論 1 3