主頁 > 移動端開發 > 手把手教你搭建應用的網路診斷模塊(1)——Ping與TraceRoute

手把手教你搭建應用的網路診斷模塊(1)——Ping與TraceRoute

2021-08-10 07:59:22 移動端開發

「椎鋒陷陳」微信技術號現已開通,為了獲得第一手的技術文章推送,歡迎搜索關注!

前言

一個App功能的整體表現,往往與用戶當前的網路狀況密不可分,通過為App引入一個輕量級的網路診斷模塊,收集那些能夠衡量當前網路狀況的重要資訊,然后在征得用戶同意的情況下,將資訊上報到服務端進行分析,可以有針對性地對網路鏈路中的薄榷訓節進行優化,

眾所周知,Android系統基于Linux內核的,Linux本身就提供了許多可用于檢測網路狀況的工具,熟練地運用這些工具,可以很輕松地達到我們網路診斷的目的,今天要分享的就是其中的兩個工具,Ping命令與TraceRoute命令,

網路工具介紹

Ping

聲吶技術

「Ping」這個名字源于聲吶技術,聲吶技術是利用聲波在水中的傳播和反射特性,對水下目標進行探測、分類、定位和跟蹤的技術,

概述

Ping命令是用于檢測從源主機到目標主機是否可達的工具,

該命令基于ICMP協議,通過向目標主機發送指定個數與大小的回送請求(echo request)資料包,并要求目標主機在收到之后回傳相應的回送應答(echo reply)資料包,最終結合資料包的往返時間丟包率來評估網路連接狀況,

圖示

ping命令模型

如果用開頭提及的聲吶技術來類比,就會是這樣的一個對應關系:

對應關系

形式

Ping命令的基本形式如下:

ping [-c 資料包個數] [-s 資料包大小] [主機名/IP地址]

例:

ping -c 5 -s 56 developer.android.google.cn

默認情況下,假如不指定資料包個數,Ping命令就會連續發送資料包,如果僅僅是為了進行連通性測驗,只需要指定3到5個即可,

而假如不指定資料包大小,則默認是56 bytes,

實作

Android支持直接使用命令列工具執行Ping命令,因此只需設定好引數,逐行讀取輸出內容即可:

/**
 * Ping命令
 */
class Ping(
    /** 目標主機域名/IP地址  */
    private val host: String,
    /** 資料包個數,默認連續發送  */
    private val count: Int? = null,
    /** 資料包大小,單位bytes,默認為56 bytes  */
    private val packetSize: Int? = null,
    /** 資料包生存時間 */
    private val ttl: Int? = null,
    /** 超時間隔,單位s */
    private val deadline: Int? = null
) {

    /**
     * ## 執行Ping命令
     * 請注意,ping命令在Linux系統下的引數與在Windows系統下有差異,需要區分
     * -c count ping指定次數后停止ping;
     * -s packetsize 指定每次ping發送的資料位元組數,默認為“56位元組”+“28位元組”的ICMP頭,一共是84位元組;
     */
    fun execute(callback: ExecuteCallback? = null): String {
        val command = toString()
        // 回呼輸出執行的Ping命令
        callback?.onExecuting("% $command\n")

        val result = StringBuilder()
        var process: Process? = null
        var reader: BufferedReader? = null
        try {
            process = Runtime.getRuntime().exec(command)
            reader = BufferedReader(InputStreamReader(process.inputStream))
            // 讀取首行輸出內容
            var line = reader.readLine()
            while (line != null) {
                // 回呼執行程序的輸出內容
                callback?.onExecuting(line)
                // 記錄輸出行到結果字串
                result.append(line).append("\n")
                // 讀取下一行輸出內容
                line = reader.readLine()
            }
            callback?.onCompleted(result.toString())
            reader.close()
            process.waitFor()
        } catch (e: Exception) {
            e.printStackTrace()
        } finally {
            reader?.close()
            process?.destroy()
        }

        return result.toString()
    }

    /**
     * ## 根據構造欄位將物體轉換為具體的Ping命令
     * 判斷各欄位非
     */
    override fun toString(): String {
        val stringBuilder = StringBuilder("ping")
        if (count != null) stringBuilder.append(" -c $count")
        if (packetSize != null) stringBuilder.append(" -s $packetSize")
        if (ttl != null) stringBuilder.append(" -t $ttl")
        if (deadline != null) stringBuilder.append(" -w $deadline")
        stringBuilder.append(" $host")
        return stringBuilder.toString()
    }

}

