React Native 熱加載指定目錄bundle資源

目錄

搭建服務提供下載資源

資源下載

重新指定RN資源加載路徑並加載相關資源


ReactNative在Android環境中運行時候會先將RN相關資源打包併合併到android應用的assets目錄,相關資源包內容如下(包括但不限於一下資源,這裏是demo,比較簡單):

Drawable-x包下面主要是一些圖片的資源

raw包配置相關資源

index.androd.bindlejs相關代碼打包後的特殊格式文件,其中包含應用相關信息等,也只android應用主要加載的目標。

 

 

apk運行起來後,需要加載RN相關資源的時候應用默認會去assets目錄下找,這個目錄是固定不變,而且從雲端下拉的資源也無法放入,但云端下啦的資源可以放在固定的目錄下,所以本地熱加載RN資源的重點是重新指定資源加載路徑,並讓android應用將這些資源加載運行起來。

 

思路:

  1. 搭建本地服務器,事先準備好RN打包好的bundle.zip資源包;
  2. 下拉bundle.zip資源包並解壓到指定目錄;
  3. 重寫React native相關代碼指定資源包加載路徑和加載邏輯

 

 

搭建服務提供下載資源

mac上相對簡單,主要通過python,實現在桌面建立文件見並將bundle.zip包放入,命令行

1)$ cd /Users/xxxx/Desktop/server

2)$ python -m SimpleHTTPServer 8900

3)驗證服務可用,瀏覽器輸入:http://0.0.0.0:8900/,檢測可以下載

 

4)但是通過android模擬器是無法建立鏈接的需要修改爲http://10.0.2.2:8900/bundle.zip

 

 

資源下載

相關代碼結構如下,

 

下載的zip包存放路徑是android的根目錄的bundles包

 

 

重新指定RN資源加載路徑並加載相關資源

首先需要重寫一些方法,主要是在Application中進行,整體代碼如下:

public class MainApplication extends Application implements ReactApplication {

    public static Context appContext;
    private static MainApplication instance;
    private ReactInstanceManager mReactInstanceManager;
    private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
        @Override
        public boolean getUseDeveloperSupport() {
            return false;//在Debug模式下,會去加載JS Server服務的bundle。在Release模式下會去加載本地的bundle
        }

        @Override
        protected List<ReactPackage> getPackages() {
            @SuppressWarnings("UnnecessaryLocalVariable")
            List<ReactPackage> packages = new PackageList(this).getPackages();
            // Packages that cannot be autolinked yet can be added manually here, for example:
            packages.add(new UpgradePackage());
            return packages;
        }

        @Override
        protected String getJSMainModuleName() {
            return "index";
        }

        @Nullable
        @Override
        protected String getJSBundleFile() {
            String path = getBundlePath();
            
            File file = new File(path);
            if (file != null && file.exists()) {
                return path;
            }
            
            return null;

        }

        @Override
        protected ReactInstanceManager createReactInstanceManager() {
            ReactInstanceManagerBuilder builder = ReactInstanceManager.builder()
                    .setApplication(getApplication())
                    .setJSMainModulePath(getJSMainModuleName())
                    .setUseDeveloperSupport(getUseDeveloperSupport())
                    .setRedBoxHandler(getRedBoxHandler())
                    .setJavaScriptExecutorFactory(getJavaScriptExecutorFactory())
                    .setUIImplementationProvider(getUIImplementationProvider())
                    .setJSIModulesPackage(getJSIModulePackage())
                    .setInitialLifecycleState(LifecycleState.BEFORE_CREATE);

            for (ReactPackage reactPackage : getPackages()) {
                builder.addPackage(reactPackage);
            }
            String jsBundleFile = getJSBundleFile();
            if (jsBundleFile != null) {
                
                builder.setJSBundleFile(jsBundleFile);
            } else {
               
                builder.setBundleAssetName(Assertions.assertNotNull(getBundleAssetName()));
            }
            mReactInstanceManager = builder.build();
            return mReactInstanceManager;
        }
    };

    @Override
    public ReactNativeHost getReactNativeHost() {
        return mReactNativeHost;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        instance = this;
        appContext = getApplicationContext();
        SoLoader.init(this, false);
    }

    /**
     * 獲取Application實例
     */
    public static MainApplication getInstance() {
        return instance;
    }

    /**
     * 獲取包名
     */
    public String getAppPackageName() {
        return this.getPackageName();
    }


    public ReactContext getReactContext(){
        return mReactNativeHost.getReactInstanceManager().getCurrentReactContext();
    }

    public String getBundlePath() {
        return Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "bundles/index.android.bundle";
    }
}

最主要的是構建:mReactNativeHost,其中有兩個重要的實現方法,getJSBundleFile()和createReactInstanceManager()

getJSBundleFile就是用來指定資源加載路徑的,也就是Android應用加載RN資源包的路徑;

createReactInstanceManager是用來創建ReactInstanceManager的,而該類就是用來重新加載RN資源的,具體加載代碼如下:

/**
     * 重新加載
     */
    private void reloadJSBundle() {
        File file = new File(MainApplication.getInstance().getBundlePath());
        if (file == null || !file.exists()) {
            Log.i(TAG, "download error, check URL or network state");
            return;
        }

        Log.i(TAG, "download success, reload js bundle");

        Toast.makeText(this, "Downloading complete", Toast.LENGTH_SHORT).show();
        Field jsBundleField;
        try {

            ReactInstanceManager reactInstanceManager = MainApplication.getInstance().getReactNativeHost().getReactInstanceManager();
            Class<?> RIManagerClazz = reactInstanceManager.getClass();
            jsBundleField = RIManagerClazz.getDeclaredField("mBundleLoader");
            jsBundleField.setAccessible(true);
            jsBundleField.set(reactInstanceManager, JSBundleLoader.createFileLoader(file.getPath()));
            if (!reactInstanceManager.hasStartedCreatingInitialContext()) {
                reactInstanceManager.createReactContextInBackground();
            } else {
                reactInstanceManager.recreateReactContextInBackground();
            }

            reactInstanceManager.addReactInstanceEventListener(new ReactInstanceManager.ReactInstanceEventListener(){

                @Override
                public void onReactContextInitialized(ReactContext context) {
                    reactInstanceManager.removeReactInstanceEventListener(this);
                    try {
                        CatalystInstance catalystInstance = reactInstanceManager.getCurrentReactContext().getCatalystInstance();
                        Method method = CatalystInstanceImpl.class.getDeclaredMethod("loadScriptFromFile", String.class, String.class, boolean.class);
                        method.setAccessible(true);
                        method.invoke(catalystInstance, file.getPath(), file.getPath(), false);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }

                }
            });
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

注意上面使用的ReactInstanceManager 實例便是先前在Application中重寫的createReactInstanceManager方法返回的。

ReactInstanceManager reactInstanceManager = MainApplication.getInstance().getReactNativeHost().getReactInstanceManager();

上面工作基本工作就緒後運行驗證,可能不注意修改getUseDeveloperSupport()返回值,默認是返回BuildConfig.DEBUG,這樣運行不會成功的,因爲在debug模式下,會去加載JS Server服務的bundle,在release模式下會去加載本地的bundle,返回值false表示release模式

        @Override
        public boolean getUseDeveloperSupport() {
            return BuildConfig.DEBUG;
        }

如果運行如下圖,就是不成功的,加載的是JS Server服務器中的代碼

修改返回值爲false,重新運行:

        @Override
        public boolean getUseDeveloperSupport() {
            return false;
        }

驗證成功

 

 

發佈了106 篇原創文章 · 獲贊 20 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章