主頁 > 移動端開發 > 【Android】OkHttp原始碼解讀逐字稿(2)-OkHttpClient

【Android】OkHttp原始碼解讀逐字稿(2)-OkHttpClient

2021-08-17 08:06:05 移動端開發

目錄

0.前言

1.各個屬性淺析

01.dispatcher

02.connectionPool

03.interceptors&networkInterceptors

04.eventListenerFactory

05.retryOnConnectionFailure

06.authenticator

07.followRedirects & followSslRedirects

08.cookieJar

09.cache

10.dns

11.proxy & proxySelector & proxyAuthenticator

12.socketFactory & sslSocketFactory

13.x509TrustManager

14.connectionSpecs

15.protocols

16.hostnameVerifier

17.certificatePinner

18.certificateChainCleaner

19.一組跟時間有關的屬性 callTimeoutMillis & connectTimeoutMillis & readTimeoutMillis & writeTimeoutMillis & pingIntervalMillis

2.結語


0.前言

如期而至,相約與本周發布的文章,他來了哦~

首先,我想先講一個點,就是這兩篇文章都稍微比較長,但是如果真的能認真的看完,理解完(如果有讀不懂的地方,我隨時恭候大家來找我交流),我相信,我們都能上到一個不錯的 level 了,

還沒有看上篇文章的,快去圍觀吧~【Android】OkHttp原始碼解讀逐字稿(1)-攔截器

本篇文章,主要探索 OkHttpClient 的一些配置,

先上圖,基于上次的代碼,我們在OkHttpClient()后加上一個.我們就可以看到如下圖的多個屬性和方法,那具體他們的作用是什么呢?那就是我們本篇文章的任務了,走帶著該死的好奇心,一起去瞧瞧,


進入到 OkHttpClient 的代碼中,在剛開始的代碼中,列出了若干個屬性,就是我們要研究的,我先粘出來,這樣方便我們一個個分析,(為了更好看到大局,我洗掉了大部分注釋)

@get:JvmName("dispatcher") val dispatcher: Dispatcher = builder.dispatcher
@get:JvmName("connectionPool") val connectionPool: ConnectionPool = builder.connectionPool
@get:JvmName("interceptors") val interceptors: List<Interceptor> = builder.interceptors.toImmutableList()
@get:JvmName("networkInterceptors") val networkInterceptors: List<Interceptor> = builder.networkInterceptors.toImmutableList()
@get:JvmName("eventListenerFactory") val eventListenerFactory: EventListener.Factory = builder.eventListenerFactory
@get:JvmName("retryOnConnectionFailure") val retryOnConnectionFailure: Boolean = builder.retryOnConnectionFailure
@get:JvmName("authenticator") val authenticator: Authenticator = builder.authenticator
@get:JvmName("followRedirects") val followRedirects: Boolean = builder.followRedirects
@get:JvmName("followSslRedirects") val followSslRedirects: Boolean = builder.followSslRedirects
@get:JvmName("cookieJar") val cookieJar: CookieJar = builder.cookieJar
@get:JvmName("cache") val cache: Cache? = builder.cache
@get:JvmName("dns") val dns: Dns = builder.dns
@get:JvmName("proxy") val proxy: Proxy? = builder.proxy
@get:JvmName("proxySelector") val proxySelector: ProxySelector =
    when {
      // Defer calls to ProxySelector.getDefault() because it can throw a SecurityException.
      builder.proxy != null -> NullProxySelector
      else -> builder.proxySelector ?: ProxySelector.getDefault() ?: NullProxySelector
    }
@get:JvmName("proxyAuthenticator") val proxyAuthenticator: Authenticator =
    builder.proxyAuthenticator
@get:JvmName("socketFactory") val socketFactory: SocketFactory = builder.socketFactory
private val sslSocketFactoryOrNull: SSLSocketFactory?
@get:JvmName("sslSocketFactory") val sslSocketFactory: SSLSocketFactory
  get() = sslSocketFactoryOrNull ?: throw IllegalStateException("CLEARTEXT-only client")
@get:JvmName("x509TrustManager") val x509TrustManager: X509TrustManager?
@get:JvmName("connectionSpecs") val connectionSpecs: List<ConnectionSpec> =
    builder.connectionSpecs
