主頁 > 移動端開發 > Android V1簽名與校驗原理分析(全網最全最詳細)

Android V1簽名與校驗原理分析(全網最全最詳細)

2021-07-23 08:23:17 移動端開發

【前言】

Android Apk V1簽名方式是一開始時使用的簽名方案,不過V1簽名方式也稱作Jar簽名,顧名思義,就是V1簽名并不是Android獨有的簽名方式,而且在Android還沒出來時候,Jar 包也是用這種方式進行簽名檢驗的,直到Android 7.0開始才推出V2簽名,這個就是Android獨創的簽名方案,簽名與校驗的效率方面提高很多,后面Android 9.0又推出了V3簽名,再到Android 11推出了V4簽名方案

一、V1簽名程序分析

在這里插入圖片描述

1、MANIFEST.MF

遍歷Apk中除了META-INF目錄下以下檔案之外的所有檔案,

META-INF/MANIFEST.MF
META-INF/*.SF
META-INF/*.DSA
META-INF/*.RSA
META-INF/SIG-*

并對他們逐一使用SHA1或者SHA256演算法,計算出摘要值,Base64之后保存到 MANIFEST.MF檔案中,
最終 MANIFEST.MF檔案內容大致如下:
在這里插入圖片描述
1.1、前面3行是主屬性記錄

Manifest-Version: 1.0
Built-By: Signflinger
Created-By: Android Gradle 4.1.2

1.2、 其中每個檔案摘要前的SHA-256-Digest,這個由簽名apk時所用到簽名檔案的簽名演算法以及apk本身適配的最小SDK版本號共同決定,取值可能是SHA-1-DigestSHA-256-Digest,下面是簽名工具的代碼實作部分:

  public static DigestAlgorithm getSuggestedSignatureDigestAlgorithm(PublicKey signingKey, int minSdkVersion) throws InvalidKeyException {
        String keyAlgorithm = signingKey.getAlgorithm();
        if ("RSA".equalsIgnoreCase(keyAlgorithm)) {

            if (minSdkVersion < 18) {
                return DigestAlgorithm.SHA1;
            }
            return DigestAlgorithm.SHA256;
        }
        if ("DSA".equalsIgnoreCase(keyAlgorithm)) {

            if (minSdkVersion < 21) {
                return DigestAlgorithm.SHA1;
            }
            return DigestAlgorithm.SHA256;
        }
        if ("EC".equalsIgnoreCase(keyAlgorithm)) {
            if (minSdkVersion < 18) {
                throw new InvalidKeyException("ECDSA signatures only supported for minSdkVersion 18 and higher");
            }

            return DigestAlgorithm.SHA256;
        }
        throw new InvalidKeyException("Unsupported key algorithm: " + keyAlgorithm);
    }

1.3、上面說到META目錄下除了MENIFEST.MF*.SF*.RSA*.DSASIG-* 檔案之外都參與摘要簽名計算,而且也只是META根目錄的這些檔案不參與計算簽名, 在META目錄的子目錄中的所有檔案都是參與簽名的,看下圖也可得知:
在這里插入圖片描述
1.4、遍歷對apk中的檔案解壓之后,再利用SHA1或者SHA256演算法計算摘要簽名,然后Base64之后保存到MENIFEST.MF中,主要代碼實作邏輯如下:

	//解壓并更新摘要類
    private static class InflateSinkAdapter
            implements DataSink, Closeable {
        private final DataSink mDelegate;

    	.....

        public void consume(byte[] buf, int offset, int length) throws IOException {
            checkNotClosed();
            this.mInflater.setInput(buf, offset, length);
            if (this.mOutputBuffer == null) {
                this.mOutputBuffer = new byte[65536];
            }
            while (!this.mInflater.finished()) {
                int outputChunkSize;
                try {
                    //對檔案進行解壓,outputChunkSize為解壓之后的大小,mOutputBuffer保存解壓之后的資料
                    outputChunkSize = this.mInflater.inflate(this.mOutputBuffer);
                } catch (DataFormatException e) {
                    throw new IOException("Failed to inflate data", e);
                }
                if (outputChunkSize == 0) {
                    return;
                }
                //mDelegate為MessageDigestSink物件,對解壓之后的檔案進行摘要簽名更新
                this.mDelegate.consume(this.mOutputBuffer, 0, outputChunkSize);
                this.mOutputByteCount += outputChunkSize;
            }
        }
        .....
    }

	// 摘要資料更新類
    public class MessageDigestSink implements DataSink {
    	private final MessageDigest[] mMessageDigests;

    	public MessageDigestSink(MessageDigest[] digests) {
        	this.mMessageDigests = digests;
    	}


    	public void consume(byte[] buf, int offset, int length) {
        	for (MessageDigest md : this.mMessageDigests) {
            	md.update(buf, offset, length);
        	}
    	}


    	public void consume(ByteBuffer buf) {
        	int originalPosition = buf.position();
        	for (MessageDigest md : this.mMessageDigests) {
            	buf.position(originalPosition);
            	md.update(buf);
        	}
    	}   
	}

	//計算摘要
    private static void fulfillInspectInputJarEntryRequest(DataSource lfhSection, LocalFileRecord localFileRecord, ApkSignerEngine.InspectJarEntryRequest inspectEntryRequest) throws IOException, ZipFormatException {
        //解壓本地檔案資料出來并放入到MessageDigestSink中
        localFileRecord.outputUncompressedData(lfhSection, inspectEntryRequest.getDataSink());
        // 計算出檔案資料的摘要簽名
        inspectEntryRequest.done();

    }

1.5MANIFEST.MF的行最長只允許70個字符,這里面包括:Name 與 檔案名中間的冒號與空格(不包括\r\n,加上回車換行符共72個字符),要是超出70個字符就回車換行,然后在新行先寫入1個空格,再繼續寫入剩下的檔案名,代碼實作如下:

	private static final byte[] CRLF = new byte[]{13, 10};
    private static final int MAX_LINE_LENGTH = 70;
    
   	private static void writeAttribute(OutputStream out, String name, String value) throws IOException {
        writeLine(out, name + ": " + value);
    }

    
    private static void writeLine(OutputStream out, String line) throws IOException {
        byte[] lineBytes = line.getBytes(StandardCharsets.UTF_8);
        int offset = 0;
        int remaining = lineBytes.length;
        boolean firstLine = true;
        while (remaining > 0) {
            int chunkLength;
            if (firstLine) {
                //一行最高70個字符,超過70個就換行顯示
                chunkLength = Math.min(remaining, MAX_LINE_LENGTH);
            } else {
                //回車換行
                out.write(CRLF);
                //空格
                out.write(32);
                //因為這一行多了1個空格,所以最多只能69個字符
                chunkLength = Math.min(remaining, 69);
            }
            out.write(lineBytes, offset, chunkLength);
            offset += chunkLength;
            remaining -= chunkLength;
            firstLine = false;
        }
        //末尾回車換行
        out.write(CRLF);
    }

1.6、因為每次寫入1個資料塊就寫入2對回車換行符,所以在MANIFEST.MF末尾會有2個空行,下面看看每次寫入1個資料塊的代碼實作:

	//寫入資料塊
    public static void writeIndividualSection(OutputStream out, String name, Attributes attributes) throws IOException {
    	//寫入類似:Name: AndroidManifest.xml\r\n
        writeAttribute(out, "Name", name);

        if (!attributes.isEmpty()) {
            //寫入類似:SHA1-Digest: tJkLYKjlAku97m4hDC7yxlJK4XA=\r\n
            writeAttributes(out, getAttributesSortedByName(attributes));
        }
        //寫入:\r\n
        writeSectionDelimiter(out);
    }

	// 寫入回車換行符
    static void writeSectionDelimiter(OutputStream out) throws IOException {
        out.write(CRLF);
    }

2、CERT.SF

.SF檔案名是由簽名時候所傳入的引數v1-signer-nameks-key-alias或者keystore檔案名所決定,不是固定為CERT.SF,.SF檔案的主要作用是對MANIFEST.MF做校驗,防止MANIFEST.MF的資料被篡改,.SF檔案主要保存了MANIFEST.MF整個檔案的簽名摘要資訊以及每一個資料塊的簽名摘要資訊,資訊如下:
在這里插入圖片描述
2.1、第一個資料塊是.SF檔案的主屬性資訊

Signature-Version: 1.0
Created-By: 1.0 (Android)
SHA-256-Digest-Manifest: RF3bTyfX9uHZesEx91eumLw7u4EYS1cMc5emxi0npso=
X-Android-APK-Signed: 2, 3

其中,SHA-256-Digest-Manifest這個屬性的值是對MANIFEST.MF整個檔案SHA-256散列演算法計算出的摘要Base64的值;
X-Android-APK-Signed,這個屬性指定是否開啟比V1更高級的簽名方式,這里值為2,3,說明開啟了V2、V3簽名,那么應用安裝時候,假如跳過V2、V3簽名驗證(即破壞或者去掉V2、V3簽名資訊), 直接去驗證V1就會拋例外,這個是為了防止降級驗證

2.2、有些簽名工具還會在.SF主屬性中寫入SHA-256-Digest-Manifest-Main-Attributes,這個屬性的值是MANIFEST.MF主屬性塊摘要Base64的值,驗證簽名的時候會優先驗證這一塊的摘要,只有驗證通過之后才去驗證整個MANIFEST.MF檔案的資料摘要;對于資料塊這個概念定義需要注意一下,下面這樣一整塊是屬于MANIFEST.MF的一個主屬性塊,一起參與摘要計算:

Manifest-Version: 1.0
Created-By: 1.8.0_161 (Oracle Corporation)

上面把回車換行符顯式表示出來的話,實際是這樣:Manifest-Version: 1.0\r\nCreated-By: 1.8.0_161 (Oracle Corporation)\r\n\r\n,那么對這一整塊進行SHA-256演算法計算得到值為:
在這里插入圖片描述
可以用以下代碼計算:

 public static void main(String[] args) {
        String data= "Manifest-Version: 1.0\r\nCreated-By: 1.8.0_161 (Oracle Corporation)\r\n\r\n";
        MessageDigest md;
        try {
            md = MessageDigest.getInstance("SHA-256");
            byte[] digest = md.digest(data.getBytes("utf-8"));
            String base64Digest = Base64.getEncoder().encodeToString(digest);
            System.out.println("\n******************** 計算結果 ******************** ");
            System.out.println(base64Digest);
            System.out.println("************************************************** ");
        } catch (Exception e) {

        }

    }

這里一定要注意的是:計算摘要時候,一定要把最后的兩個回車換行符一起參與計算,后面各個檔案對應的摘要資料塊亦是如此,來看看簽名工具計算出來記錄在.SF檔案的數值:
在這里插入圖片描述
由于一行超過了70個字符,所以SHA-256-Digest-Manifest-Main-Attributes這一行換行了,洗掉空格跟換行之后,跟我們計算出來的值是一致的

2.3、接下來就是對各個檔案摘要資料塊的進行摘要簽名計算,計算方式跟主屬性摘要計算一樣,比如對于MANIFEST.MF下圖這一檔案摘要資料塊:
在這里插入圖片描述
字串表示為:Name: res/drawable-mdpi/ic_currency_mad.png\r\nSHA-256-Digest: tFS4pZtxah1Uc84XRqsMhYVcBxN0bdI9PKinhLj79UA=\r\n\r\n, 用SHA-256算出摘要再Base64的結果如下:
在這里插入圖片描述
看看.SF檔案對應的值,的確也是一致的
在這里插入圖片描述

3、CERT.RSA

CERT.RSA檔案名也不是固定的,命名規則跟.SF檔案一樣,而且后綴名也根據不同的簽名演算法,取不同的后綴名:.DSA.RSA.EC

3.1、CERT.RSA檔案實際上是PKCS#7格式的資料經過DER規則編碼之后的二進制檔案

  • PKCS#7,即密碼訊息語法標準(Cryptographic Message Syntax Standard),是公鑰加密標準(Public Key Cryptography Standards, PKCS)的1.5版本,資料格式大致如下:
    在這里插入圖片描述
  • DER(Distinguished Encoding Rules),即可分辨編碼規則,是ASN.1標準(Abstract Syntax Notation One,抽象語法標記)的一種編碼規則
  • ContentInfo這個欄位,理論上來說是存放待簽名內容,在這里的話,也就是對應.SF檔案資料,但是因為可以直接去讀取.SF檔案資料來進行簽名校驗,所以實際上ContentInfo并沒有保存.SF檔案資料

3.2PKCS#7中包含了X.509證書(密碼學里公鑰證書的格式標準),X.509證書格式如下
在這里插入圖片描述
可以通過openssl以下命令查看CERT.RSA檔案中包含的所有x509證書詳情

openssl pkcs7 -inform DER -in <*.RSA檔案路徑> -text -noout -print_certs

顯示資訊如下:
在這里插入圖片描述
3.3PKCS#7中包含的簽名者資訊SignerInfo資料結構如下:
在這里插入圖片描述
其中,EncryptedDigest中存盤的就是*.SF檔案資料SHA-1或者SHA-256演算法算出的摘要值,然后用私鑰簽名之后的資料
看看運行時signerInfo物件:
在這里插入圖片描述列印出來的資料如下:
在這里插入圖片描述

二、V1簽名校驗程序分析

1、先在META-INF目錄下查找后綴名為.DSA.RSA.EC 的簽名檔案,找到之后,根據簽名檔案的檔案名推出.SF檔案的檔案名,比如:找到META-INF目錄下檔案名為:KK.RSA的簽名檔案, 那么,可以推出.SF檔案的檔案名為:KK.SF

2、讀取.RSA簽名檔案資料,構造出PKCS#7格式的物件pkcs7, 從pkcs7X.509證書中讀取出公鑰pk,從pkcs7signerInfo中讀取出簽名資料encryptedDigest,然后用公鑰pk簽名資料encryptedDigest進行解密得到摘要資料digest, 讀取.SF檔案資料然后計算摘要得到摘要資料sfDigest,最后比對摘要資料sfDigest摘要資料digest是否相等,如果相等,說明.SF檔案沒有被篡改,否則簽名校驗失敗

3、假如.SF檔案中 Created-By的屬性值不存在:signtool字串,同時SHA-256-Digest-Manifest-Main-Attributes(或SHA-1-Digest-Manifest-Main-Attributes)的屬性值存在,那么先校驗MANIFEST.MF的主屬性資料塊的摘要是否跟SHA-256-Digest-Manifest-Main-Attributes屬性值相等,相等的話才繼續進行下一步的校驗,否則簽名校驗失敗

4、計算MANIFEST.MF整個檔案的摘要值,跟.SF檔案記錄的SHA-256-Digest-Manifest-Main-Attributes對應的值比較,假如相等,那么可以肯定MANIFEST.MF檔案沒有被篡改,否則需要進一步對MANIFEST.MF檔案中的每一個資料塊進行計算摘要值,然后跟.SF檔案中記錄的摘要值進行比對,如果每一個資料塊的摘要值都相等才進行下一步的校驗,否則簽名校驗失敗

5、先讀取AndroidManifest.xml檔案資料計算出摘要值,跟MANIFEST.MF中記錄的摘要值比對,如果相等,繼續遍歷所有檔案并計算出摘要值跟MANIFEST.MF中記錄的摘要值比對,否則簽名校驗失敗
在這里插入圖片描述

三、V1簽名校驗程序原始碼分析

因為V1簽名原始碼部分比較繞,所以這里對原始碼閱讀進行一個簡要的分析,以便大家快速找到自己想要閱讀的部分
在這里插入圖片描述

1verifyCertificate里面的包括:方法verifyBytes(校驗.SF檔案摘要是否跟.RSA檔案中記錄的摘要值一致)、verify(校驗MANIFEST.MF檔案的摘要是否.SF檔案的記錄一致)

2loadCertificates方法主要是校驗apk內的檔案計算出的摘要值是否跟MANIFEST.MF中記錄的一致,其中,計算摘要的詳細實作是在read方法中,從上圖可以看出,read方法最侄訓呼叫MessageDigest#digest方法計算出檔案的摘要值,然后呼叫verifyMessageDigest方法比對計算出來的摘要值跟MANIFEST.MF中記錄的是否一致

【擴展問題】

V1簽名的主要目的是為了防止apk內的檔案被篡改,在整個簽名程序中,我們可以看到先對Apk內每個檔案計算摘要記錄到MANIFEST.MF中,然后又對MANIFEST.MF整個檔案以及每個資料塊計算摘要記錄到.SF中,最后再對.SF整個檔案計算摘要并用私鑰簽名記錄到.RSA中,那么這個程序就會有一個疑問,為啥要多此一舉去創建一個.SF檔案呢?直接對MANIFEST.MF整個檔案計算摘要并用私鑰簽名記錄到.RSA中,不是一樣可以達到防止篡改的目的嗎?從簽名校驗的程序中分析可以得知,.SF存在的意義應該是在對MANIFEST.MF整個檔案的摘要值校驗失敗時,可以再對MANIFEST.MF中的每一個資料塊進行摘要計算,要是每一個資料塊的摘要校驗可以通過,那么簽名校驗依然是可以通過的,只不過這樣的一個校驗設計邏輯是基于什么方面的考慮呢?這個不得而知,有知道的小伙伴歡迎告知一二
在這里插入圖片描述

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

標籤:其他

上一篇:Android mPaaS 接入流程

下一篇:稍等,我手機幫你遠程除錯下代碼!

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