background

In the early stage, due to insufficient pre-research on CodePush, it was thought to support multi-packet hot update. As a result, in the actual application, it was found that the hot update bundle resources taken by CodePush did not distinguish between businesses, resulting in a blank screen when switching business scenarios, so CodePush source code needs to be reconstructed.

The fork github.com/microsoft/r… And submit the refactored code: github.com/hsl5430/rea…

Modify the record

The date of instructions The React Native version is recommended
2021-03-12 Synchronous updateGithub.com/microsoft/r…Latest code v7.0.0 V0.63.4 Androidx (support)
2019-09-06 Submit documents, etc.
2019-08-06 fork Github.com/microsoft/r…V5.6.1, and reconstruction V0.59.10 (last version of Androidx not supported)

statement

  • **[reactProject]** is used to identify the React Native project root directory
  • React Native is rn for short
  • React Native version: 0.63.4
  • CodePush version: 7.0.0
  • Projects that haven’t upgraded Androidx cangit checkout 670620a54d05c50464d195eecc061af07a4ccf8d, corresponding to react Native version: 0.59.10, CodePush version: 5.6.1

Before getting into CodePush refactoring, a few key points need to be addressed:

About the react. Gradle

Path: [reactProject] / node_modules/react – native/react. Gradle

In the [reactProject] / android/app/build. Gradle, rely on the react. Gradle

apply from: ".. /.. /node_modules/react-native/react.gradle"
Copy the code

What does this Gradle script mainly do? In fact, two tasks were created to generate bundle files and related resource files (images, etc.) during app building.

bundle${targetName}JsAndAssets

def currentBundleTask = tasks.create(
    name: "bundle${targetName}JsAndAssets",
    type: Exec) {
    group = "react"
    description = "bundle JS and assets for ${targetName}.". }Copy the code

Calling the Node script

commandLine(*nodeExecutableAndArgs, cliPath, bundleCommand, "--platform"."android"."--dev"."${devEnabled}"."--reset-cache"."--entry-file", entryFile, "--bundle-output", jsBundleFile, "--assets-dest", resourcesDir, *extraArgs)
Copy the code

Generate the bundle file to

file("$buildDir/generated/assets/react/${targetPath}")
Copy the code

Generate resource files to

file("$buildDir/generated/res/react/${targetPath}")
Copy the code

copy${targetName}BundledJs

def currentAssetsCopyTask = tasks.create(
    name: "copy${targetName}BundledJs",
    type: Copy) {
    group = "react"
    description = "copy bundled JS into ${targetName}.". }Copy the code

File copy bundle to $buildDir/intermediates/assets / ${targetPath} and $buildDir/intermediates/merged_assets / ${variant. The name} / merge ${tar getName}Assets/out

When an Android project builds mergeAssets, it packages the bundle files in the mergeAssets directory into assets.

So, if you are directly through the node script generates the bundle package, or by other developers will generate good bundle package to you, and on the [reactProject] / android/app/SRC/main/assets, actually don’t have to rely on the react. Gradle, You don’t have to run these two tasks every time you build

About codepush. Gradle

Path: [reactProject] / node_modules/react – native code – push/android/codepush gradle

In the [reactProject] / android/app/build. Gradle, rely on the codepush. Gradle

apply from: ".. /.. /node_modules/react-native-code-push/android/codepush.gradle"
Copy the code

What does this Gradle script mainly do?

Record the build time of apK files

gradle.projectsEvaluated {
    android.buildTypes.each {
        // to prevent incorrect long value restoration from strings.xml we need to wrap it with double quotes
        // https://github.com/microsoft/cordova-plugin-code-push/issues/264
        it.resValue 'string'."CODE_PUSH_APK_BUILD_TIME", String.format("\"%d\"", System.currentTimeMillis())
    }
    ...
}
Copy the code

At first, when I accessed CodePush, I did not rely on CodePush. Gradle and did not report any errors. As a result, I crashed directly after running CodePush. Call the getBinaryResourcesModifiedTime

