主頁 > 移動端開發 > Android啟動那些事兒,從頭到尾拎一遍~

Android啟動那些事兒,從頭到尾拎一遍~

2021-12-10 08:47:24 移動端開發

前言

作為一個應用工程師,除了寫一些業務代碼,性能優化也是我們需要關注的點!

如果想要去做啟動優化,那么去了解啟動程序就是一個繞不過去的坎兒,

那么除了關于啟動程序的那些代碼,我們還應該去知道什么呢?

一、多行程那些事兒

在大家很早學習 Android 的時候,想必就知道,每一個 Android App 就代表著一個行程,

1. 為什么要開啟行程?

為什么要開啟一個新的行程呢?

在 Linux 中,執行緒和行程可沒多大區別,內核并沒有給執行緒準備特別的調度演算法或者特殊的資料結構,相反,執行緒被視為一個與其他行程共享某些資源的行程,

看到了嗎?執行緒之間可是會共享資源的,比如地址空間,你肯定不想發送微信的時候,其他應用都能知道,你發了什么吧,

2. 如何開啟一個應用行程

想要開啟一個應用行程可不容易,大家可能都聽說過,Linux 行程的創建都通過 fork() 方法,Android 自然也是同理,所有的應用行程的父行程都是 Zygote 行程,它是 Android 系統的兩大之主行程之一,

點擊 App 圖示后,Launcher 行程會通知 AMS(ActivityManagerService)AMS 就會呼叫自身的 startProcessLocked 方法,接著會呼叫 Process#start() 方法,透過層層嵌套,我們可以看到連接 Socket 的地方:

public static ZygoteState connect(LocalSocketAddress address) throws IOException {
    //...
    final LocalSocket zygoteSocket = new LocalSocket();
    try {
        zygoteSocket.connect(address);
        zygoteInputStream = new DataInputStream(zygoteSocket.getInputStream());
        zygoteWriter = new BufferedWriter(new OutputStreamWriter(zygoteSocket.getOutputStream()), 256);
    } catch (IOException ex) {
        //...
    }
    //...
    return new ZygoteState(zygoteSocket, zygoteInputStream, zygoteWriter, Arrays.asList(abiListString.split(",")));
}

Zygote 行程會開啟 Socket 等待連接,連上了以后,最侄訓觸發它的 Zygote#forkAndSpecialize() 方法:

static int forkAndSpecialize(int uid, int gid, int[] gids, int runtimeFlags,
                             int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose,
                             int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir,
                             boolean isTopApp, String[] pkgDataInfoList, String[] whitelistedDataInfoList,
                             boolean bindMountAppDataDirs, boolean bindMountAppStorageDirs) {
    ZygoteHooks.preFork();
    int pid = nativeForkAndSpecialize(
            uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose,
            fdsToIgnore, startChildZygote, instructionSet, appDataDir, isTopApp,
            pkgDataInfoList, whitelistedDataInfoList, bindMountAppDataDirs,
            bindMountAppStorageDirs);
    //...
    ZygoteHooks.postForkCommon();
    return pid;
}

可以看到,一個新的行程就創建好了,并回傳行程ID,

應用行程創建完了,應用還得和 AMSPMS 等其他服務或者行程通信啊,所以還得創建 Binder 執行緒池,不然就沒法和外界交流,

接下來,可到了重點部分了,系統會呼叫 RuntimeInit#findStaticMain() 方法,該方法最侄訓呼叫一個 Runnable 方法:

static class MethodAndArgsCaller implements Runnable {
    //...
    public void run() {
        try {
            mMethod.invoke(null, new Object[] { mArgs });
        } catch (IllegalAccessException ex) {
            // ...
        } catch (InvocationTargetException ex) {
            // ...
        }
    }
}

mMethod 就是 ActivityThread#main() 方法,引數都是之前傳遞進去的,最終通過反射去啟動我們的 ActivityThread

3. Android特有的多行程傳輸方式

你以為要分析 ActivityThread#main(),不,我們沒有,

上面我們談到了 Socket,但 Binder 才是 Android 中特有的多行程通信方式,并且在下面會被我多次提及,

往簡單了說,因為 Android 中的行程的記憶體地址是各自獨立的,但是它們都得通過內核的限制與硬體層進行交流,