執行

Ping命令執行結果.jpg

分析

為了方便進行說明,我們在每一個結果行前添加了一個序號,

整個示例可以分為兩塊區域,從第1到6行為執行程序,從第7到8行為統計資訊,

執行程序

第1行表示的是向目標主機發送了5個56 bytes的資料包,

第2-6行數表示的是每個發送的回送請求資料包的執行結果,其中:

  • [64 bytes]是從目標主機回傳的資料,之所以是64 bytes是由于加多了8 bytes的ICMP報頭,
  • [icmp_seq]是ICMP報頭中包含的時序號,用于確定資料包到達的順序以及判斷資料包是否重復
  • [ttl]是資料包的生存時間,是time to live的縮寫,是為了防止資料包在路由選擇的程序中無休止地在網路中流動而設定的,
  • [time]是資料包的往返時間,即從發送回送請求報文之后,到接收到回送應答報文之前經過的時間,
統計資訊

第7行表示的是資料包的傳輸接收情況以及丟包率,其中:

  • [5 packets transmitted]表示傳輸了5個資料包
  • [5 packets received]表示接收了5個資料包
  • [0 packet loss]表示資料包的丟包率為0%,該數值越大,表示網路狀況越不穩定,最高為100%,即目標主機不可達

第8行表示的是資料包往返時間的最小值/平均值/最大值,單位為毫秒(ms),數值越大,意味著網路延遲越嚴重最小值與最大值之間的差值越大,意味著網路抖動越厲害

TraceRoute

路由跟蹤.png

兩臺主機之間的通信,往往需要經過很多中間節點,如果其中某個節點出現問題,可能會導致資料無法送達,通過TraceRoute(跟蹤路由)我們可以定位資料是在哪個節點丟失的,

概述

TraceRoute命令是用于定位從源主機到目標主機所經過的路由,以及到達各個路由的資料包往返時間的工具,

該命令利用的是IP報頭的TTL值ICMP超時報文以及ICMP埠不可達報文,TraceRoute每次都向相同的目標主機發送三次設定了相同TTL值的資料包,利用資料包被丟棄時路由器回傳ICMP超時報文獲知路由器的IP地址及資料包的往返時間,

流程

  1. 首先,向目標主機發送TTL值設為1的資料包,處理該資料包的第一個路由器會將TTL值減1當TTL值變為0時,該資料包就會被丟棄,并發回一份ICMP超時報文,這樣就得到了該路徑中的第一個路由器的IP地址,
  2. 接著,發送TTL值設為2的資料包,該資料包在經過第二個路由器時就會被丟棄,這樣就得到了第二個路由器的IP地址,
  3. 持續這個程序,直至資料包到達目標主機,
  4. 為了確認資料包是否到達目標主機,TraceRoute使用了一個一般應用程式都不會使用的埠號(30000以上)作為目標埠號,這樣,當資料包到達目標主機時,目標主機就會回傳一個ICMP埠不可達報文,從而讓源主機可以確認資料包已經到達了目標主機,

圖示

TraceRoute執行流程(1).png

形式

TraceRoute命令的基本形式如下:

traceroute [主機名/IP地址]

例:

traceroute developer.android.google.cn

實作

由于Android的非Root設備不支持直接使用命令列工具執行TraceRoute命令,因此我們改成以執行Ping命令并通過限定TTL的方式來模擬TraceRoute的程序,從而達到相等效果,缺點是模擬程序較慢,可能會頻繁出現超時情況,

