Android優化篇之網絡優化

網絡類型

網絡連接類型

  1. 單 工通信:數據只能一方發送到另一方。(例:UDP協議,Android廣播)
  2. 半雙工通信:數據可以從A發送到B,也可以從B發到A,但同一時刻,只能有一個方向的數據傳輸。(例:Http協議,向服務器發起請求,服務器返回響應)
  3. 全雙工通信:任意時刻,都會存在A到B和B到A的雙向傳輸。(例:Socket協議,長連接通道,IM聊天,視頻通話)
  • Http1.0協議默認關閉長連接屬于半雙工通信

可增加http請求頭字段"Connection:Keep-Alive",Http1.1默認開啟

可增加HTTP請求頭字段"Connection:close"

Https協議

  • 保證http數據傳輸的可靠性。s指SSL/TLS協議

  • App(Http) : TCP IP

  • App(Https):SSL/TLS TCP IP

  • Https協議相比于Http協議在TCP協議前會先走SSL/TLS協議

對稱加密與非對稱加密

  • https能保證傳輸可靠性,是因為對數據進行了加密,http為明文傳輸,數據易被竊取和冒充
  • http優化過程中,對數據進行了加密傳輸,https因此誕生

對稱加密

對稱加密和解密采用一把鑰匙,需雙方約定,發送方通過密鑰加密數據,接收方使用同一密鑰解密獲取傳遞數據。

  • 流程

發送方 A 發送用密鑰加密后的數據 ---> 接收方 B 接收加密數據并用密鑰解密

  • 優缺點

流程簡單,解析數據快。

安全性差,密鑰管理有風險(因需約定同一key,key存在傳輸劫持風險,統一存儲存在被攻擊風險)。

非對稱加密

兩把密鑰:私鑰 + 公鑰,公鑰任何人可知,發送方使用公鑰加密,接收方可以用只有自己知道的私鑰解密并獲取數據。

  • 流程

接收方B發送公鑰至發送方A ---> A發送數據,公鑰加密 ---> B接收數據,私鑰解密

  • 優缺點

私鑰只有自己知道。

流程繁瑣,解密速度慢。

公鑰的安全保障

CA證書

  • 生成規則

接收方公鑰+接受方身份信息+其他信息--->Hash算法--->消息摘要--->CA機構私鑰加密--->數字簽名

  • 最終發送給接收方是數字證書

數字證書:數字簽名+公鑰+接收方的個人信息等

  • 合法性驗證

比較數字證書的消息摘要(Hash算法和CA公鑰解密)是否一致

https的傳輸流程

  • 一個https請求,包含2次http傳輸:以請求www.google.com流程為例
  1. 客戶端向服務端發起請求,要訪問google,先于google服務器建立連接
  2. 服務端有公鑰和私鑰,公鑰可以發給客戶端,然后給客戶端發送一個SSL證書,證書包含:CA簽名、公鑰、google的信息
  3. 客戶端接收到SSL證書后,對CA簽名解密,判斷證書合法性,不合法就斷開連接,合法則生成一個隨機數,作為數據對稱加密的密鑰,通過公鑰加密發送至服務端
  4. 服務端接收到客戶端加密數據后,通過私鑰解密,拿到對稱加密的密鑰,然后將google相關數據通過對稱加密密鑰加密,發送到客戶端
  5. 客戶端通過解密拿到服務端的數據,請求結束
  • https非完全非對稱加密

對稱加密密鑰傳遞有風險,前期通過非對稱加密傳遞對稱加密密鑰,后續數據傳遞都是通過對稱加密,以提高數據解析效率。

DNS 解析

  • 添加trace日志除了可以看到net timeout錯誤,還可以看到UnknowHostException:DNS解析失敗,未解析獲取到服務器ip地址

添加DNS解析策略

