Microsoft CodePush 研究報(bào)告

1. 功能

1.1 三種更新策略選擇:

  • 靜默模式(slient mode): 無提示的直接安裝新的更新,可以更新起效的時(shí)間點(diǎn):
    • IMMEDIATE 立即生效,activity重啟;
    • ON_NEXT_RESTART 下一次應(yīng)用restart
    • ON_NEXT_RESUME 下一次應(yīng)用resume或者restart時(shí)
  • 選擇模式(active mode): 彈出一個(gè)對(duì)話框,提示用戶升級(jí),由用戶決定;
  • 自定義模式(custom mode): CodePush 提供了onSyncStatusChange 和 onDownloadProgress的回調(diào)函數(shù)的使用方法,可以通過自定回調(diào)函數(shù)以達(dá)到自定義的更新流程的功能

1.2 注冊(cè)使用

2. 基本架構(gòu)

涉及到Javascript部分和Java部分,值得注意的是兩部分均有網(wǎng)絡(luò)訪問服務(wù)器的功能,Javascript部分的網(wǎng)絡(luò)訪問請(qǐng)求主要是檢查是否有更新,而Java部分的網(wǎng)絡(luò)訪問請(qǐng)求則是下載更新包,猜測(cè)CodePush將檢查是否有更新放到Javascript里可能是考慮到該接口可能會(huì)有更改,比如報(bào)文字段,而下載更新包的接口實(shí)質(zhì)是給定資源地址,不存在請(qǐng)求參數(shù),所以變動(dòng)不大。

CodePush for RN
CodePush for RN

2.1 JavaScript部分


CodePush.js

熱更新js端入口文件,其實(shí)質(zhì)是一個(gè)對(duì)應(yīng)用的根組件的進(jìn)行裝飾的裝飾類.

// CodePush.js
var decorator = (RootComponent) => {
    return class CodePushComponent extends React.Component {
        componentDidMount() {
            // ...
            CodePush.sync(options,...);
        }
        render() {
            return <RootComponent {...this.props} ref={"rootComponent"} />;
        }
    }
};
if (typeof options === "function") {
// Infer that the root component was directly passed to us.
    return decorator(options);
} else {
    return decorator;
}
// 不使用CodePush的寫法
class myApp extends Component {
    //…
};
AppRegistry.registerCompent("myApp", () => myApp);

/************************************************/

// 使用CodePush的寫法
class myApp extends Component{
    //…
};

let CodePushOptions = { //設(shè)置一些CodePush相關(guān)屬性
    //…
    }; 
let CodePushApp = CodePush(CodePushOptions)(myApp); //裝飾myApp
AppRegistry.registerCompent("myApp", () => CodePushApp);

AcquistionManager

與后臺(tái)服務(wù)器通信的SDK文件,負(fù)責(zé)檢查更新請(qǐng)求的發(fā)送等內(nèi)容,注意下載更新包的請(qǐng)求并不式j(luò)s端完成,而是在Java端完成;

  • queryUpdateWithCurrentPackage() 根據(jù)當(dāng)前的安裝包信息查詢更新情況
  • reportStatusDeploy() 向服務(wù)器上傳信息
  • reportStatusDownload() 向服務(wù)器上傳信息

RestartManager

維持一個(gè)_restartQueue數(shù)組,提供以下四個(gè)函數(shù)

  • allow() 設(shè)置_allowed變量為true,如果隊(duì)列中不為空,則執(zhí)行restartApp(_restartQueue.shit(1))
  • clearPendingRestart() 清空隊(duì)列,即_restartQueue = []
  • disallow() 設(shè)置_allowed變量為false
  • restartApp() 進(jìn)一步調(diào)用NativeCodePush.restartApp()

NativeCodePush

JavaCodePushNativeModuleJS端的調(diào)用對(duì)象,用于調(diào)用Native的方法.

let NativeCodePush = require("react-native").NativeModules.CodePush;

2.2 Java部分


CodePush.java

一個(gè)ReactPackage的實(shí)現(xiàn),管理CodePushNativeModule

public class CodePush implements ReactPackage {
    // ...
    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactApplicationContext) {
        CodePushNativeModule codePushModule = new CodePushNativeModule(reactApplicationContext, this, mUpdateManager, mTelemetryManager, mSettingsManager);
        CodePushDialog dialogModule = new CodePushDialog(reactApplicationContext);

        List<NativeModule> nativeModules = new ArrayList<>();
        nativeModules.add(codePushModule);
        nativeModules.add(dialogModule);
        return nativeModules;
    }
}

CodePushNativeModule