具體的模擬程序如下:

  1. 對目標主機執行Ping命令,發送1個TTL值為1的資料包,第一個路由器將TTL值減1變為0,資料包被路由器丟棄,輸出以下結果行:
    From 10.0.168.254: icmp_seq=1 Time to live exceeded
  1. 對該結果行進行正則運算式匹配,提取其中包含的路由器IP地址,如10.0.168.254;
  2. 對路由器IP地址執行Ping命令,發送3個大小為40 bytes的資料包,資料包到達該路由器,輸出以下結果行:
    48 bytes from 211.136.203.125: icmp_seq=1 ttl=251 time=28.2 ms
    48 bytes from 211.136.203.125: icmp_seq=2 ttl=251 time=75.4 ms
    48 bytes from 211.136.203.125: icmp_seq=3 ttl=251 time=33.5 ms
  1. 對該結果行進行正則運算式匹配,提取其中包含的資料包往返時間;
  2. 對目標主機再次執行Ping命令,發送1個TTL值設為2的資料包,在經過第二個路由器時被丟棄,同樣從結果行中提取出路由器IP地址,
  3. 對第二個路由器的IP地址執行Ping命令,同樣從結果行中提取出資料包往返時間,
  4. 持續這個程序直至資料包到達目標主機,輸出以下結果行:
64 bytes from 113.108.239.226: icmp_seq=1 ttl=115 time=33.0 ms
  1. 對目標主機IP地址執行Ping命令,同樣從結果行中提取出資料包往返時間,模擬結束,
  2. 如果程序中資料包超過5s沒有回傳,則會輸出空的結果行,因而提取不出路由器IP地址,轉而輸出[* * *],
  3. 當躍點數超過設立的最大30個躍點數后仍未到達目標主機,則模擬結束,

相應的流程圖如下:
PingTraceRoute.png

具體代碼如下:

/**
 * TraceRoute命令
 * <p>
 * 由于Android的非Root設備不支持直接使用命令列工具API執行TraceRoute命令,因此改用執行Ping命令
 * 并通過限定TTL引數(IP包被路由器丟棄之前允許通過的最大網段數)來達到相等效果
 * 路由器地址通過正則運算式匹配從Ping回應內容中截取,
 * 路由耗時通過執行Ping命令前后時間戳對比估算
 */
class TraceRoute(
    /** 目標主機域名  */
    private val host: String
) {
    var TAG = this::class.java.simpleName

    companion object {
        /** IP包被路由器丟棄之前允許通過的最大網段數  */
        const val MAX_HOP = 30

        /** 正則運算式-路由器IP地址 */
        private const val REGEX_ROUTE_IP = "(?<=From )(?:[0-9]{1,3}\\.){3}[0-9]{1,3}"
        /** 正則運算式-目標主機IP地址 */
        private const val REGEX_HOST_IP = "(?<=from ).*(?=: icmp_seq=1 ttl=)"
        /** 正則運算式-資料包往返時間 */
        private const val REGEX_RRT = "(?<=time=).*?ms"
    }

    /**
     * 執行Ping命令模擬TraceRoute流程
     * -c count ping指定次數后停止ping;
     * -t 設定TTL(Time To Live,生存時間)為指定的值,該欄位指定IP包被路由器丟棄之前允許通過的最大網段數;
     */
    fun execute(callback: ExecuteCallback? = null) {
        val command = toString();
        callback?.onExecuting("% $command\n")

        callback?.onExecuting("traceroute to $host, 30 hos max, 40 byte packets\n")

        // 當前躍點數
        var hop = 1
        // 終止標識
        var done = false

        while (!done && hop <= MAX_HOP) {
            val pingResult = Ping(host, packetSize = 40, count = 1, ttl = hop, deadline = 5).execute()
            Log.d(TAG, "ping host ip: $pingResult \n\n")

            val lineBuilder = StringBuilder()
            lineBuilder.append(hop).append(".")

            // 用正則運算式匹配回應內容行
            val routerIpMatcher = matchRouterIp(pingResult)
            if (routerIpMatcher.find()) {   // 匹配到了路由器IP地址,列印路由器IP地址及到達該路由器的耗時
                val routerIp = subRouteIpString(routerIpMatcher)
                lineBuilder.append("\t\t").append(routerIp)

                val pingResult = Ping(host = routerIp, packetSize = 40, count = 3, deadline = 5).execute()
                Log.d(TAG, "ping route ip: $pingResult \n\n")
                matchAndAppendRTT(pingResult, lineBuilder)
            } else {    // 匹配不到
                val hostIpMatcher = matchHostIp(pingResult)
                if(hostIpMatcher.find()) {
                    val hostIp = hostIpMatcher.group()
                    lineBuilder.append("\t\t").append(hostIp)

                    val pingResult = Ping(host = hostIp, packetSize = 40, count = 3, deadline = 5).execute()
                    Log.d(TAG, "ping host ip: $pingResult \n\n")
                    matchAndAppendRTT(pingResult, lineBuilder)
                    done = true
                } else {
                    lineBuilder.append("\t\t *\t\t*\t\t* \t")
                }
            }

            callback?.onExecuting(lineBuilder.toString())

            hop++
        }
    }

    /**
     * 匹配并記錄資料包往返時間
     */
    private fun matchAndAppendRTT(pingResult: String, lineBuilder: StringBuilder) {
        val rttMatcher = matchRTT(pingResult)
        lineBuilder.append("\t\t")
        var i = 0
        while(i < 3) {
            if(rttMatcher.find()) {
                val rtt = rttMatcher.group()
                lineBuilder.append(rtt).append("\t\t")
            } else {
                lineBuilder.append("*").append("\t\t")
            }
            i++
        }
        lineBuilder.append("\t")
    }

    /**
     * 匹配路由器IP地址
     */
    private fun matchRouterIp(input: CharSequence) = Pattern.compile(REGEX_ROUTE_IP).matcher(input)

    /**
     * 匹配資料包往返時間
     */
    private fun matchRTT(input: CharSequence) = Pattern.compile(REGEX_RRT).matcher(input)

    /**
     * 匹配目標主機IP地址
     */
    private fun matchHostIp(input: CharSequence) =  Pattern.compile(REGEX_HOST_IP).matcher(input)

    /**
     * 截取路由器IP字串
     */
    private fun subRouteIpString(matcher: Matcher): String {
        var pingIp = matcher.group()
        val start = pingIp.indexOf('(')
        if (start >= 0) {
            pingIp = pingIp.substring(start + 1)
        }
        return pingIp
    }

    override fun toString(): String {
        return "traceroute $host"
    }
}