@get:JvmName("protocols") val protocols: List<Protocol> = builder.protocols
@get:JvmName("hostnameVerifier") val hostnameVerifier: HostnameVerifier = builder.hostnameVerifier
@get:JvmName("certificatePinner") val certificatePinner: CertificatePinner
@get:JvmName("certificateChainCleaner") val certificateChainCleaner: CertificateChainCleaner?
@get:JvmName("callTimeoutMillis") val callTimeoutMillis: Int = builder.callTimeout
@get:JvmName("connectTimeoutMillis") val connectTimeoutMillis: Int = builder.connectTimeout
@get:JvmName("readTimeoutMillis") val readTimeoutMillis: Int = builder.readTimeout
@get:JvmName("writeTimeoutMillis") val writeTimeoutMillis: Int = builder.writeTimeout
@get:JvmName("pingIntervalMillis") val pingIntervalMillis: Int = builder.pingInterval


1.各個屬性淺析

01.dispatcher

如果仔細點的道友,可以發現在上篇文章中的開始分析的第一段代碼中,就出現了它,當時,我們并沒有分析它,那現在我們再來看看吧,

override fun execute(): Response {
  check(executed.compareAndSet(false, true)) { "Already Executed" }

  timeout.enter()
  callStart()
  try {
    client.dispatcher.executed(this)
    return getResponseWithInterceptorChain()
  } finally {
    client.dispatcher.finished(this)
  }
}

點擊去,查看一下

runningSyncCalls.add(call)

/** Running synchronous calls. Includes canceled calls that haven't finished yet. */
private val runningSyncCalls = ArrayDeque<RealCall>()

哦~看到上面的注釋,我們知道了就是將當前的 call 放進了一個佇列中 這還是一個同步的 call,

居然有同步,我們很自然就會想到異步這個概念,

正如我們所料,確實,還有另外兩個佇列,一個是 readyAsyncCalls, runningAsyncCalls,看注釋,我們就知道他們的作用了,

/** Ready async calls in the order they'll be run. */
private val readyAsyncCalls = ArrayDeque<AsyncCall>()

/** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
private val runningAsyncCalls = ArrayDeque<AsyncCall>()

這時候,我們應該想起,我們使用 OkHttp 做一個網路請求時,我們還可以使用 enqueue 的方法

client.newCall(request).enqueue(object : Callback{
    override fun onFailure(call: Call, e: IOException) {
    }
    override fun onResponse(call: Call, response: Response) {
    }
})

同理 看到 RealCall 中對應的方法

override fun enqueue(responseCallback: Callback) {
  check(executed.compareAndSet(false, true)) { "Already Executed" }

  callStart()
  client.dispatcher.enqueue(AsyncCall(responseCallback))
}

發現,它呼叫了 dispatcher 的同名方法,

internal fun enqueue(call: AsyncCall) {
  synchronized(this) {
    readyAsyncCalls.add(call)

    // Mutate the AsyncCall so that it shares the AtomicInteger of an existing running call to
    // the same host.
    if (!call.call.forWebSocket) {
      val existingCall = findExistingCallWithHost(call.host)
      if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
    }
  }
  promoteAndExecute()
}

一樣,看到最后一行

/**
 * Promotes eligible calls from [readyAsyncCalls] to [runningAsyncCalls] and runs them on the
 * executor service. Must not be called with synchronization because executing calls can call
 * into user code.
 *
 * @return true if the dispatcher is currently running calls.
 */
private fun promoteAndExecute(): Boolean {
  this.assertThreadDoesntHoldLock()

  val executableCalls = mutableListOf<AsyncCall>()
  val isRunning: Boolean
  synchronized(this) {
    val i = readyAsyncCalls.iterator()
    while (i.hasNext()) {
      val asyncCall = i.next()

      if (runningAsyncCalls.size >= this.maxRequests) break // Max capacity.
      if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue // Host max capacity.

      i.remove()
      asyncCall.callsPerHost.incrementAndGet()
      executableCalls.add(asyncCall)
      runningAsyncCalls.add(asyncCall)
    }
    isRunning = runningCallsCount() > 0
  }

  for (i in 0 until executableCalls.size) {
    val asyncCall = executableCalls[i]
    asyncCall.executeOn(executorService)
  }
  return isRunning
}

看到注釋,我們可以得知,這個方法,就是將一個可以被執行的 call 用 executor ervices 跑起來,看上面兩個 if,只要經歷過這兩個 if 的篩選的話,就可以 executableCalls.add(asyncCall) ,

那我們這兩個if ,主要做了什么,

1.判斷正在執行的 call 的個數是否大于 maxRequests

2.判斷同一個Host下的 call 個數是否大于 maxRequestsPerHost

