Android子线程更新UI的方法总结
消息機(jī)制,對于Android開發(fā)者來說,應(yīng)該是非常熟悉。對于處理有著大量交互的場景,采用消息機(jī)制,是再好不過了。有些特殊的場景,比如我們都知道,在Android開發(fā)中,子線程不能更新UI,而主線程又不能進(jìn)行耗時(shí)操作,一種常用的處理方法就是,在子線程中進(jìn)行耗時(shí)操作,完成之后發(fā)送消息,通知主線程更新UI。或者使用異步任務(wù),異步任務(wù)的實(shí)質(zhì)也是對消息機(jī)制的封裝。
關(guān)于子線程到底能不能更新UI這個(gè)問題,之前看到一篇文章很有趣,讓我對這個(gè)問題也有了新的認(rèn)識(shí),那么我也來寫個(gè)簡單例子測試下,布局文件如下:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:paddingBottom="@dimen/activity_vertical_margin"android:paddingLeft="@dimen/activity_horizontal_margin"android:paddingRight="@dimen/activity_horizontal_margin"android:paddingTop="@dimen/activity_vertical_margin"tools:context="com.example.joy.messagetest.MainActivity"><TextViewandroid:id="@+id/tv_test"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerHorizontal="true"android:text="Hello World!" /></RelativeLayout>布局中只有一個(gè)TextView,java代碼如下:
package com.example.joy.messagetest;import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.TextView;public class MainActivity extends AppCompatActivity {private TextView mTvTest;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);initView();new Thread(new Runnable() {@Overridepublic void run() {mTvTest.setText("子線程可以更新UI");}}).start();}private void initView() {mTvTest = (TextView) findViewById(R.id.tv_test);} }代碼也很簡單,我開啟子線程,在子線程中,將 TextView 內(nèi)容設(shè)置為“子線程可以更新UI”,而在布局文件中,TextView 的 text 為“Hello world!”,那么現(xiàn)在運(yùn)行程序,可能會(huì)出現(xiàn)的結(jié)果有三種:
- 程序崩了,拋異常了:說明子線程不能更新UI
- 程序正常運(yùn)行,textview 上面顯示“Hello World!”:說明子線程不能更新UI
- 程序正常運(yùn)行,textview 上面顯示“子線程可以更新UI”:說明子線程可以更新UI
運(yùn)行程序,結(jié)果如下:
這說明什么?從結(jié)果看,子線程更新UI成功了。真的是這樣嗎?我自己也不相信,趕緊再驗(yàn)證一遍。這次我在布局文件中添加一個(gè)Button,修改后的布局文件如下:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:paddingBottom="@dimen/activity_vertical_margin"android:paddingLeft="@dimen/activity_horizontal_margin"android:paddingRight="@dimen/activity_horizontal_margin"android:paddingTop="@dimen/activity_vertical_margin"tools:context="com.example.joy.messagetest.MainActivity"><TextViewandroid:id="@+id/tv_test"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerHorizontal="true"android:text="Hello World!" /><Buttonandroid:id="@+id/btn_test1"android:layout_below="@id/tv_test"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerHorizontal="true"android:text="子線程更新UI測試"/></RelativeLayout>同時(shí)修改java代碼:
package com.example.joy.messagetest;import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.TextView;public class MainActivity extends AppCompatActivity implements View.OnClickListener {private TextView mTvTest;private Button mBtnTest1;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);initView();new Thread(new Runnable() {@Overridepublic void run() {mTvTest.setText("子線程可以更新UI");}}).start();}private void initView() {mTvTest = (TextView) findViewById(R.id.tv_test);mBtnTest1 = (Button) findViewById(R.id.btn_test1);mBtnTest1.setOnClickListener(this);}@Overridepublic void onClick(View v) {switch(v.getId()){case R.id.btn_test1:new Thread(new Runnable() {@Overridepublic void run() {mTvTest.setText("子線程真的可以更新UI嗎?");}}).start();break;default:break;}} }我們增加了一個(gè)button,點(diǎn)擊button,啟動(dòng)一個(gè)子線程,在子線程中將 textview 的顯示內(nèi)容改為 “子線程真的可以更新UI嗎?”。同樣按照前面的分析,我們再來驗(yàn)證一下。重新運(yùn)行程序, textview 顯示 “子線程可以更新UI”, 然后我們點(diǎn)擊 button。結(jié)果如下:
怎么回事?程序崩了。仔細(xì)看,你會(huì)發(fā)現(xiàn),點(diǎn)擊 button 后 textview 的內(nèi)容其實(shí)是發(fā)生了更改的,然后程序崩潰了。查看日志,拋出如下異常:
AndroidRuntime: FATAL EXCEPTION: Thread-176Process: com.example.joy.messagetest, PID: 11201android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6357)at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:874)這次終于看到了熟悉的錯(cuò)誤日志,只有初始創(chuàng)建視圖的線程才能觸碰這些視圖,也就是說只有主線程才能更新UI。通過下面一行
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6357)我們能發(fā)現(xiàn)點(diǎn)端倪:在 framework/base/core/java/android/view/ViewRootImpl.java 中有一個(gè)方法 checkThread ,源碼如下:
void checkThread() {if (mThread != Thread.currentThread()) {throw new CalledFromWrongThreadException("Only the original thread that created a view hierarchy can touch its views.");} }該異常就是在這里觸發(fā)的。對于這個(gè)問題,如果你還想深入下去探究清楚,可以跟進(jìn)去 RTFSC ! 這里推薦一篇文章,Android中子線程真的不能更新UI嗎?
說了這么多,其實(shí)子線程是不能直接更新UI的。Android實(shí)現(xiàn)View更新有兩組方法,分別是invalidate和postInvalidate。前者在UI線程中使用,后者在非UI線程即子線程中使用。換句話說,在子線程調(diào)用 invalidate 方法會(huì)導(dǎo)致線程不安全。熟悉View工作原理的人都知道,invalidate 方法會(huì)通知 view 立即重繪,刷新界面。作一個(gè)假設(shè),現(xiàn)在我用 invalidate 在子線程中刷新界面,同時(shí)UI線程也在用 invalidate 刷新界面,這樣會(huì)不會(huì)導(dǎo)致界面的刷新不能同步?這就是invalidate不能在子線程中使用的原因。
但是我們可以在子線程執(zhí)行某段代碼,需要更新UI的時(shí)候去通知主線程,讓主線程來更新。如何做呢?常見的方法,除了前面提到的在UI線程創(chuàng)建Handler,在子線程發(fā)送消息到UI線程,通知UI線程更新UI,還有 handler.post(Runnable r)、 view.post(Runnable r)、activity.runOnUIThread(Runnable r)等方法。跟進(jìn)去看源碼,發(fā)現(xiàn)其實(shí)它們的實(shí)現(xiàn)原理都還是一樣,最終都是通過Handler發(fā)送消息來實(shí)現(xiàn)的。下面分別用這幾種方法實(shí)現(xiàn)一下在子線程更新UI。
修改后的布局文件代碼如下:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:paddingBottom="@dimen/activity_vertical_margin"android:paddingLeft="@dimen/activity_horizontal_margin"android:paddingRight="@dimen/activity_horizontal_margin"android:paddingTop="@dimen/activity_vertical_margin"tools:context="com.example.joy.messagetest.MainActivity"><TextViewandroid:id="@+id/tv_test"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerHorizontal="true"android:text="Hello World!" /><Buttonandroid:id="@+id/btn_test1"android:layout_below="@id/tv_test"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerHorizontal="true"android:text="子線程更新UI測試"/><Buttonandroid:id="@+id/btn_test2"android:layout_below="@id/btn_test1"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerHorizontal="true"android:text="Handler發(fā)送消息"/><Buttonandroid:id="@+id/btn_test3"android:layout_below="@id/btn_test2"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerHorizontal="true"android:text="Handler.Post"/><Buttonandroid:id="@+id/btn_test4"android:layout_below="@id/btn_test3"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerHorizontal="true"android:text="View.Post"/><Buttonandroid:id="@+id/btn_test5"android:layout_below="@id/btn_test4"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerHorizontal="true"android:text="Activity.RunOnUIThread"/></RelativeLayout>? java代碼如下:
package com.example.joy.messagetest;import android.os.AsyncTask; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.TextView; import android.widget.Toast;public class MainActivity extends AppCompatActivity implements View.OnClickListener {private TextView mTvTest;private Button mBtnTest1;private Button mBtnTest2;private Button mBtnTest3;private Button mBtnTest4;private Button mBtnTest5;private Handler mHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);if (msg.what == 100) {mTvTest.setText("由Handler發(fā)送消息");}}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);initView();new Thread(new Runnable() {@Overridepublic void run() {mTvTest.setText("子線程可以更新UI");}}).start();}private void initView() {mTvTest = (TextView) findViewById(R.id.tv_test);mBtnTest1 = (Button) findViewById(R.id.btn_test1);mBtnTest2 = (Button) findViewById(R.id.btn_test2);mBtnTest3 = (Button) findViewById(R.id.btn_test3);mBtnTest4 = (Button) findViewById(R.id.btn_test4);mBtnTest5 = (Button) findViewById(R.id.btn_test5);mBtnTest1.setOnClickListener(this);mBtnTest2.setOnClickListener(this);mBtnTest2.setOnClickListener(this);mBtnTest3.setOnClickListener(this);mBtnTest4.setOnClickListener(this);mBtnTest5.setOnClickListener(this);}@Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.btn_test1:new Thread(new Runnable() {@Overridepublic void run() {mTvTest.setText("子線程真的可以更新UI嗎?");}}).start();break;case R.id.btn_test2: //通過發(fā)送消息new Thread(new Runnable() {@Overridepublic void run() {mHandler.sendEmptyMessage(100);}}).start();break;case R.id.btn_test3: //通過Handler.post方法new Thread(new Runnable() {@Overridepublic void run() {mHandler.post(new Runnable() {@Overridepublic void run() {mTvTest.setText("handler.post");}});}}).start();break;case R.id.btn_test4: //通過 view.post方法new Thread(new Runnable() {@Overridepublic void run() {mTvTest.post(new Runnable() {@Overridepublic void run() {mTvTest.setText("view.post");}});}}).start();break;case R.id.btn_test5: //通過 activity 的 runOnUiThread方法new Thread(new Runnable() {@Overridepublic void run() {runOnUiThread(new Runnable() {@Overridepublic void run() {mTvTest.setText("runOnUIThread");}});}}).start();break;default:break;}} }運(yùn)行一下效果如下圖:
以上就是消息機(jī)制最常見的應(yīng)用場景——在子線程通知主線程更新UI的幾種用法。
?
?
?
作者:SharpCJ
出處:https://www.cnblogs.com/joy99/p/6121280.html
總結(jié)
以上是生活随笔為你收集整理的Android子线程更新UI的方法总结的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: PartitionMotionSearc
- 下一篇: Android多线程之同步锁的使用