1. 程式人生 > >Android開發——實現子執行緒更新UI

Android開發——實現子執行緒更新UI

Android中執行緒按功能分的話,可以分為兩個,一個是主執行緒(UI執行緒),其他的都是子執行緒

主執行緒不能執行那些耗時過長的程式碼或任務(執行耗時過長的程式碼會出現應用未響應的提示),所以都是使用子執行緒來執行耗時過長的程式碼,比如說下載檔案等任務

一般情況,子執行緒中執行過長的程式碼,都是需要進行更新UI操作。

但是Android中,為了防止安全,是不允許在子執行緒更新UI的,但是我們可以使用到Android官方給予的API來實現子執行緒更新UI的操作(本質上,這些API也是切換回了主執行緒來進行更新UI)

例子:點選一個按鈕,過了1s後完成了下載任務,返回了資料,此資料會顯示在介面上

具體解釋:

點選按鈕,之後開啟一個子執行緒來模擬下載過程(執行緒休眠1s),之後任務執行完畢會返回資料(一個String),使用返回的資料更新UI

新建一個方法,用來模擬下載任務

/**
 * 模擬下載
 */
fun download(): String {
    Thread.sleep(1000)
    return "this is data"
}

下面的使用6種方式和上面的模擬下載任務的方法,來實現上面例子的效果

1.Activity.runOnUiThread()

runOnUiThread是Activity中的方法,只有當前物件是Activity,就可以直接使用,如果當前的物件不是Activity,需要找到Activity物件,才能執行此方法

runOnUiThread方法的引數為一個Runnable介面,我使用的kotlin,所以有很多東西都是省略了

設定按鈕的點選事件,點選按鈕開啟一個執行緒

btn_start.setOnClickListener {
    thread {
        val data = download()
        runOnUiThread({
            //這裡進行更新UI操作
            tv_show.text = data
        })
    }
}

Java版

btn_start.setOnClickListener(new OnClickListener(){
    new Thread(new Runnable(){
        String data = download();
        runOnUiThread(new Runnable(){
            @Override
            public void run() {
                tv_show.setText(data);
            }
        })
    }).start();
});

2.View.post()

post方法是View物件的方法,引數也是接收一個runnable介面

這裡我選用的view物件是需要進行更新textview的本身,當然也可以選用其他的View物件,只要是在當前Activity的物件都可以

btn_start.setOnClickListener {
    thread {
        val data = download()
        //選擇當前Activity的View物件都可以
        tv_show.post { 
            tv_show.text = data 
        }
    }
}

3.View.PostDelayed()

此方法和上面的post方法類似,只是多一個引數,用來實現延遲更新UI的效果

btn_start.setOnClickListener {
    thread {
        val data = download()
        tv_show.postDelayed({
            tv_show.text = data
        },2000)
    }
}

上面的程式碼實現的效果是點選按鈕之後,過了3s後才會看到介面發生改變

4.Handler.post()

new一個Handler物件(全域性變數)

private val handler = Handler()

使用post方法更新UI,此post方法和之前的post方法一樣,引數都是為Runnable介面

btn_start.setOnClickListener {
    thread {
        val data = download()
        handler.post {
            tv_show.text = data
        }
    }
}

5.AsyncTask(推薦)

說明

AsyncTask是一個抽象類,必須建立一個子類類繼承它

這裡介紹一下關於AsyncTask的三個泛型引數和幾個方法

泛型引數可以為任意型別,為空的話使用Void

引數 說明
params 引數泛型,doInBackground方法的引數
progress 進度泛型,onProgressUpdate方法的引數
result 結果泛型,onPostExecute方法的引數

抽象方法說明:

方法名 說明
onPreExectute() 此方法中,常常進行初始化操作,如進度條顯示
doInBackground(Params...) 此方法必須實現,
onProgressUpdate(Progress...) 進行更新UI的操作
publishProgress(Progress...) 在doInBackground方法中呼叫,呼叫此方法後會回撥執行onProgressUpdate方法進行更新UI
onPostExcute(Result) 任務結束之後進行更新UI

簡單來說,如果子類繼承了AsyncTask,它的抽象方法的引數都會變成泛型對應的型別

例子

下面的程式碼是取自我的APP,簡單地說明一下AsyncTask<String, DownloadingItem, DownloadedItem>

我傳入的是3個泛型引數分別為StringDownloadingItemDownloadedItem,分別對應的paramsprogressresult泛型

這裡我是根據自己的需要而兩個類DownloadingItemDownloadedItem,從下面的程式碼可以看到,抽象方法的引數變為了我們的泛型的型別

internal inner class DownloadingTask : AsyncTask<String, DownloadingItem, DownloadedItem>() {

    override fun onPreExecute() {
        //一些初始化操作
    }

    override fun doInBackground(vararg params: String?): DownloadedItem {
        //params是一個引數陣列,如果建立DownloadingTask物件只傳入了一個引數,直接取下標為0的那個即可(需要轉型)
        //耗時操作(如下載操作),獲得進度資料
        
        //將新的進度資料傳遞到onProgressUpdate方法,更新UI
        publishProgress(messageItem)
        
        //任務執行完畢,返回結果(回撥onPostExecute方法)
    }

    override fun onProgressUpdate(vararg values: DownloadingItem?) {
        //這裡使用最新的進度來進行相關UI的更新
        //values是一個DownloadingItem陣列,取末尾那個即為最新的進度資料
    }

    override fun onPostExecute(result: DownloadedItem?) {
        //下載成功提示或者是其他更新UI的操作
    }
}

執行:

執行Task的時候需要在主執行緒(UI執行緒呼叫)

DownloadingTask().execute("引數")

批量下載:

//允許在同一時刻有5個任務正在執行,並且最多能夠儲存50個任務
private val exec = ThreadPoolExecutor(5, 50, 10, TimeUnit.SECONDS, LinkedBlockingQueue<Runnable>())
DownloadingTask().executeOnExecutor(exec, url)

6.Handler機制實現(核心)

其實,Handler機制是子程序更新UI的核心

我們上面的五種實現子程序更新UI的方式,都是基於Handler機制實現的

具體機制本文就不多說了,網上有許多的機制說明,這裡就只講一下實現的步驟

Message中有幾個屬性,whatarg1arg2,這三個都是接收一個Int,所以,傳遞資料不是很友好,這裡就不準備實現之前的例子效果了

what表示來源,arg1和arg2用來傳遞Int資料

1.重寫Handler類中的handleMessage方法

一般都是規定好一個Int的常量,來表示what

private val handler =object : Handler(){
    override fun handleMessage(msg: Message?) {
        if (msg.what == 1) {
            //來源為1,則
        }
    }
}

2.傳送Message

val msg = handler.obtainMessage()
//一般都是規定好一個Int的常量,來表示what
msg.what = 1
//傳遞Int資料
msg.arg1 = 20
handler.sendMessage(msg)