至于,maxRequests 和 maxRequestsPerHost 就不細講了,見明知意,maxRequests 默認值為 5 ,maxRequestsPerHost 默認值是 64,

我們看看 dispatchtor 這個類的注釋,做一個小結

/**
 * Policy on when async requests are executed.
 *
 * Each dispatcher uses an [ExecutorService] to run calls internally. If you supply your own
 * executor, it should be able to run [the configured maximum][maxRequests] number of calls
 * concurrently.
 */

大概意思,就是會通過一個 ExecutorService 去執行一個call,

最后,留一個疑問,“enqueue 方法,是通過 promoteAndExcute 方法,去呼叫一個 ExecutorService 去執行,那 execute() 是通過什么去運行一個 call的呢?”


02.connectionPool

這個跟我們的 ThreadPool ,具有異曲同工之妙

看下這個類的注釋

* Manages reuse of HTTP and HTTP/2 connections for reduced network latency. HTTP requests that
* share the same [Address] may share a [Connection]. This class implements the policy
* of which connections to keep open for future use.

這里,單獨說明了,HTTP 當同樣的 IP 地址的時候,才可以共享一個連接,

那么 HTTP2 是具有多路復用(Multipex)的能力的,


03.interceptors & networkInterceptors

這個就是將我們自定義的攔截器接收下來,然后在我們上篇文章中的

getResponseWithInterceptorChain() 加入到 攔截器鏈條 中


04.eventListenerFactory

事件監聽器工廠類,

EventListener

* Listener for metrics events. Extend this class to monitor the quantity, size, and duration of
* your application's HTTP calls.

05.retryOnConnectionFailure

這個回傳的是個布林值,所以這是個開關,

看名字,我們可以知道,“是否要在失敗的時候,進行重試”


06.authenticator

自動認證修訂的工具,

tokon 是一個有效期,這時候需要通過 refreshToken 去重新獲取 token


07.followRedirects & followSslRedirects

同理,回傳的是個布林值,所以是個開關,

“再發生重定向的時候,是否要重定向”

“在發生協議重定向(http->https),是否要重定向”


08.cookieJar

CookieJar 類,主要有兩個方法

fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) 
fun loadForRequest(url: HttpUrl): List<Cookie>

從相應頭中保存 對應 url 的 cookies 資訊

在請求頭中帶上 對應 url 的 cookies 資訊


09.cache

* Caches HTTP and HTTPS responses to the filesystem so they may be reused, saving time and * bandwidth.

這個是快取器,主要快取一些資料,在下次獲取的時候,可以直接從cache中獲取,從而可以達到節省帶寬的效果


10.dns

Domain name service 域名決議器,將 host 決議成對應的 IP 地址

核心代碼

InetAddress.getAllByName(hostname).toList()

11.proxy & proxySelector & proxyAuthenticator

代理,如果需要請求需要通過代理服務器來負責,則添加一個代理,

這里需要注意一下它的型別,其中包括 直連,這也是默認方法,也就是不需要代理,

public enum Type {
    /**
     * Represents a direct connection, or the absence of a proxy.
     */
    DIRECT,
    /**
     * Represents proxy for high level protocols such as HTTP or FTP.
     */
    HTTP,
    /**
     * Represents a SOCKS (V4 or V5) proxy.
     */
    SOCKS
};

而 proxy 的默認值為 null

internal var proxy: Proxy? = null

那怎么默認到 直連呢?那就是 proxySelector

when {
  // Defer calls to ProxySelector.getDefault() because it can throw a SecurityException.
  builder.proxy != null -> NullProxySelector
  else -> builder.proxySelector ?: ProxySelector.getDefault() ?: NullProxySelector
}

如果 proxy 為空 且 默認的 proxySelector 也是空的話,就默認為 NullProxySelector ,而它最后回傳的是 Type.DIRECT 的代理

public final static Proxy NO_PROXY = new Proxy();

// Creates the proxy that represents a {@code DIRECT} connection.
private Proxy() {
    type = Type.DIRECT;
    sa = null;
}

最后 proxyAuthenticator ,就是用于驗證 代理服務器 的合法性的,


12.socketFactory & sslSocketFactory

我們進行 HTTP 連接請求本質上是一個 socket, 就是通過 socketFactory 去創建,

同時,我們進行加密連接 SSL 連接的時候,這是的 socket 就是通過 sslSocketFactory 去創建的,


13.x509TrustManager