如果不存在內核,就會發生很多糟糕的情況,比如說,一共有8GB的運行記憶體,起點讀書說我全要了,剩下的其他應用還怎么玩!

多行程通信也得通過內核,分給行程的一般都是虛擬地址,每一段虛擬地址都有實際的物理地址與之對應,當發生行程通信時,一般都會將資料從行程A的用戶空間(用戶可以操作的記憶體空間)復制到內核的緩沖區,再從內核的緩沖區復制到行程B的用戶空間,

多行程通信

但是 Binder 就不同了,Binder 會通過 mmap 將內核中的虛擬地址和用戶空間的虛擬地址做映射,如果行程B使用了 Binder,當資料從行程A復制到記憶體快取區的時候,整個通信程序就結束了,因為使用了記憶體映射,資料直接映射到了行程B的記憶體空間,

Binder通信

看到這兒,相信大家對 Binder 有了基礎的印象了,再來看一下它的使用流程即可,

Binder使用流程

Binder的實作是典型的 C\S 的結構:

  • Client:服務的請求方,
  • Server:服務的提供方,
  • Service Manager:為Server提供Binder的注冊服務,為Client提供Binder的查詢服務,ServerClientService Manager的通訊都是通過Binder,
  • Binder驅動:負責Binder通信機制的建立,提供一系列底層支持,

從上圖中,Binder 通信的程序是這樣的:

  1. ServerService Manager 中注冊:Server 行程在創建的時候,也會創建對應的Binder物體,如果要提供服務給 Client,就必須為 Binder 物體注冊一個名字,
  2. Client 通過 Service Manager 獲取服務:Client 知道服務中 Binder 物體的名字后,通過名字從 Service Manager 獲取 Binder 物體的參考,
  3. Client 使用服務與 Server 進行通信:Client 通過呼叫 Binder 物體與 Server 進行通信,

以上的知識可能有點淺顯,權當拋磚引玉,如果想要深入的學習,大家可自行了解,

看到這里,相信大家可能會有一個疑惑,Binder 是 Android 中特有的高效跨行程傳輸方式,Zygote 為什么沒有選擇 Binder 而是選擇 Socket 作為跨行程通信方式呢?

其實我也有點疑惑,大家可以看看下面的解答:

?

《知乎:zygote行程為什么不啟用binder機制?》

?

二、dex那些事兒

我們的知道,解壓一個應用的 apk 檔案,里面可能會有若干個 dex 檔案,

apk解壓以后

它是經工具軟體將所有的 Java 位元組碼檔案(.class)轉化形成的,

Dex檔案

這些 dex 檔案怎么就可以直接運行了呢?

轉化程序

從圖片中我們也可以看出來了,需要根據情況討論,因為 Android 的虛擬機分為 Dalvik 和 ART,

對于 Dalvik,安裝程序會提取出 .odex,不過這個只是優化過的位元組碼檔案,最后,在運行程序,Dalvik 還要通過 Jit(即時編譯) 還需要轉化成機器可以識別的機器碼,

對于 ART,它會在安裝程序中,決議成 .oat 檔案,這個檔案裝的可不是位元組碼,而是實實在在的機器碼,少了 Jit,整個運行速度和啟動速度都大大加快了,

不過呢,在ART中,整個安裝程序和應用升級都比較耗時,所以,在 Android 7.0 以后,既采用了 JIT 又采用了 AOT,簡單來說就是:

  • 第一次啟動采用 JIT,將熱點函式包含的 dex 決議成位元組碼,
  • 等到應用空閑的時候,再執行 AOT 程序,進行編譯,

經過了這一大串,dex 檔案就變成了機器可以識別的機器碼了,這里再提醒一下,Android 系統可不是直接識別 .class 檔案的,它需要將 .dex 映射到記憶體中,并通過 PathClassLoaderDexClassLoader 將 APP 中的類加載到記憶體里,

三、進入Android啟動程序

從上面我們已經知道了,我們的應用啟動入口在 ActivityThread#main() 方法,

1. 啟動程序中的通信機制

在正式了解啟動程序之前,我想我們還得了解一下啟動程序的通信機制,這也是啟動程序的主線,雖然我知道你們已經迫不及待了!