定義對(duì)JS層暴露的方法:

  • getConfiguration() 獲取配置參數(shù),如appVersionserverUrl

  • getUpdateMetadata() 獲取當(dāng)前package的app.json的內(nèi)容

  • getNewStatusReport()

  • downloadUpdate() 下載更新,實(shí)質(zhì)執(zhí)行的CodePushUpdateManager.downloadUpdate()

  • installUpdate() 安裝更新,實(shí)質(zhì)執(zhí)行的是CodePushUpdateManager.installUpdate()

  • notifyApplicationReady()

  • recordStatusReported()

  • restartApp() 調(diào)用loadBundle()(利用反射):

    private void loadBundle() {
        mCodePush.clearDebugCacheIfNeeded();
        try {
            // #1) Get the ReactInstanceManager instance, which is what includes the
            //     logic to reload the current React context.
            final ReactInstanceManager instanceManager = resolveInstanceManager();
            if (instanceManager == null) {
                return;
            }
    
            String latestJSBundleFile = mCodePush.getJSBundleFileInternal(mCodePush.getAssetsBundleFileName());
    
            // #2) Update the locally stored JS bundle file path
            setJSBundle(instanceManager, latestJSBundleFile);
    
            // #3) Get the context creation method and fire it on the UI thread (which RN enforces)
            final Method recreateMethod = instanceManager.getClass().getMethod("recreateReactContextInBackground");
            new Handler(Looper.getMainLooper()).post(new Runnable() {
                @Override
                public void run() {
                    try {
                        recreateMethod.invoke(instanceManager);
                        mCodePush.initializeUpdateAfterRestart();
                    } catch (Exception e) {
                        // The recreation method threw an unknown exception
                        // so just simply fallback to restarting the Activity (if it exists)
                        loadBundleLegacy();
                    }
                }
            });
    
        } catch (Exception e) {
            // Our reflection logic failed somewhere
            // so fall back to restarting the Activity (if it exists)
            loadBundleLegacy();
        }
    }
    
  • saveStatusReportForRetry()

CodePushUpdateManager

管理更新包的下載、刪除等

  • downloadPackage( updateJsonObj ) 根據(jù)傳入的updateJsonObj對(duì)象構(gòu)造下載請(qǐng)求,并存儲(chǔ)結(jié)構(gòu)存儲(chǔ)下載的更新包
  • installPackage( updateJsonObj ) 根據(jù)傳入的updateJsonObj對(duì)象,更新codepush.json文件,codepush.json文件用于記錄當(dāng)前使用的和上一次使用的package的信息
  • rollbackPackage() 版本回滾,實(shí)質(zhì)是更新codepush.json文件

CodePushTelemetryManager

管理SharedPreference中的RETRY_DELOYMENT_KEYLAST_DELOPYMENT_KEY兩個(gè)值,提供的相應(yīng)的增刪改查操作;

  • RETRY_DELOYMENT_KEY
  • LAST_DELOPYMENT_KEY

SettingsManager

管理SharedPreference中的FAILED_UPDATES_KEYPENDING_UPDATE_KEY兩個(gè)值,提供的相應(yīng)的增刪改查操作;

  • FAILED_UPDATES_KEY 存儲(chǔ)安裝失敗的pacakage的信息(信息格式為json字符串)
  • PENDING_UPDATE_KEY 存儲(chǔ)等待安裝的pacakage的信息(信息格式為json字符串)

3. 數(shù)據(jù)存儲(chǔ)

3.1 存儲(chǔ)

  • SharedPerefence

    • FAILED_UPDATES_KEY
    • PENDING_UPDATE_KEY
    • RETRY_DELOYMENT_KEY
    • LAST_DELOPYMENT_KEY
  • 內(nèi)部存儲(chǔ)
    根目錄/data/data/com.xxx.xxx/files,即Context.getFilesDir().getAbsolutePath(),屬于應(yīng)用的私文件

- CodePush/
    - codepush.json
    - {hashcode}/
      - xxxx.bundle
      - app.json
    - {hashcode}/
      - xxxx.bundle
      - app.json
    - unzipped/ (臨時(shí),若下載的更新包是zip文件)

3.2 json結(jié)構(gòu)

// codepush.json 中的參數(shù)
{
    // ...
    currentPackage: "當(dāng)前package的hashCode值",
    priviousPackage: "上一個(gè)版本的package的hashcode值"
}
// app.json 中的參數(shù)
{
    // ...
    packageHash: "該package對(duì)應(yīng)的hashcode值",
    bundlePath: "JS bundle的相對(duì)位置"
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容