Object HttpTool {
    private const val TAG = "HttpTool"
    private const val URL = "https://www.xxx.com"

    fun initHttp() {
        val client = OkHttpClient.Builder().build()
        Request.Builder().url(URL).build().also {
            kotlin.runCatching {
                client.newCall(it).execute()
            }.onFailure {
                Log.e(TAG, "initHttp: error: $it")
            }
        }
    }
}

很明顯xxx.com域名不存在,就會報如下錯誤:

java.net.UnknownHostException: Unable to resolve host "www.xxx.com": No address associated with hostname

  • 一旦遇到如上問題:域名被劫持修改,整個服務會處于宕機狀態,體驗很差。
  • 優化方案:通過OkHttp提供的自定義DNS解析器優化處理
package okhttp3;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.List;

// 路由尋址
public interface Dns {
    /**
     * 使用{@link InetAddress#getAllByName}請求底層操作系統查找IP地址。
     * 大多數自定義{@link Dns}實現都應委派到此實例。
     */
    Dns SYSTEM = (hostname) -> {
        if (hostname == null) {
            throw new UnknownHostException("hostname == null");
        } else {
            try {
                return Arrays.asList(InetAddress.getAllByName(hostname));
            } catch (NullPointerException var3) {
                UnknownHostException unknownHostException = new UnknownHostException("Broken system behaviour for dns lookup of " + hostname);
                unknownHostException.initCause(var3);
                throw unknownHostException;
            }
        }
    };

    /**
     * 返回{@code hostname}的IP地址,按OkHttp嘗試的順序排列。
     * 如果連接到某個地址失敗,OkHttp將重試與下一個地址的連接,直到要么建立了連接,要么耗盡了IP地址集,要么超出了限制。
     */
    List<InetAddress> lookup(String var1) throws UnknownHostException;
}

// 自定義尋址策略
class MyDns: Dns {
    private const val TAG = "MyDns"

    override fun lookup(hostname: String): MutableList<InetAddress> {
        val result = mutableListOf<InetAddress>()
        var systemAddressList: MutableList<InetAddress>? = null

        // 通過系統DNS解析
        kotlin.runCatching {
            systemAddressList = Dns.SYSTEM.lookup(hostname)
        }.onFailure {
            Log.e(TAG, "lookup: it")
        }

        if (systemAddressList != null && systemAddressList!!.isNotEmpty()) {
            result.addAll(systemAddressList!!)
        } else {
            // 系統DNS解析失敗,走自定義路由
            result.add(InetAddress.getByName(www.google.com))

            // 一臺服務器支持多域名
            // result.addAll(InetAddress.getAllByName(ip).toList())
        }
    }
}

使用:
val client = OkHttpClient.Builder().dns(MyDns()).build()

接口數據適配策略

  • 數據格式異常:服務器定義int類型返回字符串"" 或 空數組返回null...
List適配:空解析為空數組
import com.google.gson.Gson
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import java.lang.reflect.Type
import java.util.Collections

class ListTypeAdapter: JsonDeserializer<List<*>> {
    override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): List<*> {
        return try {
            if (json?.isJsonArray == true) {
                Gson().fromJson(json, typeOfT)
            } else {
                Collections.EMPTY_LIST
            }
        } catch (e: Exception) {
            Collections.EMPTY_LIST
        }
    }
}

// String適配:是否為基礎類型(String,Number,Boolean),基礎類型正常轉
import com.google.gson.Gson
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import java.lang.reflect.Type

class StringTypeAdapter: JsonDeserializer<String> {
    override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): String {
        return try {
            if (json?.isJsonPrimitive == true) {
                Gson().fromJson(json, typeOfT)
            } else {
                ""
            }
        } catch (e: Exception) {
            ""
        }
    }
}

使用:
GsonBuilder()
    .registerTypeAdapter(String::class.java, StringTypeAdapter())
    .registerTypeAdapter(List::class.java, ListTypeAdapter())
    .create().also {
        GsonConverterFactory.create(it)
    }

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容