Kotlin入門(30)多線程交互

来源:https://www.cnblogs.com/aqi00/archive/2018/10/29/9873633.html
-Advertisement-
Play Games

Android開發時常會遇到一些耗時的業務場景,比如後臺批量處理數據、訪問後端伺服器介面等等,此時為了保證界面交互的及時響應,必須通過線程單獨運行這些耗時任務。簡單的線程可使用Thread類來啟動,無論Java還是Kotlin都一樣,該方式首先要聲明一個自定義線程類,對應的Java代碼如下所示: 自 ...


Android開發時常會遇到一些耗時的業務場景,比如後臺批量處理數據、訪問後端伺服器介面等等,此時為了保證界面交互的及時響應,必須通過線程單獨運行這些耗時任務。簡單的線程可使用Thread類來啟動,無論Java還是Kotlin都一樣,該方式首先要聲明一個自定義線程類,對應的Java代碼如下所示:

    private class PlayThread extends Thread {
        @Override
        public void run() {
            //此處省略具體的線程內部代碼
        }
    }

 

自定義線程的Kotlin代碼與Java大同小異,具體見下:

    private inner class PlayThread : Thread() {
        override fun run() {
            //此處省略具體的線程內部代碼
        }
    }

 

線程類聲明完畢,接著要啟動線程處理任務,在Java中調用一行代碼“new PlayThread().start();”即可,至於Kotlin則更簡單了,只要“PlayThread().start()”就行。如此看來,Java的線程處理代碼跟Kotlin差不了多少,沒發覺Kotlin比Java有什麼優勢。倘使這樣,真是小瞧了Kotlin,它身懷多項絕技,單單是匿名函數這招,之前在介紹任務Runnabe時便領教過了,線程Thread同樣也能運用匿名函數化繁為簡。註意到自定義線程類均需由Thread派生而來,然後必須且僅需重寫run方法,所以像類繼承、函數重載這些代碼都是走過場,完全沒必要每次都依樣畫葫蘆,編譯器真正關心的是run方法內部的具體代碼。於是,藉助於匿名函數,Kotlin的線程執行代碼可以簡寫成下麵這般:

    Thread {
        //此處省略具體的線程內部代碼
    }.start()

 

以上代碼段看似無理,實則有規,不但指明這是個線程,而且命令啟動該線程,可謂是簡潔明瞭。

線程代碼在運行過程中,通常還要根據實際情況來更新界面,以達到動態刷新的效果。可是Android規定了只有主線程才能操作界面控制項,分線程是無法直接調用控制項對象的,只能通過Android提供的處理器Handler才能間接操縱控制項。這意味著,要想讓分線程持續刷新界面,仍需完成傳統Android開發的下麵幾項工作:
1、聲明一個自定義的處理器類Handler,並重寫該類的handleMessage方法,根據不同的消息類型進行相應的控制項操作;
2、線程內部針對各種運行狀況,調用處理器對象的sendEmptyMessage或者sendMessage方法,發送事先約定好的消息類型;
舉個具體的業務例子,現在有一個新聞版塊,每隔兩秒在界面上滾動播報新聞,其中便聯合運用了線程和處理器,先由線程根據情況發出消息指令,再由處理器按照消息指令輪播新聞。詳細的業務代碼示例如下:

class MessageActivity : AppCompatActivity() {
    private var bPlay = false
    private val BEGIN = 0 //開始播放新聞
    private val SCROLL = 1 //持續滾動新聞
    private val END = 2 //結束播放新聞
    private val news = arrayOf("北斗三號衛星發射成功,定位精度媲美GPS", "美國賭城拉斯維加斯發生重大槍擊事件", "日本在越南承建的跨海大橋未建完已下沉", "南水北調功在當代,近億人喝上長江水", "德國外長要求中國尊重“一個歐洲”政策")

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_message)
        tv_message.gravity = Gravity.LEFT or Gravity.BOTTOM
        tv_message.setLines(8)
        tv_message.maxLines = 8
        tv_message.movementMethod = ScrollingMovementMethod()
        btn_start_message.setOnClickListener {
            if (!bPlay) {
                bPlay = true
                //線程第一種寫法的調用方式,通過具體的線程類進行構造。
                //註意每個線程實例只能啟動一次,不能重覆啟動。
                //若要多次執行該線程的任務,則需每次都構造新的線程實例。
                //PlayThread().start()
                //線程的第二種寫法,採用匿名類的形式。第二種寫法無需顯式構造
                Thread {
                    //發送“開始播放新聞”的消息類型
                    handler.sendEmptyMessage(BEGIN)
                    while (bPlay) {
                        //休眠兩秒,模擬獲取突發新聞的網路延遲
                        Thread.sleep(2000)
                        val message = Message.obtain()
                        message.what = SCROLL
                        message.obj = news[(Math.random() * 30 % 5).toInt()]
                        //發送“持續滾動新聞”的消息類型
                        handler.sendMessage(message)
                    }
                    bPlay = true
                    Thread.sleep(2000)
                    //發送“結束播放新聞”的消息類型
                    handler.sendEmptyMessage(END)
                    bPlay = false
                }.start()
            }
        }
        btn_stop_message.setOnClickListener { bPlay = false }
    }

    //自定義的處理器類,區分三種消息類型,給tv_message顯示不同的文本內容
    private val handler = object : Handler() {
        override fun handleMessage(msg: Message) {
            val desc = tv_message.text.toString()
            tv_message.text = when (msg.what) {
                BEGIN -> "$desc\n${DateUtil.nowTime} 下麵開始播放新聞"
                SCROLL -> "$desc\n${DateUtil.nowTime} ${msg.obj}"
                else -> "$desc\n${DateUtil.nowTime} 新聞播放結束,謝謝觀看"
            }
        }
    }

}