long getBinaryResourcesModifiedTime(a) {
    try {
        String packageName = this.mContext.getPackageName();
        int codePushApkBuildTimeId = this.mContext.getResources().getIdentifier(CodePushConstants.CODE_PUSH_APK_BUILD_TIME_KEY, "string", packageName);
        // replace double quotes needed for correct restoration of long value from strings.xml
        // https://github.com/microsoft/cordova-plugin-code-push/issues/264
        String codePushApkBuildTime = this.mContext.getResources().getString(codePushApkBuildTimeId).replaceAll("\" "."");
        return Long.parseLong(codePushApkBuildTime);
    } catch (Exception e) {
        throw new CodePushUnknownException("Error in getting binary resources modified time", e); }}Copy the code

Apparently, it crashed without getting CODE_PUSH_APK_BUILD_TIME.

Generate CodePushHash

def generateBundledResourcesHash = tasks.create(
        name: "generateBundledResourcesHash${targetName}",
        type: Exec) {
    commandLine (*nodeExecutableAndArgs, "${nodeModulesPath}/react-native-code-push/scripts/generateBundledResourcesHash.js", resourcesDir, jsBundleFile, jsBundleDir)
    enabled config."bundleIn${targetName}" ||
    config."bundleIn${variant.buildType.name.capitalize()}"? : targetName.toLowerCase().contains("release")}Copy the code

Call node script generateBundledResourcesHash. Js (children’s shoes are interested can dig the hash is how to generate), was introduced into the bundle package and the corresponding image resources such as files, generate the corresponding hash, and in the form of a file to store the hash, Stored in the [reactProject] / android/app/build/generated/assets/react/debug/CodePushHash

[reactProject] / android/app/build/generated/assets/react/debug ├ ─ ─ CodePushHash └ ─ ─ index. The android. The bundleCopy the code

Gradle does not rely on CodePush. No error is reported when CodePush is first accessed. As a result, a log is printed under logcat after running

Unable to get the hash of the binary's bundled resources - "codepush.gradle" may have not been added to the build definition.
Copy the code

Locate the CodePush source code

public static String getHashForBinaryContents(Context context, boolean isDebugMode) {
    try {
        return CodePushUtils.getStringFromInputStream(context.getAssets().open(CodePushConstants.CODE_PUSH_HASH_FILE_NAME));
    } catch (IOException e) {
        try {
            return CodePushUtils.getStringFromInputStream(context.getAssets().open(CodePushConstants.CODE_PUSH_OLD_HASH_FILE_NAME));
        } catch (IOException ex) {
            if(! isDebugMode) {// Only print this message in "Release" mode. In "Debug", we may not have the
                // hash if the build skips bundling the files.
                CodePushUtils.log("Unable to get the hash of the binary's bundled resources - \"codepush.gradle\" may have not been added to the build definition."); }}return null; }}Copy the code

This is part of how react gets the bundle resource information. What does react do with this hash?

In short, since CodePush is connected, it is necessary to rely on codepush.gradle according to the documentation requirements to ensure that apK file build time and CodePushHash generation can be properly recorded.

About directory Structure

built-inPath to bundle and image resources

├─ exercises, ├─ exercises, exercises, exercises, exercises, exercises, exercises ├ ─ drawable - xhdpi │ ├ ─ ic_back. PNG │ └ ─... └ ─...Copy the code

CodePush hot updatePath to bundle and image resources

/data/data/ files ├─ CodePush ├─ CodePush. Json// Record some basic information about the current hot update resource pack
     ├─ [hash]
     │   ├─ app.json // Record some basic information about the hot update resource pack corresponding to the hash│ └ ─ [version]// Version here is the hot update version defined by CodePush, not the APP version│ ├ ─ index. Android. Bundle │ ├ ─ drawable - mdpi │ │ ├ ─ ic_back. PNG │ │ └ ─... │ ├ ─ drawable - xhdpi │ │ ├ ─ ic_back. PNG │ │ └ ─... │ └ ─... ├ ─ [hash2] └ ─...Copy the code

Codepush.getjsbundlefile (“index.android.bundle”) reads a bundle named index.android.bundle. Specific to view method CodePush. GetJSBundleFileInternal (String assetsBundleFileName)


