• <rp id="vcwyv"></rp>

      <b id="vcwyv"></b>
      <tt id="vcwyv"></tt>

    1. 最新安卓入門教程
      掃碼關注
      獲取更多技術干貨

      image

      作者論壇ID:陪你嘮嗑    

      https://github.com/zhouxu88

      http://www.mamashuozhangdahouwohuiyouhenduololi.cn/myspaceblog-927424.html

      版權聲明:本章節為安卓巴士博主原創文章,未經授權不得以任意形式轉載發布/發表,違者將依法追究責任


      9.1 初識Http協議

      前言

      在android app開發中,經常做的一個操作就是網絡訪問,具體來說,當你向用戶展示一個炫酷的頁面之前,通常需要先向服務器請求數據,然后拿到數據之后,解析數據,再更新到你的UI界面上;當用戶在你的app上做一些交互操作時,你需要把用戶操作完成后的變化的這部分數據提交給服務器,這是服務器一般回去數據庫中更新該用戶的一些數據等等。既然網絡的使用是如此的頻繁,也是如此的重要,那我們還是有必要去稍加了解一下網絡訪問的一些基礎原理。這篇文章就主要介紹一下http協議。

      Http概述

      • 概念 HTTP全稱Hypertext Transfer Protocol,即超文本傳輸協議,是基于TCP/IP的協議,在計算機網絡中屬于五層協議(從底層到上層依次是:物理層、數據聯絡層、網絡層、運輸層、應用層)中最上面的應用層。它定義了客戶端如何向服務器請求數據以及服務器端如何把數據傳輸給客戶端。

      • 特點

        1.支持C/S(客戶端/服務器端)架構。

        2.簡單快速:客戶端向服務器端請求服務時,只需傳送請求方法和路徑即可。因為HTTP協議簡單,使得HTTP服務器的程序規模小,因而通信速度很快。

        3.HTTP協議基于請求響應模型,一次請求對于一次響應,請求只能由客戶端發出,服務器只能被動的等待請求作出響應。

        4.無連接:無連接的含義是限制每次連接只處理一個請求。服務器處理完客戶的請求,并收到客戶的應答后,即斷開連接。采用這種方式可以節省傳輸時間。

        5.無狀態:HTTP協議是無狀態協議。無狀態是指協議對于事務處理沒有記憶能力。缺少狀態意味著如果后續處理需要前面的信息,則它必須重傳,這樣可能導致每次連接傳送的數據量增大。另一方面,在服務器不需要先前信息時它的應答就較快。

      • 工作流程

        一次完整的Http操作稱為一個事務,其工作流程大體上可以分為四步:

        1.客戶端與服務器建立連接

        2.連接成功后,客戶端通過一個URL地址向服務器發送請求

        3.服務器接收到請求后,給予相應的響應信息。

        4.客戶端接收到服務器返回的信息,并面向用戶作出相應的UI顯示,最后客戶端斷開連接,這樣一次http請求也就結束了。

      • URL詳解

        URL全稱Universal Resource Locator,即統一資源定位符。URL一般由三部組成:協議、主機域名或者IP地址(也包括端口號)、主機資源的具體地址。如目錄和文件名等。一般格式如下:

         schema://host:port/path/params
        
         scheme         指定低層使用的協議(例如:http, https, ftp)                                                           
         host                  HTTP服務器的域名IP地址
         port#                 HTTP服務器的默認端口是80,這種情況下端口號可以省略。如果使用了別的端口,
                               必須指明,例如 http://www.cnblogs.com:8080/
         path                  訪問資源或者具體文件目錄的路徑
         params            Url請求參數

      舉個例子

       http://www.dearxiaoyun.com.cn/app/index.php?r=goods.detail&id=
       http:表示協議
       www.dearxiaoyun.com.c:表示服務器域名,這里用的就是默認端口號
       index.php:表示訪問的具體文件路徑
       r、id:表示URL請求的參數
      • 請求方式

        客戶端和服務端通過HTTP協議來傳遞數據,這一部分是通過請求報文和響應報文的方式來實現的,這一部分可以在讀者熟練的掌握了網絡操作以后作為進階來自行學習,這里不展開細講。對于網絡請求,我們一般關心請求方式,常見的請求方式有:PUT、DELETE、POST、GET、OPTION、HEAD、TRACE、CONNECT。其中前四種對應增刪改查,而在android app開發當中,我們最常用的還是GET、POST請求。

        • OPTION:請求一些選項的信息
        • GET:根據URL地址,讀取服務器的數據
        • HEAD:請求讀取由URL所標志的信息的首部
        • POST:給服務器發送信息
        • PUT:在指明的URL下存儲一個文檔
        • DELETE:刪除指明的URL所標志的資源
        • TRACE:用來進行環回測試的請求報文
        • CONNECT:用于代理服務器
      • 響應狀態碼

        當發出一次網絡請求以后,一般會收到一個狀態碼來表示這次請求的結果,狀態代碼有三位數字組成,第一個數字定義了響應的類別,且有五種可能取值:

        100~199:指示信息,表示請求已接收,繼續處理
        200~299:請求成功,表示請求已被成功接收、理解、接受
        300~399:重定向,要完成請求必須進行更進一步的操作
        400~499:客戶端錯誤,請求有語法錯誤或請求無法實現
        500~599:服務器端錯誤,服務器未能實現合法的請求

      常見的狀態碼如下:

      200 OK:客戶端請求成功,服務器已成功處理了請求,并返回了信息。只有這個狀態碼才能確定請求成功。
      400 Bad Request:客戶端請求有語法錯誤,不能被服務器所理解
      401 Unauthorized:請求未經授權,這個狀態代碼必須和WWW-Authenticate報頭域一起使用
      403 Forbidden:服務器收到請求,但是拒絕提供服務
      404 Not Found: 服務器找不到請求的網頁,也就是說客戶端給的URL地址有誤。
      500 Internal Server Error:服務器發生不可預期的錯誤
      503 Server Unavailable:服務器當前不能處理客戶端的請求,一段時間后可能恢復正常

      寫在最后

      要想查看網頁或者手機請求網絡的請求報文和響應報文有很多種方法,這里推薦2個抓包工具:Fiddler,Charles(青花瓷),這個使用簡單,可自行查閱研究。

      9.2 Android基于HTTP協議網絡傳輸的實現

      概述

      在上一篇文章,我們已經講到了http協議的一些基礎概念以及客戶端和服務端的一個交互流程,而這一篇就來講講在android app開發中,是如何實現一個網絡請求的。雖然現在已經有了很多比較優秀的網絡訪問的開源庫,但是對于一個初學者而言,還是應當先去學習一下android原生的API的實現方式,因為這是官方推出的原生API,而且所有的開源庫也都是基于這些原生的API基礎上封裝而成的。Android原生API提供的網絡請求方式是通過HttpURLConnection或者HttpClient這2個類來實現的。由于在Android6.0版本Google官方直接刪除了HttpClient類庫,所以這里只講解HttpURLConnection。

      HttpURLConnection簡介:

      HttpURLConnection是一種多用途、輕量極的HTTP客戶端。它的API簡單,體積較小,因而非常適用于Android項目,壓縮和緩存機制可以有效地減少網絡訪問的流量,在提升速度和省電方面也起到了較大的作用,使用它來進行HTTP操作可以適用于大多數的應用程序。HttpUrlConnection是Android SDK的標準實現,直接支持系統級連接池,即打開的連接不會直接關閉,在一段時間內所有程序可共用;直接在系統層面做了緩存策略處理,加快重復請求的速度。

      HttpURLConnecetion的使用步驟

      GET請求

      (1)創建一個URL 對象,通過這個URL對象獲取到HttpURLConnection 的實例,創建連接。

      URL url = new URL("http://img1.3lian.com/2015/a1/43/d/82.jpg");
      // 打開一個HttpURLConnection連接
      HttpURLConnection connection = (HttpURLConnection) url.openConnection();

      (2)得到了HttpURLConnection 連接對象以后,可以設置一些關于連接的屬性,比如設置連接超時、讀取超時

      connection.setConnectTimeout(8 * 1000); //設置連接超時,單位毫秒
      connection.setReadTimeout(8 * 1000); //設置讀取超時,單位毫秒
      // 設置是否使用緩存  默認是true,而且只有Get請求才有效,Post是無效的
      connection.setUseCaches(true);
      // 設置為GET請求
      connection.setRequestMethod("GET");

      (3) 判斷相應碼

      //相應碼為200的時候才是連接成功,才能進行后續的獲取數據操作
      connection.getResponseCode()

      (4)獲取到服務器返回的輸入流,對I/O流的操作

      InputStream in = connection.getInputStream();
      //服務器返回的數據都在這個流里面了,接下來就是I/O流的讀寫操作了

      下載網絡圖片(Get請求)

      HttpUtils

          /**
           * 下載網絡圖片
           */
          fun getBitmap(imgUrl: String): Bitmap? {
              var bitmap: Bitmap? = null
              try {
                  //創建URL對象
                  val url = URL(imgUrl)
                  // 打開一個HttpURLConnection連接
                  val connection = url.openConnection() as HttpURLConnection
      
                  //設置連接相關的屬性
                  connection.readTimeout = 8 * 1000 //讀取數據超時,8s
                  connection.connectTimeout = 8 * 1000 //連接超時,8s
      
                  //判斷響應碼,200成功
                  var responseCode = 0
                  responseCode = connection.responseCode
                  if (responseCode == 200) {
                      //連接成功
                      //獲取服務器返回的輸入流
                      val inputStream = connection.inputStream
                      Log.i("tag", "downloadImg: " + inputStream)
                      bitmap = BitmapFactory.decodeStream(inputStream)
                      //關閉連接
                      connection.disconnect()
                  }
              } catch (e: IOException) {
                  e.printStackTrace()
              }
      
              return bitmap
      
          }

      Activity

      public class MainActivity extends AppCompatActivity {
      
          private static final String URL_IMG = "http://img1.3lian.com/2015/a1/43/d/82.jpg";
          private ImageView imageView;
      
      
          @Override
          protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_main);
      
              imageView = (ImageView) findViewById(R.id.imageView);
              findViewById(R.id.download_btn).setOnClickListener(new View.OnClickListener() {
                  @Override
                  public void onClick(View view) {
                      new DownloadImageTask().execute(URL_IMG);
                  }
              });
          }
      
      
      
          /**
           * 下載網絡圖片的異步任務
           */
          class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
      
              @Override
              protected Bitmap doInBackground(String... strings) {
                  return HttpUtils.getBitmap(strings[0]);
              }
      
              @Override
              protected void onPostExecute(Bitmap bitmap) {
                  super.onPostExecute(bitmap);
                  if (bitmap != null) {
                      //在主線程更新UI
                      imageView.setImageBitmap(bitmap);
                  }
              }
          }
      }

      別忘了加上網絡權限

      <!-- 連接網絡的權限 -->
      <uses-permission android:name="android.permission.INTERNET" />

      下載圖片

      登錄(Post請求)

      PostUtils

      object PostUtils {
          /**
           * 用戶名,密碼登錄(需要配置服務器信息)
           * @param loginUrl 登錄的URL
           * @param name 用戶名
           * @param pwd 密碼
           * @return 服務器返回的登錄結果
           */
          fun login(loginUrl: String, name: String, pwd: String): String {
      
              //登錄后,服務器返回的信息
              var result = ""
              try {
                  val url = URL(loginUrl)
                  //創建連接
                  val connection = url.openConnection() as HttpURLConnection
      
                  //設置請求方式為post請求
                  connection.requestMethod = "POST"
                  //設置連接,讀取超時
                  connection.readTimeout = 8 * 1000
                  connection.connectTimeout = 8 * 1000
                  //設置允許輸入,輸出
                  connection.doOutput = true
                  connection.doInput = true
                  //Post方式不能緩存,需手動設置為false
                  connection.useCaches = false
      
                  val data = "passwd=" + URLEncoder.encode(pwd, "UTF-8") +
                          "&number=" + URLEncoder.encode(name, "UTF-8")
                  //這里可以寫一些請求頭的東西
                   //connection.setRequestProperty("Connection","Keep-Alive");
                  //獲取輸出流
                  val out = connection.outputStream
                  out.write(data.toByteArray())
                  out.flush()
      
                  //判斷是否響應成功
                  if (connection.responseCode == 200) {
                      result = getStr(connection)
                  }
      
              } catch (e: IOException) {
                  e.printStackTrace()
              }
      
              return result
          }
      
          /**
           * 獲取字符串
           *
           * @param connection
           * @return
           * @throws IOException
           */
          @Throws(IOException::class)
          private fun getStr(connection: HttpURLConnection): String {
              // 獲取響應的輸入流對象
              val inputStream = connection.inputStream
              // 創建字節輸出流對象
              val bos = ByteArrayOutputStream()
              // 定義讀取的長度
              var len = 0
              // 定義緩沖區
              val buffer = ByteArray(1024)
              // 按照緩沖區的大小,循環讀取
              while ((len = inputStream.read(buffer)) != -1) {
                  // 根據讀取的長度寫入到os對象中
                  bos.write(buffer, 0, len)
              }
              // 返回字符串
              val msg = String(bos.toByteArray())
              // 釋放資源
              inputStream.close()
              bos.close()
              return msg
          }
      }

      9.3 OkHttp網絡框架入門

      前言

      android app開發中一般都會有大量的網絡請求,所以在這里給大家推薦一個非常強大的網絡開源庫OkHttp,它是Square公司出品的,熟悉這個公司的朋友都知道,這個公司貢獻了很多優秀的開源庫,所以這個公司出品的東西每次都能夠引起強烈的反響。而且retrofit+okhttp這種模式也在企業開發中被使用的越來越多。retrofit是什么?不知道?沒關系,在后續的章節會為大家講解,而這一篇就著重為大家講解一下okhttp的入門使用。下面這2個地址是官方教程,希望給初學者帶來一點幫助。

      OkHttp官網地址

      OkHttp GitHub地址

      下面用官方的話來讓大家認識一下Okhttp

      官方描述

      大概意思是說:OkHttp是一款優秀的網絡訪問框架,支持基于Http的所有請求方式,內部使用連接池來減少請求時的延遲問題,支持下載文件透明的GZIP壓縮,可以設置緩存來避免重復的網絡請求。處理了很多網絡疑難雜癥:會從很多常用的連接問題中自動恢復。如果您的服務器配置了多個IP地址,當第一個IP連接失敗的時候,OkHttp會自動嘗試下一個IP。OkHttp還處理了代理服務器問題和SSL握手失敗問題。提供了簡潔的API來供開發者使用,它同時支持同步和異步的調用 方式來處理響應。支持Android 2.3以上和java jdk1.7以上。

      android studio的相關配置

      在app的buid.gradle中添加如下依賴

      compile 'com.squareup.okhttp3:okhttp:3.9.1'

      或者MAVEN

      <dependency>
        <groupId>com.squareup.okhttp3</groupId>
        <artifactId>okhttp</artifactId>
        <version>3.9.1</version>
      </dependency>

      二、Get請求的使用

      Okhttp的Get請求分為2種,一種是同步的Get請求,一種是異步的Get請求。同步Get請求會一直等待http請求, 直到服務器返回了響應內容. 在這段時間會阻塞當前線程, 很明顯同步Get請求不能在Android的UI線程中執行。而異步Get請求是在另外的工作線程中執行http請求, 請求時不會阻塞當前的線程, 所以是可以直接在Android的UI線程中使用,我也推薦這種異步的Get請求。

      2.1 同步的Get請求

      這種方式需要放在一個子線程中執行

              //創建okHttpClient對象
              val okHttpClient = OkHttpClient()
              //Request是請求體
              val request = Request.Builder()
                      .url(URL_GET_NEWS)
                      // .addHeader() //可以添加head
                      .build()
              // Call負責發送執行請求和讀取響應
              val call = okHttpClient.newCall(request)
              try {
                  //同步的get請求的發起,Response代表Http請求的響應結果信息
                  val response = call.execute()
                  if (response.isSuccessful) {
                      //響應成功,獲取響應信息
                      val result = response.body()!!.string()
                      Log.i(TAG, "result: " + result);
                  }
              } catch (e: IOException) {
                  e.printStackTrace()
              }

      我們可以來看看發送一個get請求的步驟:

      1、構建一個OkHttpClient

      2、通過Request.Builder輔助類來構建Request對象,很明顯,這是一個經典的建造者模式這里包含了請求的所有信息,最少包含包含你的URL地址,當然還可以設置header、method、cacheControl等

      3、通過OkHttpClient和Request對象來獲取一個Call對象,Call是一個接口,Call負責發送執行請求和讀取響應

      4、通過execute()來獲得Response對象,而Response對象代表Http請求的響應信息

      5、通過response.isSuccessful來判斷是否響應成功,如果響應成功可以進一步通過Response對象來獲取響應信息,比如,可以通過response.body().string()獲取String字符串;可以通過response.body().byteStream()獲取輸入流Inputstream以及其他類型的數據。注意一點:string()方法對于數據量比較少來說十分方便,比如簡單的json信息,但是它會將把整個信息加載到內存中,所以如果響應體太大(超過1MB), 應使用獲取流的方式來處理響應體。

      從isSuccessful的源碼可以看出,Okhttp判斷是否響應成功的方式也是通過相應碼來實現的,之前的HttpUrlConnection中,我們已經知道200是成功的,實際上200系列的都是可以理解為響應成功。

      /**
         * Returns true if the code is in [200..300), which means the request was successfully received,
         * understood, and accepted.
         */
        public boolean isSuccessful() {
          return code >= 200 && code < 300;
        }

      2.2 異步Get請求

      這種方式不用再次開啟子線程,但回調方法是執行在子線程中,所以在更新UI時還要跳轉到UI線程中,可以利用handler或者runOnUiThread來做

      //到獲取Call對象的操作和同步的方式是一致的
      call.enqueue(object : Callback {
                  override fun onFailure(call: Call, e: IOException) {
                      Log.i(TAG, "onFailure: " + e.message)
                  }
      
                  override fun onResponse(call: Call, response: Response) {
                      if (response.isSuccessful) {
                          //響應成功
                          val result = response.body()!!.string()
                          Log.i(TAG, "result: " + result)
                          runOnUiThread { resultTv!!.text = result }
                      }
                  }
              })

      回調接口的onFailure方法和onResponse執行在子線程。 響應失敗時會執行onFailure(),可以在這里獲取失敗的異常信息,onResponse可以獲取響應成功的信息。

      三、Post請求的使用

      在app開發中經常有這樣的應用場景,向服務器提交用戶填寫的表單信息,或者上傳json字符串,或者上傳一張圖片或者其他文件。這種情況下,就會用到Okhttp的Post請求了,大家都知道在post請求上傳數據的時候,都是把數據放在body里面來傳遞的,而在okhttp中,最核心的一步就是構建RequestBody對象,把要上傳的信息都放在這個RequestBody對象里面。

      3.1 上傳表單信息

      val okHttpClient = OkHttpClient()
              //創建表單請求體
              val formBody = FormBody.Builder()
              //以key-value的形式添加表單信息
              formBody.add("username", "zx") //添加姓名
              formBody.add("sex", "0") //添加性別
      
              val request = Request.Builder()
                      .url("http://www.xxx/upload")
                      .post(formBody.build())//傳遞請求體
                      .build()
      
              //new call
              val call = okHttpClient.newCall(request)
              call.enqueue(object : Callback {
                  override fun onFailure(call: Call, e: IOException) {
                      Log.i(TAG, "onFailure: " + e.message)
                  }
      
                  override fun onResponse(call: Call, response: Response) {
                      if (response.isSuccessful) {
                          val result = response.body()!!.string()
                          Log.i(TAG, "onResponse: result: " + result)
                      }
                  }
              })

      通過FormBody以key-value的形式添加表單信息,注意,這個key是后臺定義的,不能出錯,這和html的表單提交是一樣的。而FormBody是Request Body的子類。

      3.2 上傳json字符串

       //創建一個OkHttpClient對象
              val okHttpClient = OkHttpClient()
              val json = "" //要傳遞的json字符串
              //表示傳遞的數據類型為json串
              val JSON = MediaType.parse("application/json; charset=utf-8")
              /**
               * 創建一個RequestBody
               * 第一個參數:傳遞的數據類型
               * 第二個參數:json串
               */
              val requestBody = RequestBody.create(JSON, json)
              //創建一個請求對象
              val request = Request.Builder()
                      .url("http://xxx/UploadJson")
                      .post(requestBody)
                      .build()
      
              okHttpClient.newCall(request).enqueue(object : Callback {
                  override fun onFailure(call: Call, e: IOException) {
      
                  }
      
                  override fun onResponse(call: Call, response: Response) {
      
                  }
              })

      這里直接通過RequestBody來設置請求體,注意需要通過MediaType來設置上傳的數據類型。

      3.3 上傳圖片或者其他文件

             //創建一個OkHttpClient對象
              val okHttpClient = OkHttpClient()
              //表示傳遞的數據類型為所有類型的文件
              val fileType = MediaType.parse("File/*")
              val path = "" //文件路徑
              val file = File(path)
              /**
               * 上傳文件的請求體
               * 第一個參數:傳遞的數據類型,這里是文件file類型
               * 第二個參數:文件file對象
               */
              val body = RequestBody.create(fileType, file)
              val request = Request.Builder()
                      .url("http://xxx/UploadFile")
                      .post(body)
                      .build()
      
              okHttpClient.newCall(request).enqueue(object : Callback {
                  override fun onFailure(call: Call, e: IOException) {
      
                  }
      
                  override fun onResponse(call: Call, response: Response) {
      
                  }
              })

      注意:這里的關鍵也是通過MediaType來設置上傳的數據類型為所有類型的文件

      3.4 使用MultipartBody同時表單信息和文件 這也是在開發中用的比較多的一種場景,比如上傳一張用戶頭像的圖片,同時需要上傳用戶的uid和token信息等參數,這時候,用這種方式再合適不過了

       //創建一個OkHttpClient對象
              val okHttpClient = OkHttpClient()
              val mediaType = MediaType.parse("file/*")
              val requestBody = RequestBody.create(mediaType, File("xx.png"))
              //構建MultipartBody對象,可以同時提交表單和文件
              val multipartBody = MultipartBody.Builder()
                      .setType(MultipartBody.FORM)
                      .addFormDataPart("phone", "13x")
                      .addFormDataPart("image", "head_img", requestBody)
                      .build()
      
              //創建一個請求對象
              val request = Request.Builder()
                      .url("http://xxx/Upload")
                      .post(multipartBody)
                      .build()
      
              okHttpClient.newCall(request).enqueue(object : Callback {
                  override fun onFailure(call: Call, e: IOException) {
      
                  }
      
                  override fun onResponse(call: Call, response: Response) {
      
                  }
              })

      FormBody方式提交表單是以form-data的形式上傳,而MultipartBody是以Multipart/form-data的形式上傳,二者只是不同的上傳數據的方式而已

      9.4 Okhttp進階教程

      前言

      在上一篇文章中介紹了okhttp的get、post請求的基本使用,那么okhttp的功能就只有這些嗎?答案當然不是,在開發中,我們經常可能需要設置請求頭、在本地緩存網絡數據、設置請求超時的時間,取消網絡請求等等,以及對okhttp的封裝,而這一篇就為大家帶來okhttp高級一點的用法。

      設置請求頭

      可以通過header或者addHeader的方式來設置

      Request request = new Request.Builder()
                     .url("http://www.xxx")
                     .header("User-Agent", "android")
                     .addHeader("content-type", "application/json;charset:utf-8") 
                     .build();

      使用Gson

      一般情況下,后臺返回給app的數據都是json或者xml格式,尤其以json為多。json是什么?json是一種輕量級的數據交換格式,是一種完全獨立于編程語言的文本格式,由于其簡潔和清晰的層次結構使得 JSON 成為理想的數據交換語言。正是這些特性,使得在網絡傳輸中被大量采用。如果你還不清楚Gson怎么用,這里推薦2篇文章供大家學習。

      GsonFormat插件從配置到使用

      Gson解析——從簡單數據到復雜數據

      Gson是Google的開源庫,一般會配合GsonFormat插件使用,最大的作用就是便于在json串和java對象之間的快速轉換。

      val okHttpClient = OkHttpClient()
              val request = Request.Builder()
                      .url(URL_GET_NEWS)
                      .build()
      
              okHttpClient.newCall(request).enqueue(object : Callback {
                  override fun onFailure(call: Call, e: IOException) {
                      Log.i("tag", "onFailure: " + e.message)
                  }
      
                  override fun onResponse(call: Call, response: Response) {
                      if (response.isSuccessful) {
                          val data = response.body()!!.string()
                          Log.i("tag", "data: " + data)
                        //把json串轉化成java bean對象
                          val newsBean = Gson().fromJson<NewsBean>(data, NewsBean::class.java!!)
                      }
                  }
              })

      設置緩存

      okhttp中建議用CacheControl這個類來進行緩存策略的制定。

       val cacheFile = File(Environment.getExternalStorageDirectory().toString() + "cache")
              val cacheSize = 10 * 1024 * 1024 // 緩存大小為10M
              //插件緩存對象
              val cache = Cache(cacheFile, cacheSize.toLong())
              val okHttpClient = OkHttpClient.Builder()
                      .cache(cache) //設置緩存
                      .build()
              //設置緩存控制
              val cacheControl = CacheControl.Builder()
                      .maxAge(60, TimeUnit.SECONDS)
                      .build()
              val request = Request.Builder()
                      .url("http://xxx")
                      .cacheControl(cacheControl)
                      .build()
      
              okHttpClient.newCall(request).enqueue(object : Callback {
                  override fun onFailure(call: Call, e: IOException) {
      
                  }
      
                  override fun onResponse(call: Call, response: Response) {
      
                  }
              })

      更多關于緩存的設置,可以參考這篇文章。 OKHTTP之緩存配置詳解

      設置超時

      由于網絡請求的操作是個耗時操作,是需要用戶等待的,而我們的app也不能讓用戶等待太長時間而一直沒有響應,所以設置連接超時, 讀取超時和寫入超時是很有必要的

      OkHttpClient client = new OkHttpClient.Builder()
              //連接超時,單位秒
              .connectTimeout(8,TimeUnit.SECONDS)
              //寫入超時
              .writeTimeout(8, TimeUnit.SECONDS)
              //讀取數據超時
              .readTimeout(8, TimeUnit.SECONDS)
              .build();

      取消網絡請求

      有時候在一個頁面進行網絡請求的時候,如果網絡不好,用戶等待了很長時間或者用戶不想看app了,都會直接推出app,這個時候,我們就需要取消正在請求的http request, 在Okhttp2.x中可以使用 okhttpClient.cancel(tag)來直接取消,而3.x中沒有這個方法了,但是我們依然有辦法解決。

      var okHttpClient: OkHttpClient = OkHttpClient()
      val request = Request.Builder()
                      .url("http://xxx")
                      .tag("tag")   //為請求設置tag
                      .build()
      var call = okHttpClient.newCall(request)
      call.enqueue(object : Callback {
              override fun onFailure(call: Call, e: IOException) {
      
                  }
      
              override fun onResponse(call: Call, response: Response) {
      
                  }
              })
      
      //取消一個網絡請求
      call.cancel();

      關于取消OkHttp請求的問題

      9.5 Retrofit入門講解

      一、前言

      在之前的文章當中講解了okhttp框架,而這一篇為大家帶來另外一種網絡框架Retrofit2.0。Retrofit也是Square公司的開源庫,閱讀它的源碼會發現,它的底層實現是由okhttp來實現的,也就是說它也是由okhttp去完成網絡請求,只是它通過接口和注解的方式進行網絡請求,使用Retrofit將會更好的處理網絡請求。

      Retrofit官方學習地址

      Retrofit github地址

      集成Retrofit

      • Maven
        <dependency>
        <groupId>com.squareup.retrofit2</groupId>
        <artifactId>retrofit</artifactId>
        <version>2.3.0</version>
        </dependency>

        或者

      • Gradle
        compile 'com.squareup.retrofit2:retrofit:2.3.0'
        //支持gson
        compile 'com.squareup.retrofit2:converter-gson:2.0.2'

      二、Retrofit實現一個最基本的Get請求

      以知乎日報的這個接口舉例 https://news-at.zhihu.com/api/4/themes

      2.1 先定義一個Retrofit接口

      public interface ApiService {
          @GET("api/4/themes")
          val getNewsData: Call<ResponseBody>
      }

      接口中定義的獲取網絡數據的方法的返回值是Call+范型,這個Call就是Okhttp中的Call對象,而且頭部需要加上注解,因為這里是一個Get請求,所以這里是@Get注解,注解里面是這個URL地址的路徑,除了Get注解,還有很多其他注解,下面按照分類給出這些注解。

      • 方法注解,包含@GET、@POST、@PUT、@DELETE、@PATH、@HEAD、@OPTIONS、@HTTP。
      • 標記注解,包含@FormUrlEncoded、@Multipart、@Streaming。
      • 參數注解,包含@Query,@QueryMap、@Body、@Field,@FieldMap、@Part,@PartMap。
      • 其他注解,@Path、@Header,@Headers、@Url

      2.2 創建Retrofit對象,并獲取這個接口對象

      //獲取Retrofit實例
      var retrofit = Retrofit.Builder()
                  //設置baseUrl,必須以"/"結尾
                  .baseUrl("http://litchiapi.jstv.com/")
                  //可以用Gson將獲取到到json數據用直接轉換成bean對象
                  .addConverterFactory(GsonConverterFactory.create())
                  //可以轉化成String
                  .addConverterFactory(ScalarsConverterFactory.create())
                  .build()
      //獲取Retrofit接口對象
      var apiService = retrofit.create<ApiService>(ApiService::class.java)
      • baseUrl:也就是一個URL的基本部分,一般是協議+域名的就構成了url的前綴。
      • addConverterFactory:設置一個轉換工廠,這里的GsonConverterFactory.create()是設置可以用Gson解析數據

      2.3 接口調用

      Call<ResponseBody> call = apiService.getNewsData();
      call.enqueue();

      通過接口對象apiService獲取到Call對象之后就和okhttp的用法一樣了。

      三、復雜一點的Get請求

      下面這個URL是帶參數的,并且以bean對象的形式把獲得的數據呈現給我們 http://litchiapi.jstv.com/api/GetFeeds?column=0&PageSize=10&pageIndex=1

      3.1 接口

      public interface ApiService {
          @GET("api/GetFeeds")
          fun getNews(@QueryMap params: Map<String, String>): Call<NewsBean>
      }

      因為我們的URL是帶參數的,所以這里用了@QueryMap注解,表示請求參數可以放在一個Map集合。當然除了使用@QueryMap注解以外,還可以使用@Query注解,這種方式的話,接口的方法用如下表示,也就是說調用的時候,直接傳各個參數就行了。

       @GET("api/GetFeeds")
       fun getNews(@Query("column") String column, @Query("PageSize") String PageSize,@Query("pageIndex") String pageIndex): Call<NewsBean>

      3.2 創建Retrofit對象和接口對象

       //獲取Retrofit實例
           var retrofit = Retrofit.Builder()
                  //設置baseUrl,必須以"/"結尾
                  .baseUrl("http://litchiapi.jstv.com/")
                  //可以用Gson將獲取到到json數據用直接轉換成bean對象
                  .addConverterFactory(GsonConverterFactory.create())
                  //可以轉化成String
                  .addConverterFactory(ScalarsConverterFactory.create())
                  .build()
      
          //獲取Retrofit接口對象
           var apiService = retrofit.create<ApiService>(ApiService::class.java)

      addConverterFactory這是添加一個轉換工廠,而這里的參數是GsonConverterFactory.create()表示可以用Gson來解析最后獲取的結果,也就是最后的結果可以用對象的形式返回給我們。

      3.3 調用接口

       val map = HashMap<String, String>()
              map.put("column", "0")
              map.put("PageSize", "10")
              map.put("pageIndex", "1")
              apiService.getGoods(map).enqueue(object : Callback<NewsBean> {
                  override fun onResponse(call: Call<NewsBean>, response: Response<NewsBean>) {
                      if (response.isSuccessful) {
                          val status = response.body()!!.status
                          Log.i(TAG, "onResponse: status:" + status)
                      }
                  }
      
                  override fun onFailure(call: Call<NewsBean>, t: Throwable) {
                      Log.i(TAG, "onFailure: " + t.toString())
                  }
              })

      四、POST注解相關

      • 4.1 上傳簡單的表單參數
        @FormUrlEncoded
        @POST("user/login")
        fun login(@Field("phone") phone: String, @Field("pwd") pwd: String): Call<UserBean>

        @POST:也就是post請求 @FormUrlEncoded:說明請求體是Form表單,也就是以form-data的形式上傳 @Field:表示這個參數是表單字段,其中@Field("phone")里的參數phone是表單的key,而String phone是表單的value,不能弄混淆了。

      如果參數過多,也可以考慮@FieldMap的形式

      @Multipart
          @POST("upload/img")
          fun upload(@Part file: MultipartBody.Part): Call<ResponseBody>
      • 4.2上傳圖片或者其他文件

      4.2.1 接口

      @Multipart
      @POST("upload/img")
      Call<ResponseBody> upload(@Part MultipartBody.Part file);

      @Multipart@Part是需要配合使用的,表示上傳文件,@Multipart:請求體是支持文件上傳的 From 表單

      4.2.2 調用接口

      val file = File("path")
      val requestBody = RequestBody.create(MediaType.parse("multipart/form-data"), file)
              //第一個參數是后臺接收這個文件的key,第二個參數是文件名稱,第三個參數是請求體
      val multipartBody = MultipartBody.Part.createFormData("key",
            file.name, requestBody)
      
      apiService.upload(multipartBody).enqueue()
      • 4.3 同時上傳多文件多表單參數(最簡單的方式)

      4.3.1 接口定義

       @POST("upload/info")
       fun upload(@Body Body: RequestBody): Call<ResponseBody>

      @Body:表示這是非表單的請求體,且指定一個對象作為請求體,當然這里是指定RequestBody對象

      4.3.2 調用接口

          //構建body
              val builder = MultipartBody.Builder()
                      .setType(MultipartBody.FORM)
                      .addFormDataPart("do", "mobile")
                      .addFormDataPart("r", "util.uploader")
                      .addFormDataPart("mid", "mid")
                      .addFormDataPart("token", "token")
              for (file in fileList) {
                  //這里上傳的是多圖
                  builder.addFormDataPart("file[]", file.name, RequestBody.create(MediaType.parse("image/*"), file))
              }
              val requestBody = builder.build()
              apiService.upload(requestBody)

      總結:Retrofit的也是通過okhttp去進行網絡請求的,只是通過接口和N多注解多形式暴漏給我們使用,要多掌握其中的注解,下面2個地址有對這些注解有詳細的解釋,下一節講解retrofit2.0+rxjava2

      Retrofit 2.0 注解篇

      你真的會用Retrofit2嗎?Retrofit2完全教程

      9.6 Retrofit2.0+rxjva2優雅的實現網絡請求

      一、前言

      在上一小節中,已經為大家講解了retrofit的使用,也掌握了一些基本的操作符,更多的用法,希望大家根據文章提供的連接自行查閱,學習,而這一節為大家帶來的是rxjava2+retrofit2。首先我們一起來回顧一下在之前使用retrofit做網絡請求的流程或者模式是什么樣的。

      如果你還對rxjava不熟悉,不妨去這里了解一下先,里面內容還算詳細、易懂。 這可能是最好的RxJava 2.x 教程(完結版)

      1.1 在接口中定義我們網絡請求的方法

          /**
           * 獲取地圖數據
           * @param map
           * @return
           */
          @GET("service/regeo")
          fun getMapData(@QueryMap map: Map<String, String>): Call<MapBean>

      1.2 調用接口,通過這個Call對象去做網絡請求

      val call = getApiService()!!.getMapData(map)
       call.enqueue(object : Callback<MapBean> {
                  override fun onResponse(call: Call<MapBean>, response: Response<MapBean>) {
                      Log.i(TAG, "onResponse: " + response.body()!!.status)
                  }
      
                  override fun onFailure(call: Call<MapBean>, t: Throwable) {
                      Log.i(TAG, "onFailure: " + t.toString())
                  }
              })

      二、使用rxjava2+retrofit2的實現方式

      下面通過一個最簡單的小例子來為大家講解

      2.1 創建接口

        @GET("service/regeo")
        fun getMapData2(@QueryMap map: Map<String, String>): Observable<MapBean>

      由于網絡請求是結合rxjava2來使用,所以這的返回值已經不是Call了,而是Observable對象。

      2.2 創建Retrofit對象和接口對象

      Retrofit retrofit = new Retrofit.Builder()
                          .baseUrl("http://ditu.amap.com/")
                          //把請求結果轉換成實體類
                          .addConverterFactory(GsonConverterFactory.create())
                          //設置可以使用rxjava2和retrofit結合使用
                          .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                          .build();
      apiService= retrofit.create(ApiService.class);

      2.3 調用接口,開始網絡請求

      getApiService()!!.getMapData2(map)
                      //被觀察者在子線程中執行
                      .subscribeOn(Schedulers.io())
                      //觀察者在主線程中執行
                      .observeOn(AndroidSchedulers.mainThread())
                      .subscribe(object : Observer<MapBean> {
                          override fun onSubscribe(d: Disposable) {
                              Log.i(TAG, "onSubscribe: start request")
                          }
      
                          override fun onNext(value: MapBean) {
                              Log.i(TAG, "onNext: request success")
                          }
      
                          override fun onError(e: Throwable) {
                              Log.i(TAG, "onError:  " + e.toString())
                          }
      
                          override fun onComplete() {
                              Log.i(TAG, "onComplete: request complete")
                          }
                      })

      注意:在rxjava1中subscribe方法的參數可以是Subscriber對象,而在rxjava2中是沒有辦法new出這個對象的,可以是 Observer或者Disposable;而回調中的 onSubscribe等同于rxjava中的onStart,表示開啟了網絡請求,其他幾個回調與rxjava1相同。

      三、一步步封裝一下簡單的Retrofit2+rxjava2框架

      3.1 一般后臺傳給我們的json數據的格式都是固定的,所以可以先封裝一個實體類的基類

      class BaseResponse<T> {
          var status: Int = 0 //狀態碼,1:成功,其他:失敗
          var errorMsg: String? = null //錯誤信息的描述
          var result: T? = null //返回的結果(數據結構)
      }

      3.2 使用了基類的Retrofit接口

      @GET("service/regeo")
      fun getMapData3(@QueryMap map: Map<String, String>): Observable<BaseResponse<MapBean>>

      3.3 對Observer的封裝

      abstract class BaseObserver<T> : Observer<BaseResponse<T>> {
          private val mContext: Context
          private val isShowDialog: Boolean
      
          /**
           *
           * @param mContext
           * @param isShowDialog 是否顯示加載進度圈
           */
          constructor(mContext: Context, isShowDialog: Boolean) {
              this.mContext = mContext
              this.isShowDialog = isShowDialog
          }
      
      
          override fun onSubscribe(d: Disposable) {
              Log.i(TAG, "onStart: ")
              if (isShowDialog) {
                  // 顯示加載進度圈
                  DialogHelper.showProgressDialog(mContext, "加載中")
              }
          }
      
          override fun onComplete() {
              //關閉加載進度圈
              Log.i(TAG, "onCompleted: ")
              if (isShowDialog) {
                  DialogHelper.stopProgressDialog()
              }
          }
      
      
          override fun onError(throwable: Throwable) {
              Log.i(TAG, "onError:---->" + throwable.toString())
              if (isShowDialog) {
                  DialogHelper.stopProgressDialog()
              }
              onFailure(throwable.toString())
          }
      
          override fun onNext(response: BaseResponse<T>) {
              if (response.status == 1) {
                  //獲取數據成功
                  onSuccess(response.result)
              } else {
                  //獲取數據失敗
                  onFailure("onNext解析數據異常")
              }
          }
      
          /**
           * 錯誤回調
           */
          protected abstract fun onFailure(msg: String)
      
          /**
           * 成功的回調
           */
          protected abstract fun onSuccess(t: T?)
      
          companion object {
      
              private val TAG = "BaseObserver"
          }
      }

      因為Observer是一個接口,說白了,這個基類就是實現這個接口,對這幾個回調onSubscribe,onComplete,onNext,onError做一個統一處理而已,其中onSubscribe,onComplete分別表示請求開始,和請求完成,一般在這2個方法中是去顯示一個正在進行網絡請求的dialog和關閉這個dialog,所以需要把它封裝起來,只需要把onNext,onError這2個方法,(第一個表示成功,第二個表示失敗)暴漏就行。

      3.4 對線程調度器去切換線程的2個方法去封裝,因為所有的地方都需要用到這2個

      //被觀察者在子線程中執行
      .subscribeOn(Schedulers.io())
      //觀察者在主線程中執行
      .observeOn(AndroidSchedulers.mainThread())

      封裝起來的結果

      public class RxSchedulers {
      
          public static <T> ObservableTransformer<T, T> setSchedulers() {
             return new ObservableTransformer<T, T>() {
                 @Override
                 public ObservableSource<T> apply(Observable<T> upstream) {
                     return upstream.subscribeOn(Schedulers.io())
                             .observeOn(AndroidSchedulers.mainThread());
                 }
             };
          }
      }

      3.5 最后來看看,最終封裝后的調用是什么樣的,怎么樣,很簡潔吧,這個簡單的封裝就完成了。

      getApiService().getMapData3(map)
                      .compose(RxSchedulers.<BaseResponse<MapBean>>setSchedulers())
                      .subscribe(new BaseObserver<MapBean>(context,false) {
                          @Override
                          protected void onFailure(String msg) {
      
                          }
      
                          @Override
                          protected void onSuccess(MapBean mapBean) {
      
                          }
                      });

      歡迎大家對本教程的內容進行刊錯和糾正,點擊此處前往論壇給你喜歡的文章作者給予支持鼓勵,若有問題反饋和建議請注明具體章節
      夫妻性姿势真人示范 - 视频 - 在线观看 - 影视资讯 - 唯爱网