通過線程加上處理器固然可以實現滾動播放的功能,可是想必大家也看到了,這種交互方式依舊很突兀,還有好幾個難以剋服的缺點:

1、自定義的處理器仍然存在類繼承和函數重載的冗餘寫法;
2、每次操作界面都得經過發送消息、接收消息兩道工序,繁瑣且拖沓;
3、線程和處理器均需在指定的Activity代碼中聲明,無法在別處重用;
有鑒於此,Android早已提供了非同步任務AsyncTask這個模版類,專門用於耗時任務的分線程處理。然而AsyncTask的用法著實不簡單,首先它是個模板類,初學者瞅著模板就發慌;其次它區分了好幾種運行狀態,包括未運行、正在運行、取消運行、運行結束等等,一堆的概念叫人頭痛;再次為了各種狀況都能與界面交互,又得定義事件監聽器及其事件處理方法;末了還得在Activity代碼中實現監聽器的相應方法,才能正常調用定義好的AsyncTask類。
初步看了下自定義AsyncTask要做的事情,直讓人倒吸一口冷氣,看起來很高深的樣子,確實每個Android開發者剛接觸AsyncTask之時都費了不少腦細胞。為了說明AsyncTask是多麼的與眾不同,下麵來個非同步載入書籍任務的完整Java代碼,溫習一下那些年虐過開發者的AsyncTask:

//模板類的第一個參數表示外部調用execute方法的輸入參數類型,第二個參數表示運行過程中與界面交互的數據類型,第三個參數表示運行結束後返回的輸出參數類型
public class ProgressAsyncTask extends AsyncTask<String, Integer, String> {
    private String mBook;
    //構造函數,初始化數據
    public ProgressAsyncTask(String title) {
        super();
        mBook = title;
    }

    //在後臺運行的任務代碼,註意此處不可與界面交互
    @Override
    protected String doInBackground(String... params) {
        int ratio = 0;
        for (; ratio <= 100; ratio += 5) {
            // 睡眠200毫秒模擬網路通信處理
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //刷新進度,該函數會觸發調用onProgressUpdate方法
            publishProgress(ratio);
        }
        return params[0];
    }

    //在任務開始前調用,即先於doInBackground執行
    @Override
    protected void onPreExecute() {
        mListener.onBegin(mBook);
    }

    //刷新進度時調用,由publishProgress函數觸發
    @Override
    protected void onProgressUpdate(Integer... values) {
        mListener.onUpdate(mBook, values[0], 0);
    }

    //在任務結束後調用,即後於doInBackground執行
    @Override
    protected void onPostExecute(String result) {
        mListener.onFinish(result);
    }

    //在任務取消時調用
    @Override
    protected void onCancelled(String result) {
        mListener.onCancel(result);
    }

    //聲明監聽器對象
    private OnProgressListener mListener;
    public void setOnProgressListener(OnProgressListener listener) {
        mListener = listener;
    }

    //定義該任務的事件監聽器及其事件處理方法
    public static interface OnProgressListener {
        public abstract void onFinish(String result);
        public abstract void onCancel(String result);
        public abstract void onUpdate(String request, int progress, int sub_progress);
        public abstract void onBegin(String request);
    }

}