首先 x509 是一種證書格式,這個類就是驗證證書的合法性的,(如果不太明白,可以自行先了解一下 HTTPS 連接流程其中的安全性是如何保證的?后續,有空有把一些相關基礎性的東西補充成另一篇文章)


14.connectionSpecs

連接標準,

在請求連接的時候,客戶端需要向服務器,發送支持的協議,加密套件等資訊 (看不懂,同上)

這里,我們還需要知道是,提供的四種配置,

/** A secure TLS connection that requires a recent client platform and a recent server. */
@JvmField
val RESTRICTED_TLS = Builder(true)
    .cipherSuites(*RESTRICTED_CIPHER_SUITES)
    .tlsVersions(TlsVersion.TLS_1_3, TlsVersion.TLS_1_2)
    .supportsTlsExtensions(true)
    .build()
/**
 * A modern TLS configuration that works on most client platforms and can connect to most servers.
 * This is OkHttp's default configuration.
 */
@JvmField
val MODERN_TLS = Builder(true)
    .cipherSuites(*APPROVED_CIPHER_SUITES)
    .tlsVersions(TlsVersion.TLS_1_3, TlsVersion.TLS_1_2)
    .supportsTlsExtensions(true)
    .build()

/**
 * A backwards-compatible fallback configuration that works on obsolete client platforms and can
 * connect to obsolete servers. When possible, prefer to upgrade your client platform or server
 * rather than using this configuration.
 */
@JvmField
val COMPATIBLE_TLS = Builder(true)
    .cipherSuites(*APPROVED_CIPHER_SUITES)
    .tlsVersions(TlsVersion.TLS_1_3, TlsVersion.TLS_1_2, TlsVersion.TLS_1_1, TlsVersion.TLS_1_0)
    .supportsTlsExtensions(true)
    .build()

/** Unencrypted, unauthenticated connections for `http:` URLs. */
@JvmField
val CLEARTEXT = Builder(false).build()

其中最后一種 明文傳輸就是 HTTP ,

第一種限制更多;第二種是比較流行的,同時也是默認值;第三種限制比較少,即兼容性更好,


15.protocols

支持的協議版本號,例如:HTTP_1_0; HTTP_1_1;HTTP_2;H2_PRIOR_KNOWLEDGE(不加密,明文傳輸的時候使用)


16.hostnameVerifier

在驗證 證書的合法性的同時,我們還需要驗證是這個證書是哪個網站的,那么就需要這個 hostnameVerifier 來驗證,


17.certificatePinner

這個可以用來對某個網站,在驗證證書的合法性同時,要滿足我們指定的證書哈希值,但是不建議這么做,因為在更換驗證機構后,會導致之前的用戶無法正常使用我們的應用,


18.certificateChainCleaner

這是一個 X509TrustManager 的操作員


19.一組跟時間有關的屬性 callTimeoutMillis & connectTimeoutMillis & readTimeoutMillis & writeTimeoutMillis & pingIntervalMillis

/**
 * Default call timeout (in milliseconds). By default there is no timeout for complete calls, but
 * there is for the connect, write, and read actions within a call.
 */
@get:JvmName("callTimeoutMillis") val callTimeoutMillis: Int = builder.callTimeout

/** Default connect timeout (in milliseconds). The default is 10 seconds. */
@get:JvmName("connectTimeoutMillis") val connectTimeoutMillis: Int = builder.connectTimeout

/** Default read timeout (in milliseconds). The default is 10 seconds. */
@get:JvmName("readTimeoutMillis") val readTimeoutMillis: Int = builder.readTimeout

/** Default write timeout (in milliseconds). The default is 10 seconds. */
@get:JvmName("writeTimeoutMillis") val writeTimeoutMillis: Int = builder.writeTimeout

/** Web socket and HTTP/2 ping interval (in milliseconds). By default pings are not sent. */
@get:JvmName("pingIntervalMillis") val pingIntervalMillis: Int = builder.pingInterval

其中需要額外了解下的是,最后一個 pingIntervalMillis,這是 HTTP2 的時候,可能是一個長連接,那么需要通過這個來進行保活,客戶端發一個 ping,服務端回一個 pong


2.結語

這次的文章,主要是講了這些屬性的,以便我們在一些定制化需求上,知道我們可以用什么來做,然后具體的用法,其實自己嘗試一下,或者搜一下就出來了,

如果文章中,有不懂的,或者紕漏,或者對我的文章寫作排版有建議和意見的,都可以評論或者私信我,

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/294261.html

標籤:其他

上一篇:activity變成dialog

下一篇:Rxjava3的基本使用

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more