1. 程式人生 > >Kotlin入門(32)網路介面訪問

Kotlin入門(32)網路介面訪問

手機上的資源畢竟有限,為了獲取更豐富的資訊,就得到遼闊的網際網路大海上衝浪。對於App自身,也要經常與伺服器互動,以便獲取最新的資料顯示到介面上。這個客戶端與服務端之間的資訊互動,基本使用HTTP協議進行通訊,即App訪問伺服器的HTTP介面來傳輸資料。HTTP介面呼叫在Java程式碼中可不是一個輕鬆的活,開發者若用最基礎的HttpURLConnection來編碼的話,至少要考慮以下場景的處理:
1、HTTP的請求方式是什麼,是GET還是POST還是PUT還是DELETE?
2、HTTP的連線超時時間是多少,請求應答的超時時間又是多少?
3、HTTP頭部的語言和瀏覽器資訊該設定為什麼?
4、HTTP傳輸的資料內容採取的是哪種編碼方式?
5、HTTP的應答資料如果是壓縮過的,又要如何解壓?
6、HTTP的輸入輸出流需要注意哪些方面?
7、HTTP如何分塊傳輸較大的資料資訊?
瞧瞧上面層出不窮的功能要求,如果開發者事必躬親逐個編碼,那可真是要累得夠嗆。因此,各種意圖取代HttpURLConnection的網路互動框架如雨後春筍般湧現出來,既有老資格的如HttpClient,又有後起之秀如Android-Async-Http、Volley、OkHttp、Retrofit等等,可謂是百花齊放、百家爭鳴。當然,這些網路框架是需要學習成本的,使用起來也不如想象中的那麼容易;它們只是在技術上各有千秋,並非終極的解決方案,往往是你方唱罷我登臺,各領風騷幾年然後歇菜。
其實HTTP互動原本無需這樣大動干戈,常見的介面呼叫僅僅是App往伺服器傳送一串請求資訊,然後伺服器返回給App一串處理結果,這種簡單的業務場景已經足夠應付大多數App的網路通訊需求。所以大道至簡,Kotlin把網路互動看作是跟檔案讀寫一樣的I/O操作,後端地址就像是個檔案路徑,那麼請求伺服器的資料猶如讀取檔案內容。文字分為文字檔案和二進位制檔案兩種,則HTTP介面對應獲取文字資料和獲取二進位制資料兩種,於是整個網路請求便簡化為資料的存跟取了。
具體到詳細的Kotlin編碼,檔案物件由“File(檔案路徑)”構建,而HTTP物件由“URL(網路地址)”構建,獲取介面資料則有readText和readBytes兩個方法,前者用於獲取文字形式的應答資料,後者用於二進位制形式的應答資料如圖片檔案、音訊檔案等等。僅僅一個readText方法真的能完成繁雜的HTTP介面呼叫操作嗎?下面我們通過一個具體的介面訪問案例,探討一下如何使用Kotlin程式碼實現HTTP介面呼叫。
智慧手機普遍提供了定位功能,可是系統自帶的定位服務只能獲得使用者所在的經緯度資訊,而這枯燥的經緯度數字令人不知所云,肯定要把經緯度轉換為詳細的地址資訊才方便使用者理解。將經緯度轉換為詳細地址,就要訪問谷歌地圖提供的地址查詢介面了,該介面的地址形如“http://maps.google.cn/maps/api/geocode/json?請求引數資訊”,App把經緯度資料作文請求引數傳入,對方會返回一個包含地址資訊的json串,通過解析json串即可獲得當前的詳細地址。由於訪問網路需要在分執行緒進行,因此介面訪問程式碼必須放在doAsync程式碼塊中,下面給出根據經緯度獲取詳細地址的Kotlin程式碼片段:

    private val mapsUrl = "http://maps.google.cn/maps/api/geocode/json?latlng={0},{1}&sensor=true&language=zh-CN"
    
    //位置監聽器偵聽到定位變化事件,就呼叫該函式請求詳細地址
    private fun setLocationText(location: Location?) {
        if (location != null) {
            doAsync {
                //根據經緯度資料從谷歌地圖獲取詳細地址資訊
                val url = MessageFormat.format(mapsUrl, location.latitude, location.longitude)
                val text = URL(url).readText()
                val obj = JSONObject(text)
                val resultArray = obj.getJSONArray("results")
                var address = ""
                //解析json字串,其中formatted_address欄位為具體地址名稱
                if (resultArray.length() > 0) {
                    val resultObj = resultArray.getJSONObject(0)
                    address = resultObj.getString("formatted_address")
                }
                //獲得該地點的詳細地址之後,回到主執行緒把地址顯示在介面上
                uiThread { findAddress(location, address) }
            }
        } else {
            tv_location.text = "$mLocation\n暫未獲取到定位物件"
        }
    }

    //在主執行緒中把定位資訊連同地址資訊都列印到介面上
    private fun findAddress(location: Location, address: String) {
        tv_location.text = "$mLocation\n定位物件資訊如下: " +
                "\n\t時間:${DateUtil.nowDateTime}" +
                "\n\t經度:${location.longitude},緯度:${location.latitude}" +
                "\n\t高度:${location.altitude}米,精度:${location.accuracy}米" +
                "\n\t地址:$address"
    }