見識過了AsyncTask的驚濤駭浪,不禁喟嘆開發者的心靈有多麼地強大。多線程是如此的令人望而卻步,直到Kotlin與Anko的搭檔出現,因為它倆線上程方面帶來了革命性的思維,即編程理應是面向產品,而非面向機器。對於分線程與界面之間的交互問題,它倆給出了堪稱完美的解決方案,所有的線程處理邏輯都被歸結為兩點:其一是如何標識這種牽涉界面交互的分線程,該點由關鍵字“doAsync”闡明;其二是如何在分線程中傳遞消息給主線程,該點由關鍵字“uiThread”界定。有了這兩個關鍵字,分線程的編碼異乎尋常地簡單,即使加上Activity的響應代碼也只有以下寥寥數行:

    //圓圈進度對話框
    private fun dialogCircle(book: String) {
        dialog = indeterminateProgressDialog("${book}頁面載入中……", "稍等")
        doAsync {
            // 睡眠200毫秒模擬網路通信處理
            for (ratio in 0..20) Thread.sleep(200)
            //處理完成,回到主線程在界面上顯示書籍載入結果
            uiThread { finishLoad(book) }
        }
    }

    private fun finishLoad(book: String) {
        tv_async.text = "您要閱讀的《$book》已經載入完畢"
        if (dialog.isShowing) dialog.dismiss()
    }

以上代碼被doAsync括弧圈起來的代碼段,就是分線程要執行的全部代碼;至於uiThread括弧圈起來的代碼,則為通知主線程要完成的工作。倘若在分線程運行過程中,要不斷刷新當前進度,也只需在待刷新的地方添加一行uiThread便成,下麵是添加了進度刷新的代碼例子:

    //長條進度對話框
    private fun dialogBar(book: String) {
        dialog = progressDialog("${book}頁面載入中……", "稍等")
        doAsync {
            for (ratio in 0..20) {
                Thread.sleep(200)
                //處理過程中,實時通知主線程當前的處理進度
                uiThread { dialog.progress = ratio*100/20 }
            }
            uiThread { finishLoad(book) }
        }
    }

  


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一. 概述 在上一篇中,搭建了一主一從的複製架構,這篇通過一些診斷方法來瞭解複製的運行狀態和一些選項參數說明。上次mysql主從服務關機,今天在打開mysql服務,出現了錯誤信息。 1.首先 啟動主從mysql服務 2.在從庫上執行START SLAVE, 開始複製。 3.在從庫上執行SHOW PR ...
  • 註意:本地MySQL服務要開啟 更新整個資料庫 1、將正式伺服器上的資料庫做備份 登錄到正式伺服器,執行如下命令:(註意空格) mysqldump -uroot –p密碼 資料庫名 -P 介面 --default-character-set=gbk --opt -Q -R --skip-lock-t ...
  • 介紹 這篇隨筆主要介紹MySQL的基礎API的使用姿勢 基本使用姿勢: 第一步:登陸資料庫 第二步:建立MySqlCommand對象然後進行增刪改查 建立MySqlDataReader對象-讀取查詢結果 使用: 註釋:(為了放置SQL註入,可以使用參數的方式進行傳參,執行上沒有差別) SQL可以查詢 ...
  • 前言 只有光頭才能變強 最近在學Redis,我相信只要是接觸過Java開發的都會聽過Redis這麼一個技術。面試也是非常高頻的一個知識點,之前一直都是處於瞭解階段。秋招過後這段時間是沒有什麼壓力的,所以打算系統學學Redis,這也算是我從零學習Redis的筆記吧。 本文 力求講清每個知識點 ,希望大 ...
  • Storm學習筆記 消息容錯機制 文章來自「隨筆」 http://jsynk.cn/blog/articles/153.html 1. Storm消息容錯機制概念 一個提供了可靠的處理機制的spout需要記錄自己emit(發射)的tuple(消息元祖),當下游bolt處理tuple或者子tuple失 ...
  • 目錄 簡介 安裝啟動 許可權 事務 臟讀、不可重覆讀、幻讀 MVCC 複製 非同步複製 半同步複製 GTID複製 集群(Galera) 配置 監控(Zabbix) 簡介 環境: CentOS 7.4.1708 MariaDB 10.3.9 簡介: MySQL 由 MySQLAB 公司開發。 MariaD ...
  • 零基礎的同學學習大數據開發不能急於求成,要分階段分步驟來一步步完成,科多大數據給大家來分享一下大數據的學習路線是什麼?小白該怎麼學習大數據呢,大概可以分為四步:第一個階段:瞭解大數據的基本概念 首先,學習一門課程的時候,要對這門課程有一個簡單的瞭解,比如說,要先學習這門課程的 一些專業的術語,學習一 ...
  • 修改視圖註意事項 修改先前創建的視圖。 其中包括索引視圖。 ALTER VIEW不影響相關的存儲過程或觸發器,並且不會更改許可權。 如果原來的視圖定義是使用 WITH ENCRYPTION 或 CHECK OPTION創建的,則只有在 ALTER VIEW 中也包含這些選項時,才會啟用這些選項。 如果 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...