稍微了解過啟動程序的同學應該都知道 ActivityThreadApplicationThreadActivityManagerService 這三個角色:

角色作用
ActivityThread(下稱AT):應用的啟動入口,進行應用啟動的作業,
ActivityManagerService(下稱AMS):Android系統中最重要的系統服務之一,負責四大組件的啟動、管理和調度,同時也管理應用行程,
ApplicationThreadAMSAT 通信的橋梁,AT 的內部類,

上面我們了解過 Android 系統的兩大支柱行程之一的 Zygote 行程,另外一個就是 SystemServer 行程,

AMS 就處于 SystemServer 行程,還有很多大家耳熟能詳的服務都在這里邊,AMS 基于 Binder 實作的,所以 ActivityThread 能夠在應用行程聯系遠在 SystemServer 行程的 AMS

AMS 如何聯系 ActivityThread 呢?

巧了,也是 Binder,它的實作類是 ApplicationThread,不過它是一個匿名 Binder(沒有在 Server Manager 注冊的 Binder),之所以這么設計,我想更多的是基于安全方面考慮的,

ActivityThread通信

ATAMS 的通信正如圖上所描述的那樣,

2. 入口ActivityThread

終于到了代碼解釋環節了!

進入 main 方法:

public static void main(String[] args) {
    //...
    Looper.prepareMainLooper();
    //... 省略

    ActivityThread thread = new ActivityThread();
    thread.attach(false, startSeq);

    //...
    Looper.loop();
}

重點是 ActivityThread#attach() 方法,system 變數傳遞的是 false

private void attach(boolean system, long startSeq) {
    // ...
    if (!system) {
        //. ...
        RuntimeInit.setApplicationObject(mAppThread.asBinder());
        final IActivityManager mgr = ActivityManager.getService();
        try {
            mgr.attachApplication(mAppThread, startSeq);
        } catch (RemoteException ex) {
            // ...
        }
        // Watch for getting close to heap limit.
        BinderInternal.addGcWatcher(new Runnable() {
            @Override public void run() {
                //...
             }
        });
    } else {
        //...
    }

    //...
}

我們可以看到這個方法先是直接獲取 AMS,之后就直接將 ApplicationThread 物件傳了過去,

2. 進入AMS

點進 ActivityManagerService#attachApplication() 方法,該方法又呼叫了 ActivityManagerService#attachApplicationLocked() 方法,簡化一下:

private final boolean attachApplicationLocked(IApplicationThread thread, int pid) {
    ProcessRecord app;
    if (pid != MY_PID && pid >= 0) {
        synchronized (mPidsSelfLocked) {
            app = mPidsSelfLocked.get(pid); // 根據pid獲取ProcessRecord
        }
    }
    ...

    ApplicationInfo appInfo = app.instrumentationInfo != null
            ? app.instrumentationInfo : app.info;

    thread.bindApplication(processName, appInfo, providers, app.instrumentationClass,
            profilerInfo, app.instrumentationArguments, app.instrumentationWatcher,
            app.instrumentationUiAutomationConnection, testMode, enableOpenGlTrace,
            isRestrictedBackupMode || !normalMode, app.persistent,
            new Configuration(mConfiguration), app.compat,
            getCommonServicesLocked(app.isolated),
            mCoreSettingsObserver.getCoreSettingsLocked());
    ...

    return true;
}

注意一下,上面的方法在 AMS 中,就直接呼叫了 ApplicationThread#bindApplication() 方法了,這可是跨行程,

之前我們也提過了,ApplicationThread 也是 Binder,并且我們也沒有發現 ApplicationThreadService Manager 注冊 Binder 的任何代碼,這也更加證實它是一個匿名 Binder,不信,我們可以看一下 ApplicationThread 的代碼:

private class ApplicationThread extends IApplicationThread.Stub {
    //...
}

public class ActivityManagerService extends IActivityManager.Stub
        implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {
    // ...    
}

ApplicationThreadActivityManagerService 貌似都改成 AIDL 去實作 Binder 了,沒毛病!

3. 進入ApplicationThread

ApplicationThread 自己并沒有處理,而是交給了 H 的實體,從下面的代碼中,我們就能看出 H 是一個 Handler