Why CodePush does not support multiple services and multiple packages

  • Gradle and codepush. Gradle are implemented for a single bundle
  • It looks like you can put multiple bundles with different names in assets, but the generated CodePushHash file is placed in the same directory as the bundle. The name of the file is “CodePushHash”. There is no way that a bundle corresponds to a CodePushHash file
  • Hot update, the CodePush directory uses hash values as folders to distinguish hot update packages, but since there is no business differentiation, this means that hot update packages are found in error. The AActivity page loads the a.ndroid. bundle embedded in Assets and downloads hot updates (forcing updates and applying updates the next time you go to the page). Then visit page B (BActivity) to load the b.android.bundle embedded in assets, download the hot update, and then visit page A. As A result, the hot update of page B is loaded, and the page screen is blank. In short, it is a hot update resource that has been applied for the last download.
String jsBundleFile = CodePush.getJSBundleFile("a.android.bundle"); The d (" JSBundleFile ", JSBundleFile);// The value of Log is as follows: / data/data/com. React. The demo/files/CodePush / 4247860 b1dc848a13e6c980ac9bee9323de4210951ea917bc68f7346575370e2 1.0.0 / b.a ndroi d.bundle

Copy the code

Therefore, to support multiple services and multiple packages, the React Native environment and codePush environment of each business must be isolated to ensure that they do not affect each other.


CodePush subcontracting is implemented step by step

Let’s start with a mind map

1. Project catalog management

Built-in bundles and image resources are stored in separate directories

  1. Create a new Module –> Android Library –> rnLib to manage RN-related code
  2. Create a react directory under rnLib to store bundles and images for each service
  3. In the React directory, use the bundle file name of the service as the directory name to create a subdirectory, for example, a.arndroid. bundle
  4. Service A. ndroid.bundle stores bundle files and image resources. The same applies to other services
├─ ├─ exercises - ├─ exercises - ├─ exercises - ├─ exercises - ├─ exercises - ├─ exercises - ├─ exercises - │ ├─ exercises - ├─ exercises// The name of the picture should be different from that of the business.
    │   │   └─ ...           // Otherwise, apK will report a resource merge conflict error when packaging│ └ ─... └ ─ b.a ndroid. Bundle (directory) ├ ─ b.a ndroid. The bundle (documents) ├ ─ drawable - mdpi │ ├ ─ b_ic_back. PNG │ └ ─... └ ─...Copy the code

The main reason for doing this is to facilitate the management of these RN resources

Second, compile and package processing

React. gradle and codepush.gradle scripts are reconstructed based on the directory structure above

React. gradle refactoring supports multiple packages

The react directory is not like assets directory, res directory, etc. It can be recognized by the Android project, and can be entered into apK file during the packaging process. Can be recognized by the Android project. Refactored code:

/ /node_modules/react-native/react.gradle * Supports multi-service subcontracting. The /react directory uses the service bundle name as the folder name to store the bundle files and associated resource files of each service * */
def config = project.hasProperty("react") ? project.react : []

def reactRoot = file(config.root ?: ".. /.. /") def inputExcludes = config.inputExcludes ? : ["android/**"."ios/**"]

// React js bundle directories
def jsBundleAndResDir = file("$rootDir/rnLib/react")
def generatedDir = "$buildDir/generated"

