Android可不可以在子線程中更新UI?

關(guān)于這個(gè)問題在面試的時(shí)候可能會(huì)被問到映跟,其實(shí)在某些情況下是可以在子線程中更新UI的!

比如:在一個(gè)activity的xml文件中中隨便寫一個(gè)TextView文本控件,然后在Activity的onCreate方法中開啟一個(gè)子線程并在該子線程的run方法中更新TextView文本控件扬虚,你會(huì)發(fā)現(xiàn)根本沒有任何問題努隙。

private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.threadcrashui_act);
    textView = (TextView) findViewById(R.id.textView);
    new Thread(new Runnable() {
        @Override
        public void run() {
            // 更新TextView文本內(nèi)容
            textView.setText("update TextView");
        }
    }).start();
}
Paste_Image.png

但是如果你讓子線程休眠2秒鐘如下面代碼:

private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.threadcrashui_act);
    textView = (TextView) findViewById(R.id.textView);
    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep(2000);
                //更新TextView文本內(nèi)容
                textView.setText("update TextView");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }).start();
}
Paste_Image.png

程序直接掛掉了,好吧辜昵,我們先去看看log日志荸镊,如圖提示
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
程序不允許在非UI線程中更新UI線程

Paste_Image.png
對于這個(gè)問題,可能大家都跟我一樣有點(diǎn)疑惑對吧堪置?我當(dāng)時(shí)也很疑惑躬存,不明白為啥是這樣!Rㄏ恰借浊!然后我去翻資料查找原因扔仓,最終定位到setText這個(gè)方法
  /**
 * Sets the string value of the TextView. TextView <em>does not</em> accept
 * HTML-like formatting, which you can do with text strings in XML resource files.
 * To style your strings, attach android.text.style.* objects to a
 * {@link android.text.SpannableString SpannableString}, or see the
 * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources">
 * Available Resource Types</a> documentation for an example of setting
 * formatted text in the XML resource file.
 *
 * @attr ref android.R.styleable#TextView_text
 */
@android.view.RemotableViewMethod
public final void setText(CharSequence text) {
    setText(text, mBufferType);
}
緊接著看setText方法
   /**
 * Sets the text that this TextView is to display (see
 * {@link #setText(CharSequence)}) and also sets whether it is stored
 * in a styleable/spannable buffer and whether it is editable.
 *
 * @attr ref android.R.styleable#TextView_text
 * @attr ref android.R.styleable#TextView_bufferType
 */
public void setText(CharSequence text, BufferType type) {
    setText(text, type, true, 0);
    if (mCharWrapper != null) {
        mCharWrapper.mChars = null;
    }
}
再進(jìn)setText方法
private void setText(CharSequence text, BufferType type,
                     boolean notifyBefore, int oldlen) {
  //前邊的都省略掉 .......
    if (mLayout != null) {
    //這個(gè)方法的作用就是讓界面重新繪制下
        checkForRelayout();
    }
   //后邊的也直接省略掉

}

然后我們看下checkForRelayout方法,在這里大家會(huì)看到一個(gè)invalidate的方法,因?yàn)槲覀冎浪械膙iew更新操作都會(huì)調(diào)用view的invalidate方法掀序!那么問題就在這里了
/**
 * Check whether entirely new text requires a new view layout
 * or merely a new text layout.
 */
private void checkForRelayout() {
       //注意看這里
        invalidate();
    } else {
       //注意看這里
        invalidate();
    }
}
然后我們進(jìn)invalidate方法中查找原因
/**
 * Invalidate the whole view. If the view is visible,
 * {@link #onDraw(android.graphics.Canvas)} will be called at some point in
 * the future.
 * <p>
 * This must be called from a UI thread. To call from a non-UI thread, call
 * {@link #postInvalidate()}.
 */
public void invalidate() {
    invalidate(true);
}
好 那么我們就看下invalidate(true)方法看看到底是哪的問題
 /**
 * This is where the invalidate() work actually happens. A full invalidate()
 * causes the drawing cache to be invalidated, but this function can be
 * called with invalidateCache set to false to skip that invalidation step
 * for cases that do not need it (for example, a component that remains at
 * the same dimensions with the same content).
 *
 * @param invalidateCache Whether the drawing cache for this view should be
 *            invalidated as well. This is usually true for a full
 *            invalidate, but may be set to false if the View's contents or
 *            dimensions have not changed.
 */