public final void bindApplication(
        //... 引數省略
        ) {
    //...
    AppBindData data = new AppBindData();
    //...
    sendMessage(H.BIND_APPLICATION, data);
}

H 也是 ActivityThread 的內部類,于是直接呼叫 ActivityThread#handleBindApplication() 方法,簡化一下:

private void handleBindApplication(AppBindData data) {
    mBoundApplication = data;
    Process.setArgV0(data.processName);//設定行程名
    // ...
    //獲取LoadedApk物件
    data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo);
    // ...
    // 創建ContextImpl背景關系
    final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);
    //...
    try {
        // 此處data.info是指LoadedApk, 通過反射創建目標應用Application物件
        Application app = data.info.makeApplication(data.restrictedBackupMode, null);
        // 初始化ContentProvider
        installContentProviders(app, data.providers);
        mInitialApplication = app;
        // ...
        mInstrumentation.onCreate(data.instrumentationArgs);
        //回呼onCreate 
        mInstrumentation.callApplicationOnCreate(app);
    } finally {
        StrictMode.setThreadPolicy(savedPolicy);
    }
}

我們可以看見,主要做了以下幾件事:

  1. 創建 ContextImpl
  2. 創建 Application
  3. 初始化 ContentProvider
  4. 回呼 Application#onCreate()

4. 創建Application

data.info 的型別是 LoadedApk,它保存了很多跟 Apk 相關的資訊,

進入 LoadedApk#makeApplication() 方法,這是 Application 的創建方法,注意第二個引數傳了一個 null

public Application makeApplication(boolean forceDefaultAppClass,
                                   Instrumentation instrumentation) {
    Application app = null;
    //...
    try {
        final java.lang.ClassLoader cl = getClassLoader();
        // ...
        ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
        // ...
        app = mActivityThread.mInstrumentation.newApplication(
                cl, appClass, appContext);
    } catch (Exception e) {
        //...
    }
    if (instrumentation != null) {
        // instrumentation為空,所以走不進這個方法
        try {
            instrumentation.callApplicationOnCreate(app);
        } catch (Exception e) {
            //...
        }
    }
    return app;
}

實際的創建 Application 方法又交給了 mActivityThread.mInstrumentation,它的型別 Instrumentation,這個類可是一個大管家,無論是 Application 還是 Activity,最后都會交給它來處理:

public Application newApplication(ClassLoader cl, String className, Context context) {
    // 利用反射創建的Application
    Application app = getFactory(context.getPackageName())
            .instantiateApplication(cl, className);
    app.attach(context);
    return app;
}

Application 創建完成后還會呼叫 Application#attach() 方法,在這個方法,我們看到了熟悉的方法:

final void attach(Context context) {
    attachBaseContext(context);
    // ...
}

常用的方法 Application#attachBaseContext() 就是這個時候回呼的,

5. 初始化ContentProvider

這里沒什么好說的,只有一點,ContentProvider 的初始化時機要早于 Application#onCreate() 方法,

因為之前我就出現過在 ContentProvider#onCreate() 呼叫了某個 SDK,而這個 SDK 在 Application#onCreate() 才回完成初始化,結果就閃退了,

6. 呼叫Application#oncreate()

回到步驟3中的方法,mInstrumentation 的型別是 Instrumentation,它也是通過反射創建的,最侄訓執行 Instrumentation#callApplicationOnCreate 方法:

public void callApplicationOnCreate(Application app) {
    app.onCreate();
}

在這個方法中,就可以看到我們的 Application#onCreate() 方法得以執行,這也是我們通常用來初始化 SDK 的地方,

在這完成以后,就會通過 AMS 啟動第一個 Activity,還是同樣的通信方式,就不和大伙繼續展開了,

總結

看到這兒,相信大伙兒對應用已經有了初步的認識,如果有什么爭議的地方,評論區見!

再談一些關于知識點的事,可能大家會認為學習一個個知識點是比較枯燥的事,

比如去了解Linux內核的一些知識、去了解Apk的組成、去看應用的啟動流程,但是當你把這些看似不連貫的點都能夠連貫起來的時候,你就會發現這還是挺有意思的!

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

標籤:其他

上一篇:Jenkins中的Gerrit審稿人姓名和電子郵件

下一篇:springboot使用json檔案對mongo資料進行初始化

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