afterEvaluate {

    def isAndroidLibrary = plugins.hasPlugin("com.android.library")
    def variants = isAndroidLibrary ? android.libraryVariants : android.applicationVariants
    variants.all { def variant ->
        // Create variant and target names
        def targetName = variant.name.capitalize()
        def targetPath = variant.dirName

        def jsBundleRootDir = file("$generatedDir/assets/react/${targetPath}")

        JsBundleAndResDir {// go through all the services under jsBundleAndResDir
        ArrayList<File> sources = new ArrayList<>()
        ArrayList<File> jsBundleDirs = new ArrayList<>()
        ArrayList<File> resourcesDirs = new ArrayList<>()
        files(jsBundleAndResDir.listFiles(new FilenameFilter() {
            @Override
            boolean accept(File dir, String name) {
                // Customize filtering rules
                return name.endsWith(".android.bundle")
            }
        }
        )).each { source ->
            // Business directory
            if(! source.exists() || ! source.directory) {return
            }
            sources.add(source)

            def jsBundleRelativeDir = "assets/react/${targetPath}/${source.name}"
            def resourcesRelativeDir = "res/react/${targetPath}/${source.name}"

            def jsBundleDir = file("${generatedDir}/${jsBundleRelativeDir}")
            def resourcesDir = file("${generatedDir}/${resourcesRelativeDir}")

            jsBundleDirs.add(jsBundleDir)
            resourcesDirs.add(resourcesDir)
        }

        if (sources.isEmpty()) {
            return
        }

        // This is different from the react-native/react.gradle implementation
        ${targetName}JsAndAssets = "bundle${targetName}JsAndAssets"
        def currentBundleTask = tasks.create(
                name: "bundle${targetName}JsAndAssets",
                type: Copy) {
            group = "react"
            description = "bundle JS and assets for ${targetName}."

            // Set up inputs and outputs so gradle can cache the result
            inputs.files fileTree(dir: reactRoot, excludes: inputExcludes)
            outputs.dir(jsBundleRootDir)

            JsBundleAndResDir {// go through all the services under jsBundleAndResDir
            files(sources.toArray()).each { source ->
                // Business directory
                def jsBundleRelativeDir = "assets/react/${targetPath}/${source.name}"
                def resourcesRelativeDir = "res/react/${targetPath}/${source.name}"

                def jsBundleDir = file("${generatedDir}/${jsBundleRelativeDir}")
                def resourcesDir = file("${generatedDir}/${resourcesRelativeDir}")

                // Create dirs if they are not there (e.g. the "clean" task just ran)
                jsBundleDir.deleteDir()
                jsBundleDir.mkdirs()
                resourcesDir.deleteDir()
                resourcesDir.mkdirs()

                // Set up outputs so gradle can cache the result
                //outputs.dir(jsBundleDir)
                outputs.dir(resourcesDir)

                into(generatedDir)
                // Copy the JsBundle under react/[bundle name] to the specified directory
                into(jsBundleRelativeDir) {
                    from(source)
                    include '*.bundle'
                }
                // Copy the drawable under react/[bundle name] to the specified directory
                into(resourcesRelativeDir) {
                    from(source)
                    include 'drawable*/*'
                }
            }

            enabled config."bundleIn${targetName}" ||
                    config."bundleIn${variant.buildType.name.capitalize()}"? : targetName.toLowerCase().contains("release")}// Expose a minimal interface on the application variant and the task itself:
        variant.ext.bundleJsAndAssets = currentBundleTask
        currentBundleTask.ext.generatedResFolders = files(resourcesDirs.toArray()).builtBy(currentBundleTask)
        currentBundleTask.ext.generatedAssetsFolders = files(jsBundleDirs.toArray()).builtBy(currentBundleTask)

        // registerGeneratedResFolders for Android plugin 3.x
        if (variant.respondsTo("registerGeneratedResFolders")) {
            variant.registerGeneratedResFolders(currentBundleTask.generatedResFolders)
        } else {
            variant.registerResGeneratingTask(currentBundleTask)
        }
        variant.mergeResourcesProvider.get().dependsOn(currentBundleTask)

        // packageApplication for Android plugin 3.x
        def packageTask = variant.hasProperty("packageApplication")? variant.packageApplicationProvider.get() : tasks.findByName("package${targetName}")
        if (variant.hasProperty("packageLibrary")) {
            packageTask = variant.packageLibrary
        }

        Pre bundle build Task for Android Plugin 3.2+
        def buildPreBundleTask = tasks.findByName("build${targetName}PreBundle")

        def currentAssetsCopyTask = tasks.create(
                name: "copy${targetName}BundledJs",
                type: Copy) {
            group = "react"
            description = "copy bundled JS into ${targetName}."

            into("$buildDir/intermediates")
            into("assets/${targetPath}") {
                from(jsBundleRootDir)
            }

            // Workaround for Android Gradle Plugin 3.2+ new asset directory
            into("merged_assets/${variant.name}/merge${targetName}Assets/out") {
                from(jsBundleRootDir)
            }

            // mergeAssets must run first, as it clears the intermediates directory
            dependsOn(variant.mergeAssetsProvider.get())

            enabled(currentBundleTask.enabled)
        }

        packageTask.dependsOn(currentAssetsCopyTask)
        if(buildPreBundleTask ! =null) {
            buildPreBundleTask.dependsOn(currentAssetsCopyTask)
        }
    }
}
Copy the code

Codepush. gradle refactoring supports multiple packages

  1. Task “bundle${targetName}JsAndAssets” is copied to the bundle directory and image directory
  2. Traverse the directory, called node script generateBundledResourcesHash. Generate CodePushHash js

Refactored code:

/* * Adapted from https://raw.githubusercontent.com/facebook/react-native/d16ff3bd8b92fa84a9007bf5ebedd8153e4c089d/react.gradle * * Rewrite/node_modules/react - native code - push/android/codepush gradle implementation logic: * support multiple business subcontract, different business generate respective needs CodePushHash * * /
import java.nio.file.Paths

def config = project.hasProperty("react")? project.react : []void runBefore(String dependentTaskName, Task task) {
    Task dependentTask = tasks.findByPath(dependentTaskName)
    if(dependentTask ! =null) {
        dependentTask.dependsOn task
    }
}

gradle.projectsEvaluated {
    android.buildTypes.each {
        // to prevent incorrect long value restoration from strings.xml we need to wrap it with double quotes
        // https://github.com/microsoft/cordova-plugin-code-push/issues/264
        it.resValue 'string'."CODE_PUSH_APK_BUILD_TIME", String.format("\"%d\"", System.currentTimeMillis())
    }

    android.applicationVariants.all { variant ->
        if(! variant.hasProperty("bundleJsAndAssets")) {
            return
        }

        Task reactBundleTask = variant.bundleJsAndAssets
        if (! reactBundleTask.hasProperty("generatedAssetsFolders")) {
            return
        }

        def jsBundleDirs = reactBundleTask.generatedAssetsFolders
        def resourcesDirs = reactBundleTask.generatedResFolders

        if (jsBundleDirs.isEmpty()) {
            return
        }

        def nodeModulesPath
        if (config.root) {
            nodeModulesPath = Paths.get(config.root, "/node_modules")}else if (project.hasProperty('nodeModulesPath')) {
            nodeModulesPath = project.nodeModulesPath
        } else {
            nodeModulesPath = ".. /.. /node_modules"
        }
        // Additional node commandline argumentsdef nodeExecutableAndArgs = config.nodeExecutableAndArgs ? : ["node"]

        def targetName = variant.name.capitalize()

        for (int i = 0; i < jsBundleDirs.size(); i++) {
            File jsBundleDir = jsBundleDirs[i]
            File resourcesDir = resourcesDirs[i]
            // the name of jsBundleFile is exactly the name of the directory
            File jsBundleFile = file("${jsBundleDir}/${jsBundleDir.name}")

            def indexOf = jsBundleFile.name.indexOf('. ')
            def taskSuffix = jsBundleFile.name.substring(0.1).toUpperCase() + jsBundleFile.name.substring(1, indexOf)

            // Make this task run right after the bundle task
            def generateBundledResourcesHash = tasks.create(
                    name: "generateBundledResourcesHash${targetName}${taskSuffix}",
                    type: Exec) {
                group = "react"
                description = "generate CodePushHash for ${jsBundleFile.name}."
                commandLine(*nodeExecutableAndArgs, "${nodeModulesPath}/react-native-code-push/scripts/generateBundledResourcesHash.js", resourcesDir.absolutePath, jsBundleFile, jsBundleDir.absolutePath)
                enabled(reactBundleTask.enabled)
            }

            generateBundledResourcesHash.dependsOn(reactBundleTask)
            runBefore("processArmeabi-v7a${targetName}Resources", generateBundledResourcesHash)
            runBefore("processX86${targetName}Resources", generateBundledResourcesHash)
            runBefore("processUniversal${targetName}Resources", generateBundledResourcesHash)
            runBefore("process${targetName}Resources", generateBundledResourcesHash)
        }
    }
}
Copy the code

Generated APK package, decompress to view the resource directory structure

Apk ├ ─ assets │ ├ ─ a.a ndroid. The bundle (directory) │ │ ├ ─ a.a ndroid. The bundle (documents) │ │ ├ ─ CodePushHash │ │ └ ─... │ └ ─ b.a ndroid. Bundle (directory) │ ├ ─ b.a ndroid. The bundle (documents) │ ├ ─ CodePushHash │ └ ─... └ ─ res ├ ─ drawable mdpi - v4 │ ├ ─ a_ic_back. PNG │ ├ ─ b_ic_back. PNG │ └ ─... ├ ─ drawable xhdpi - v4 │ ├ ─ a_ic_back. PNG │ ├ ─ b_ic_back. PNG │ └ ─... └ ─...Copy the code

Refactoring the Java layer code

After refactoring gradle scripts, the Java layer code is adjusted accordingly according to the resource directory structure in APK

Change the parameter of getJSBundleFile

CodePush. GetJSBundleFile (String assetsBundleFileName) internal call CodePush. GetJSBundleFileInternal (String assetsBundleFileName), The point of reconstruction here is to change assetsBundleFileName to assetsBundleFilePath, do not write the dead file name, should support the path of assets file

Before the change

public class CodePush implements ReactPackage {

    privateString mAssetsBundleFileName; .public String getAssetsBundleFileName(a) {
        return mAssetsBundleFileName;
    }

    public String getJSBundleFileInternal(String assetsBundleFileName) {
        this.mAssetsBundleFileName = assetsBundleFileName;
        String binaryJsBundleUrl = CodePushConstants.ASSETS_BUNDLE_PREFIX + assetsBundleFileName;

        String packageFilePath = null;
        try {
            packageFilePath = mUpdateManager.getCurrentPackageBundlePath(this.mAssetsBundleFileName);
        } catch (CodePushMalformedDataException e) {
            // We need to recover the app in case 'codepush.json' is corruptedCodePushUtils.log(e.getMessage()); clearUpdates(); }... }}Copy the code

The modified

public class CodePush implements ReactPackage {

    private String mAssetsBundleFileName;
    privateString mAssetsBundleFilePath; .public String getAssetsBundleFileName(a) {
        return mAssetsBundleFileName;
    }

    public String getAssetsBundleFilePath(a) {
        return mAssetsBundleFilePath;
    }

    public String getAssetsBundleFileDir(a) {
        try {
            return new File(getAssetsBundleFilePath()).getParent();
        } catch (Exception e) {
            return null; }}public String getJSBundleFileInternal(String assetsBundleFileName) {
        // Assets file path is supported
        this.mAssetsBundleFilePath = assetsBundleFileName;
        File file = new File(assetsBundleFileName);
        mAssetsBundleFileName = file.getName();
        
        String binaryJsBundleUrl = CodePushConstants.ASSETS_BUNDLE_PREFIX + assetsBundleFileName;

        String packageFilePath = null;
        try {
            packageFilePath = mUpdateManager.getCurrentPackageBundlePath(this.mAssetsBundleFileName);
        } catch (CodePushMalformedDataException e) {
            // We need to recover the app in case 'codepush.json' is corruptedCodePushUtils.log(e.getMessage()); clearUpdates(); }... }}Copy the code

At the same time, to check getJSBundleFileInternal calls and found CodePushNativeModule. LoadBundle () call

public class CodePushNativeModule extends ReactContextBaseJavaModule {...private void loadBundle(a) {... String latestJSBundleFile = mCodePush.getJSBundleFileInternal(mCodePush.getAssetsBundleFileName()); . }}Copy the code

The modified

public class CodePushNativeModule extends ReactContextBaseJavaModule {...private void loadBundle(a) {... String latestJSBundleFile = mCodePush.getJSBundleFileInternal(mCodePush.getAssetsBundleFilePath()); . }}Copy the code

Accordingly, the upper layer can be called like this:

CodePush.getJSBundleFile("a.android.bundle/a.android.bundle");
Copy the code

Modify the logical code to get CodePushHash

Before the change

public class CodePushUpdateUtils {

    public static String getHashForBinaryContents(Context context, boolean isDebugMode) {
        try {
            return CodePushUtils.getStringFromInputStream(context.getAssets().open(CodePushConstants.CODE_PUSH_HASH_FILE_NAME));
        } catch (IOException e) {
            try {
                return CodePushUtils.getStringFromInputStream(context.getAssets().open(CodePushConstants.CODE_PUSH_OLD_HASH_FILE_NAME));
            } catch (IOException ex) {
                if(! isDebugMode) {// Only print this message in "Release" mode. In "Debug", we may not have the
                    // hash if the build skips bundling the files.
                    CodePushUtils.log("Unable to get the hash of the binary's bundled resources - \"codepush.gradle\" may have not been added to the build definition."); }}return null; }}}Copy the code

Since this method is only called in CodePushNativeModule, I add getHashForBinaryContents directly to CodePushNativeModule:

public class CodePushNativeModule extends ReactContextBaseJavaModule {
    
    private String mBinaryContentsHash = null;
    privateCodePush mCodePush; .public CodePushNativeModule(ReactApplicationContext reactContext, CodePush codePush, CodePushUpdateManager codePushUpdateManager, CodePushTelemetryManager codePushTelemetryManager, SettingsManager settingsManager) {
        super(reactContext); mCodePush = codePush; . mBinaryContentsHash = getHashForBinaryContents(codePush); . }/** * overwrite {@linkCodePushUpdateUtils#getHashForBinaryContents(Context, Boolean)}@link CodePushConstants#CODE_PUSH_HASH_FILE_NAME}
     */
    public String getHashForBinaryContents(CodePush codePush) {
        // return CodePushUpdateUtils.getHashForBinaryContents(getReactApplicationContext(), codePush.isDebugMode());
        Context context = codePush.getContext();
        String assetsBundleDir = codePush.getAssetsBundleFileDir();
        String codePushHashFilePath;
        try {
            codePushHashFilePath = new File(assetsBundleDir, CodePushConstants.CODE_PUSH_HASH_FILE_NAME).getPath();
            return CodePushUtils.getStringFromInputStream(context.getAssets().open(codePushHashFilePath));
        } catch (IOException e) {
            try {
                codePushHashFilePath = new File(assetsBundleDir, CodePushConstants.CODE_PUSH_OLD_HASH_FILE_NAME).getPath();
                return CodePushUtils.getStringFromInputStream(context.getAssets().open(codePushHashFilePath));
            } catch (IOException ex) {
                if(! codePush.isDebugMode()) {// Only print this message in "Release" mode. In "Debug", we may not have the
                    // hash if the build skips bundling the files.
                    CodePushUtils.log("Unable to get the hash of the binary's bundled resources - \"codepush.gradle\" may have not been added to the build definition."); }}return null; }}}Copy the code

Modify the logic to get CodePushPath

CodePushUpdateManager getCodePushPath, this is very important! CodePush has extra references inside. The root directory for reading and writing hot update packages is specified. Therefore, subdirectories should be added to differentiate services

Before the change

public class CodePushUpdateManager {

    private String mDocumentsDirectory;

    public CodePushUpdateManager(String documentsDirectory) { mDocumentsDirectory = documentsDirectory; }...private String getCodePushPath(a) {
        String codePushPath = CodePushUtils.appendPathComponent(getDocumentsDirectory(), CodePushConstants.CODE_PUSH_FOLDER_PREFIX);
        if (CodePush.isUsingTestConfiguration()) {
            codePushPath = CodePushUtils.appendPathComponent(codePushPath, "TestPackages");
        }
        returncodePushPath; }}Copy the code

The modified

public class CodePushUpdateManager {

    private String mDocumentsDirectory;
    private CodePush mCodePush;

    public CodePushUpdateManager(String documentsDirectory, CodePush codePush) { mDocumentsDirectory = documentsDirectory; mCodePush = codePush; }...private String getCodePushPath(a) {
        String codePushPath = CodePushUtils.appendPathComponent(getDocumentsDirectory(), CodePushConstants.CODE_PUSH_FOLDER_PREFIX);
        if(! TextUtils.isEmpty(mCodePush.getAssetsBundleFileName())) {// CodePush/[bundle name]/ files/CodePush/[bundle name]/
            codePushPath = CodePushUtils.appendPathComponent(codePushPath, mCodePush.getAssetsBundleFileName());
        }
        if (CodePush.isUsingTestConfiguration()) {
            codePushPath = CodePushUtils.appendPathComponent(codePushPath, "TestPackages");
        }
        returncodePushPath; }}Copy the code

The above refactoring is to solve the path problem, in short, to add a subdirectory (named after the business bundle name) to differentiate services and load different bundles.

Isolate bundle environments

Sorted out where the foundation needs to be adjusted

  • ReactNativeHost was originally instantiated in the application, but now each Activity is required to instantiate a ReactNativeHost
  • CodePush uses a singleton design pattern, so singletons and associated static variables have to be removed,

Codepush.java has a big change. After reconstruction, errors are reported where static methods are called using class names, which need to be solved one by one. Instead, non-static methods are called using variables. Code: github.com/hsl5430/rea… .