void invalidate(boolean invalidateCache) {
    //可能不同版本的api源碼不太一樣,這里直接看invalidateInternal方法携取。
    invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,boolean fullInvalidate) {
      //我們關(guān)心的是這里 ViewParent纳令,這個(gè)ViewPrent是一個(gè)接口,而ViewGroup與ViewRootImpl實(shí)現(xiàn)了它进栽,有耐心的朋友可以在View這個(gè)類中去查找mPrent相關(guān)的一些信息德挣,如果細(xì)心的朋友在ViewGroup類中會(huì)找到ViewRootImpl這個(gè)類在它里邊的一些操作如setDragFocus等等。
        final ViewParent p = mParent;
        if (p != null && ai != null && l < r && t < b) {
            final Rect damage = ai.mTmpInvalRect;
            damage.set(l, t, r, b);
            //程序在這里檢查是不是在UI線程中做操作
            p.invalidateChild(this, damage);
        }
   }
}
這個(gè)ViewRootImpl我們可能在Eclipse或者Android Studio中我們無法查看快毛,大家可以使用Source Insight 去查看源碼:打開Source Insight 打開ViewRootImpl類格嗅,找到 invalidateChild這個(gè)方法
public void invalidateChild(View child, Rect dirty) {
  //關(guān)鍵的地方就是這個(gè)方法
    checkThread();
  //后邊的全部省略
}
 void checkThread() {
    //在這里mThread表示的是主線程番挺,程序作了判斷,檢查當(dāng)前線程是不是主線程屯掖,如果不是就會(huì)拋出異常
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
                "Only the original thread that created a view hierarchy can touch its views.");
    }
}
看到上邊方法中拋出的異常是不是感覺很熟悉玄柏,對,沒錯(cuò)贴铜,就是我log中截出來的那句話7嗾!那么我們現(xiàn)在懵逼了绍坝,為什么我們在不讓子線程休眠的情況下去更新TextView文本可以徘意,而讓線程休眠兩秒后就出拋異常呢?根本原因就是ViewRootImpl到底是在哪里被初始化的轩褐!ViewRootImpl是在onResume中初始化的椎咧,而我們開啟的子線程是在onCreat方法中,這個(gè)時(shí)候程序沒有去檢測當(dāng)前線程是不是主線程把介,所以沒有拋異常G诜怼!下邊我們?nèi)タ碅ctivityThread源碼劳澄,去找出原因5丶肌!
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward) {
         if (r.window == null && !a.mFinished && willBeVisible) {
            r.window = r.activity.getWindow();
            View decor = r.window.getDecorView();
            decor.setVisibility(View.INVISIBLE);
            //ViewPrent實(shí)現(xiàn)類
            ViewManager wm = a.getWindowManager();
            WindowManager.LayoutParams l = r.window.getAttributes();
            a.mDecor = decor;
            l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
            l.softInputMode |= forwardBit;
            if (a.mVisibleFromClient) {
                a.mWindowAdded = true;
          //問題的根源在這里秒拔,addView方法在這里對ViewRootImpl進(jìn)行初始化莫矗,大家可以去看看ViewGroup的源碼,找里邊的addView方法砂缩,你會(huì)發(fā)現(xiàn)作谚,最后又回到View的invalidate(true)方法;
                wm.addView(decor, l);
            }       
    }
}

面試會(huì)經(jīng)常問到,所以整理了下留待自己使用庵芭,同時(shí)分享給大家C美痢!有不足之處請大家留言指正双吆,同時(shí)歡迎加入 46674429 366576264 QQ技術(shù)群眨唬,進(jìn)行交流!:美帧匾竿!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市蔚万,隨后出現(xiàn)的幾起案子岭妖,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,744評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件昵慌,死亡現(xiàn)場離奇詭異假夺,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)斋攀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,505評論 3 392
  • 文/潘曉璐 我一進(jìn)店門已卷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蜻韭,你說我怎么就攤上這事悼尾∈量郏” “怎么了肖方?”我有些...
    開封第一講書人閱讀 163,105評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長未状。 經(jīng)常有香客問我俯画,道長,這世上最難降的妖魔是什么司草? 我笑而不...
    開封第一講書人閱讀 58,242評論 1 292
  • 正文 為了忘掉前任艰垂,我火速辦了婚禮,結(jié)果婚禮上埋虹,老公的妹妹穿的比我還像新娘猜憎。我一直安慰自己,他們只是感情好搔课,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,269評論 6 389
  • 文/花漫 我一把揭開白布胰柑。 她就那樣靜靜地躺著,像睡著了一般爬泥。 火紅的嫁衣襯著肌膚如雪柬讨。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,215評論 1 299
  • 那天袍啡,我揣著相機(jī)與錄音踩官,去河邊找鬼。 笑死境输,一個(gè)胖子當(dāng)著我的面吹牛蔗牡,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播嗅剖,決...
    沈念sama閱讀 40,096評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼辩越,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了窗悯?” 一聲冷哼從身側(cè)響起区匣,我...
    開封第一講書人閱讀 38,939評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后亏钩,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體莲绰,經(jīng)...
    沈念sama閱讀 45,354評論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,573評論 2 333
  • 正文 我和宋清朗相戀三年姑丑,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蛤签。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,745評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡栅哀,死狀恐怖震肮,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情留拾,我是刑警寧澤戳晌,帶...
    沈念sama閱讀 35,448評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站痴柔,受9級特大地震影響沦偎,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜咳蔚,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,048評論 3 327
  • 文/蒙蒙 一豪嚎、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧谈火,春花似錦侈询、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,683評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽谍肤。三九已至啦租,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間荒揣,已是汗流浹背篷角。 一陣腳步聲響...
    開封第一講書人閱讀 32,838評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留系任,地道東北人恳蹲。 一個(gè)月前我還...
    沈念sama閱讀 47,776評論 2 369
  • 正文 我出身青樓,卻偏偏與公主長得像俩滥,于是被迫代替她去往敵國和親嘉蕾。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,652評論 2 354

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