上述程式碼看起來顯然簡明扼要,寥寥數行便搞定了完整的功能實現。如果使用Java程式碼實現該功能,首先HTTP呼叫就得提供底層的介面訪問程式碼,其次分執行緒請求網路又得專門寫個繼承自AsyncTask的任務處理程式碼,末了Activity這邊廂還得實現該任務的完成事件,真是興師動眾、勞民傷財。由此可見Kotlin的網路互動是革命性的,方式雖然簡單,卻足以應付大部分的網路通訊需求,並且執行效果與Java程式碼幾無差別,例如呼叫地圖介面查詢地址資訊,無論採用Java編碼還是Kotlin編碼,介面效果都如下圖所示。

上面利用readText方法就完成了文字資料的介面呼叫,當時提到了readBytes可用於獲取二進位制資料如圖片檔案,那麼獲取網路圖片是否也同樣方便呢?下面我們繼續探討如何使用Kotlin程式碼讀取網路圖片。
獲取網路圖片的基本流程同文本格式的介面訪問,一樣先通過URL類構建HTTP物件,然後在doAsync程式碼塊中呼叫HTTP物件的readBytes方法獲得圖片的位元組陣列。將位元組陣列轉換為點陣圖物件,這在前面的文章《Kotlin入門(27)檔案讀寫操作》已經加以介紹,即利用BitmapFactory工具的decodeByteArray方法實現轉換操作。轉換好的點陣圖當然可以在主執行緒直接顯示出來,也可以先儲存為圖片檔案,等到需要的時候再去讀取。前面描述如何把點陣圖儲存為圖片檔案時,由於Bitmap相關類並未提供簡單的圖片儲存方法,因此當時儲存點陣圖檔案還著實頗費了一番功夫。現在儲存網路圖片反而無需如此折騰,這是因為獲取網路圖片得到了位元組陣列,位元組陣列儲存為檔案可是相當方便的噢,只要呼叫File物件的writeBytes方法,短短一行就儲存好圖片了。介紹完了網路圖片的存取流程,最終的Kotlin編碼一如既往地簡單明瞭,下面展示了一個驗證碼動態顯示的頁面程式碼:

class HttpImageActivity : AppCompatActivity() {
    private val imageUrl = "http://222.77.181.14/ValidateCode.aspx?r="

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_http_image)
        iv_image_code.setOnClickListener { getImageCode() }
        getImageCode()
    }

    //獲取網路上的圖片驗證碼
    private fun getImageCode() {
        iv_image_code.isEnabled = false
        doAsync {
            val url = "$imageUrl${DateUtil.getFormatTime()}"
            val bytes = URL(url).readBytes()
            //把位元組陣列解碼為點陣圖資料
            val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
            //也可通過下面三行程式碼把位元組陣列寫入檔案,即生成一個圖片檔案
            val path = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString() + "/"
            val file_path = "$path${DateUtil.getFormatTime()}.png"
            File(file_path).writeBytes(bytes)
            //獲得驗證碼圖片資料,回到主執行緒把驗證碼顯示在介面上
            uiThread { finishGet(bitmap) }
        }
    }

    //在主執行緒中顯示獲得到的驗證碼圖片
    private fun finishGet(bitmap: Bitmap) {
        iv_image_code.setImageBitmap(bitmap)
        iv_image_code.isEnabled = true
    }
}

看到了吧,即使是完整的Activity程式碼,Kotlin也只需數十行而已。倘若使用Java完成同樣的功能,除了HTTP底層與AsyncTask的編碼之外,還得補充Bitmap物件的圖片儲存程式碼。也就是說,Java程式碼需要額外新增三個工具類的實現程式碼,光光這一點,Kotlin的效率就令人讚歎。而且,短小精悍的Kotlin程式碼並未造成任何功能缺失,以上面的圖片驗證碼頁面為例,使用Java編碼和使用Kotlin編碼,最終的顯示效果都如下圖所示。