執行

TraceRoute命令執行結果.jpg

分析

第1行表示的是TraceRoute命令向目標主機發送最多30個躍點、40 bytes的資料包,

第2行起表示經過的路由器資訊,其中:

  • 最前面的數字表示的是躍點數,與所發送的資料包的TTL值一致
  • [172.16.88.1]表示的是經過的路由器IP地址
  • [4.69ms 9.29ms 9.24ms]表示的是所發送的三個資料包分別的往返時間
  • [*]表示的是沒有應答,當所發送的三個資料包中有任意一個超過5秒沒有應答時,則會以星號表示

如果目標主機可達,則會在到達某一躍點后結束,由此可知經過的路由器數量,如果目標主機不可達,則會在到達第30個躍點后結束,從而可知資料包被送到什么地方,

總結

為了有針對性地對網路進行優化,我們為App引入了一個輕量級的網路診斷模塊,主要借助的是Linux本身提供的檢測網路狀況的工具,在本篇中介紹的是Ping命令和TraceRoute命令,

  • Ping命令用于檢測到目標主機是否可達,通過結合資料包的往返時間和丟包率我們能初步地評估網路狀況,包括網路延遲/網路抖動/網路穩定性等情況,
  • TraceRoute命令用于定位到目標主機所經過的路由及其耗時,以定位網路故障發生的節點,由于Android的非Root設備不支持直接使用命令列工具執行TraceRoute命令,因此我們改用執行多次Ping命令來模擬TraceRoute的執行流程,

當然,網路狀況的復雜度往往超過我們的想象,還有很多這兩個命令不能覆寫到的故障場景,需要相應的工具才能進行排查,具體可以關注后續推出的文章,

「椎鋒陷陳」微信技術號現已開通,為了獲得第一手的技術文章推送,歡迎搜索關注!

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

標籤:其他

上一篇:關于我入坑Linux的那些事:安裝

下一篇:入門Electron,手把手教你撰寫完整實用案例

標籤雲
其他(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