RN热更新之Android篇

2017-05-16  本文已影响0人  哪吒闹海全靠浪

前言

这篇来研究一下RN的热更新,之前看资料见到过两个现成的方案:

不过看了文档就觉得没劲,不如自己来实现,况且之前已经有点门路了。

原理

关于热更新的原理,另开一篇,点这里

实现

既然我们知道了原理,那么列一个大致的实现思路:

打包

回顾一下打包命令

$ react-native bundle --platform android --dev false --entry-file index.android.js --bundle-output android/com/your-company-name/app-package-name/src/main/assets/index.android.bundle --assets-dest android/com/your-company-name/app-package-name/src/main/res/

发现打包分成了bundle和资源两部分,但我们的demo里有没任何图片,所以我在index.android.js加了张图片,以便验证资源是否能热加载成功。

                   <Image 
                    source={require('./img/music_play.png')}
                    style={{width:92,height:92}}
                    />

然后在根目录建了一个finalbundle的文件夹,存放最终打出的包,执行

react-native bundle --platform android --dev false --entry-file index.android.js 
--bundle-output finalbundle/index.android.bundle --assets-dest finalbundle/

在finalbundle文件夹中就生成了我们打好的包,压缩好上传到服务器即可。

更新和下载

要更新我们首先要把当前的版本号与服务端最新的版本号做比对,不一样才执行下载动作。比对这步可以是

两者的区别其实就是需不需要让用户有感知,但第一种好像更灵活一点,另外的区别就是版本号存放的位置和比对状态的区别。
第一种比较清晰,每次在入口的JS把客户端的版本号和服务端比就行了,不一致就更新,下次比对就一致了,当然就需要你在打包时的版本号和插入服务端的一致;
而第二种麻烦一点,因为它只能拿到你随包打的版本号,更新后没前端发给后端,它是拿不到新的版本号,所以需要后端的一个存储机制在更新后把更新的版本后记下来,所以比较的逻辑应该就是优先拿更新过的版本号和服务端的比,没有更新过的才用原始随包的版本号和服务的比。

我先来试试第二种:

首先需要知道怎么拿到随包打的版本号,需要在打开app/build.gradle,然后添加buildConfigField定义,如下:



然后重新编译,在BuildConfig看到就多了一条BUNDLE_VERSION

public final class BuildConfig {
  public static final boolean DEBUG = Boolean.parseBoolean("true");
  public static final String APPLICATION_ID = "com.example.zhouwenkang.rnandnative";
  public static final String BUILD_TYPE = "debug";
  public static final String FLAVOR = "";
  public static final int VERSION_CODE = 1;
  public static final String VERSION_NAME = "1.0";
  // Fields from default config.
  public static final String BUNDLE_VERSION = "1.0.0";
}

所以我们就能用BuildConfig.BUNDLE_VERSION来获取随包打的版本号。
第二,开始判断更新:
大致的思路是

我们开始改造一下MyRNActivity


package com.example.zhouwenkang.rnandnative;

import android.app.Activity;
import android.app.DownloadManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.KeyEvent;

import com.facebook.react.JSCConfig;
import com.facebook.react.ReactApplication;
import com.facebook.react.common.LifecycleState;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactRootView;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import com.facebook.react.shell.MainReactPackage;
import com.facebook.react.ReactInstanceManagerBuilder;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;


public class MyRNActivity extends Activity implements DefaultHardwareBackBtnHandler {

    private long mDownloadId;

    private ReactRootView mReactRootView;
    private ReactInstanceManager mReactInstanceManager;

    private DownloadManager dm;

    public static void startActivity(Context context){
        Intent intent = new Intent(context, MyRNActivity.class);
        context.startActivity(intent);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mReactRootView = new ReactRootView(this);
        ReactInstanceManagerBuilder builder = ReactInstanceManager.builder()
                .setApplication(getApplication())
                //.setBundleAssetName("index.android.bundle")
                .setJSMainModuleName("index.android")
                .addPackage(new MainReactPackage())
                .addPackage(new RNJavaReactPackage())
                .setUseDeveloperSupport(true)
                .setInitialLifecycleState(LifecycleState.RESUMED);

            File bundleFile = new File(getExternalCacheDir()+"/finalbundle","index.android.bundle");
            if(bundleFile.exists()){
                builder.setJSBundleFile(bundleFile.getAbsolutePath());
            } else {
                builder.setBundleAssetName("index.android.bundle");
            }
        mReactInstanceManager = builder.build();
        mReactRootView.startReactApplication(mReactInstanceManager, "rnandnative", null);
        setContentView(mReactRootView);
        updateJsBundle();
    }

    private void updateJsBundle(){
        if(BuildConfig.BUNDLE_VERSION == "1.0.0"){//TODO:这里需要发起异步获取服务端的版本号,然后和打包版本号比对

            Context context=MyRNActivity.this;//首先,在Activity里获取context
            File file=context.getFilesDir();
            String path=file.getAbsolutePath();
            System.out.println(path);
            System.out.println(Environment.getExternalStorageDirectory().toString());
            System.out.println(getExternalCacheDir());
            File reactDir = new File(getExternalCacheDir(),"finalbundle");
            System.out.println(reactDir.getAbsolutePath());
            if(!reactDir.exists()){
                reactDir.mkdirs();
            }
            System.out.println("file://"+new File(getExternalCacheDir(),"finalbundle/finalbundle.zip").getAbsolutePath());
            DownloadManager.Request request = new DownloadManager.Request(Uri.parse("https://raw.githubusercontent.com/wenkangzhou/YWNative/master/HotUpdateRes/finalbundle.zip"));
            //request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI);
            request.setDestinationUri(Uri.parse("file://"+new File(getExternalCacheDir(),"finalbundle/finalbundle.zip").getAbsolutePath()));
            //在通知栏中显示,默认就是显示的
            request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE);
            request.setVisibleInDownloadsUi(true);
            dm = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
            mDownloadId = dm.enqueue(request);

            //注册广播接收者,监听下载状态
            registerReceiver(receiver,
                    new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
        }
    }
    //广播接受者,接收下载状态
    private BroadcastReceiver receiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            checkDownloadStatus();//检查下载状态
        }
    };
    //检查下载状态
    private void checkDownloadStatus() {
        System.out.println("检查下载状态");
        DownloadManager.Query query = new DownloadManager.Query();
        query.setFilterById(mDownloadId);//筛选下载任务,传入任务ID,可变参数
        Cursor c = dm.query(query);
        if (c.moveToFirst()) {
            int status = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS));
            switch (status) {
                case DownloadManager.STATUS_PAUSED:
                    Log.i("heeeeeeee",">>>下载暂停");
                    System.out.println("下载暂停");
                case DownloadManager.STATUS_PENDING:
                    Log.i("heeeeeeee",">>>下载延迟");
                    System.out.println("下载延迟");
                case DownloadManager.STATUS_RUNNING:
                    Log.i("heeeeeeee",">>>正在下载");
                    System.out.println("正在下载");
                    break;
                case DownloadManager.STATUS_SUCCESSFUL:
                    Log.i("heeeeeeee",">>>下载完成");
                    //下载完成
                    replaceBundle();
                    break;
                case DownloadManager.STATUS_FAILED:
                    Log.i("heeeeeeee",">>>下载失败");
                    System.out.println("下载失败");
                    break;
            }
        }
    }
    protected void  replaceBundle() {
        System.out.println("下载成功");
        File reactDir = new File(getExternalCacheDir(),"finalbundle");
        System.out.println(reactDir.getAbsolutePath());
        if(!reactDir.exists()){
            System.out.println("创建");
            reactDir.mkdirs();
        }
        final File saveFile = new File(reactDir,"finalbundle.zip");
        boolean result = unzip(saveFile);
        if(result){//解压成功后保存当前最新bundle的版本
            if(true) {//立即加载bundle
                System.out.println("加载bundle");
//                ((ReactApplication) getReactApplicationContext()).getReactNativeHost().clear();
//                getCurrentActivity().recreate();
                try {

                    Class<?> RIManagerClazz = mReactInstanceManager.getClass();

                    Field f = RIManagerClazz.getDeclaredField("mJSCConfig");
                    f.setAccessible(true);
                    JSCConfig jscConfig = (JSCConfig)f.get(mReactInstanceManager);

                    Method method = RIManagerClazz.getDeclaredMethod("recreateReactContextInBackground",
                            com.facebook.react.cxxbridge.JavaScriptExecutor.Factory.class,
                            com.facebook.react.cxxbridge.JSBundleLoader.class);
                    method.setAccessible(true);
                    method.invoke(mReactInstanceManager,
                            new com.facebook.react.cxxbridge.JSCJavaScriptExecutor.Factory(jscConfig.getConfigMap()),
                            com.facebook.react.cxxbridge.JSBundleLoader.createFileLoader(new File(getExternalCacheDir()+"/finalbundle","index.android.bundle").getAbsolutePath()));
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                } catch (IllegalArgumentException e) {
                    e.printStackTrace();
                } catch (NoSuchFieldException e){
                    e.printStackTrace();
                }
            }
        }else{//解压失败应该删除掉有问题的文件,防止RN加载错误的bundle文件
            System.out.println("解压失败");
            File reactbundleDir = new File(getExternalCacheDir(),"finalbundle");
            deleteDir(reactbundleDir);
        }
    }
    private static boolean unzip(File zipFile){
        if(zipFile != null && zipFile.exists()){
            ZipInputStream inZip = null;
            try {
                inZip = new ZipInputStream(new FileInputStream(zipFile));
                ZipEntry zipEntry;
                String entryName;
                File dir = zipFile.getParentFile();
                while ((zipEntry = inZip.getNextEntry()) != null) {
                    entryName = zipEntry.getName();
                    if (zipEntry.isDirectory()) {
                        File folder = new File(dir,entryName);
                        folder.mkdirs();
                    } else {
                        File file = new File(dir,entryName);
                        file.createNewFile();

                        FileOutputStream fos = new FileOutputStream(file);
                        int len;
                        byte[] buffer = new byte[1024];
                        while ((len = inZip.read(buffer)) != -1) {
                            fos.write(buffer, 0, len);
                            fos.flush();
                        }
                        fos.close();
                    }
                }
                //("+++++解压完成+++++");
                return true;
            } catch (IOException e) {
                e.printStackTrace();
                //("+++++解压失败+++++");
                return false;
            }finally {
                try {
                    if(inZip != null){
                        inZip.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }else {
            return false;
        }
    }

    private static void deleteDir(File dir){
        if (dir==null||!dir.exists()) {
            return;
        } else {
            if (dir.isFile()) {
                dir.delete();
                return;
            }
        }
        if (dir.isDirectory()) {
            File[] childFile = dir.listFiles();
            if (childFile == null || childFile.length == 0) {
                dir.delete();
                return;
            }
            for (File f : childFile) {
                deleteDir(f);
            }
            dir.delete();
        }
    }
    @Override
    protected void onResume() {
        super.onResume();

        if(mReactInstanceManager != null){
            mReactInstanceManager.onHostResume(this, this);
        }
    }

    @Override
    protected void onPause() {
        super.onPause();

        if(mReactInstanceManager != null){
            mReactInstanceManager.onHostPause(this);
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unregisterReceiver(receiver);
        if(mReactInstanceManager != null){
            mReactInstanceManager.onHostDestroy();
        }
    }

    @Override
    public void onBackPressed() {
        super.onBackPressed();

        if(mReactInstanceManager != null){
            mReactInstanceManager.onBackPressed();
        }else{
            super.onBackPressed();
        }
    }

    @Override
    public void invokeDefaultOnBackPressed() {
        super.onBackPressed();
    }
    //我们需要改动一下开发者菜单。
    //默认情况下,任何开发者菜单都可以通过摇晃或者设备类触发,不过这对模拟器不是很有用。
    //所以我们让它在按下Menu键的时候可以显示
    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_MENU && mReactInstanceManager != null) {
            mReactInstanceManager.showDevOptionsDialog();
            return true;
        }
        return super.onKeyUp(keyCode, event);
    }
}

这里花了比较多时间,不过终于搞定了。

然后再试试第一种

通过JS端触发更新,比第一种其实就多了两点

JS:
NativeModules.updateBundle.check("5.0.0");
RNUpdateBundleModule.java:

import android.app.DownloadManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;

import com.facebook.react.JSCConfig;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.cxxbridge.JSBundleLoader;
import com.facebook.react.cxxbridge.JSCJavaScriptExecutor;
import com.facebook.react.cxxbridge.JavaScriptExecutor;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import android.database.Cursor;
import android.net.Uri;
import android.os.Environment;
import android.text.TextUtils;
import android.util.Log;
import android.app.Activity;
import android.widget.Toast;

public class RNUpdateBundleModule extends ReactContextBaseJavaModule {

    private SharedPreferences mSP;
    private static final String BUNDLE_VERSION = "CurrentBundleVersion";
    private DownloadManager dm;
    private long mDownloadId;
    private ReactInstanceManager mReactInstanceManager;
    Activity myActivity;

    public RNUpdateBundleModule(ReactApplicationContext reactApplicationContext) {
        super(reactApplicationContext);
        mSP = reactApplicationContext.getSharedPreferences("react_bundle", Context.MODE_PRIVATE);
    }
    @Override
    public String getName() {
        return "updateBundle";
    }

    /*
        一个可选的方法getContants返回了需要导出给JavaScript使用的常量。
        它并不一定需要实现,但在定义一些可以被JavaScript同步访问到的预定义的值时非常有用。
    */
    @Override
    public Map<String, Object> getConstants() {
        final Map<String, Object> constants = new HashMap<>();
        //跟随apk一起打包的bundle基础版本号,也就是assets下的bundle版本号
        String bundleVersion = BuildConfig.BUNDLE_VERSION;
        //bundle更新后的当前版本号
        String cacheBundleVersion = mSP.getString(BUNDLE_VERSION,"");
        System.out.println("+++++check version+++++-" + cacheBundleVersion);
        if(!TextUtils.isEmpty(cacheBundleVersion)){
            System.out.println("-+++++check version+++++-" + cacheBundleVersion);
            bundleVersion = cacheBundleVersion;
        }
        System.out.println("-+++++check version+++++-" + bundleVersion);
        constants.put(BUNDLE_VERSION,bundleVersion);
        return constants;
    }
    @ReactMethod
    public void check(String currVersion) {
        System.out.println("+++++check version+++++" + currVersion);
        System.out.println("+++++check version+++++" + BuildConfig.BUNDLE_VERSION);
        System.out.println("+++++check version+++++" + mSP.getString(BUNDLE_VERSION,""));
        String jsBundleVersion = BuildConfig.BUNDLE_VERSION;
        String cacheBundleVersion = mSP.getString(BUNDLE_VERSION,"");
        if(!TextUtils.isEmpty(cacheBundleVersion)){
            jsBundleVersion = cacheBundleVersion;
        }
        //测试时先隐藏
//        if(jsBundleVersion.equals("1.0.0")){//和服务下发的比对
//            System.out.println("已经是最新版本");
//            return;
//        }
        updateJsBundle();
    }
    private void updateJsBundle(){

        Context context= getReactApplicationContext();
        File file=context.getFilesDir();
        String path=file.getAbsolutePath();
        System.out.println(path);
        System.out.println(Environment.getExternalStorageDirectory().toString());
        System.out.println(getReactApplicationContext().getExternalCacheDir());
        File reactDir = new File(getReactApplicationContext().getExternalCacheDir(),"finalbundle");
        System.out.println(reactDir.getAbsolutePath());
        if(!reactDir.exists()){
            reactDir.mkdirs();
        }
        File reactZipDir = new File(getReactApplicationContext().getExternalCacheDir(),"finalbundle/finalbundle.zip");
        if(reactZipDir.exists()){
            deleteDir(reactZipDir);
        }
        System.out.println("file://"+new File(getReactApplicationContext().getExternalCacheDir(),"finalbundle/finalbundle.zip").getAbsolutePath());
        DownloadManager.Request request = new DownloadManager.Request(Uri.parse("https://raw.githubusercontent.com/wenkangzhou/YWNative/master/HotUpdateRes/finalbundle.zip"));
        //request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI);
        request.setDestinationUri(Uri.parse("file://"+new File(getReactApplicationContext().getExternalCacheDir(),"finalbundle/finalbundle.zip").getAbsolutePath()));
        //在通知栏中显示,默认就是显示的
        request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE);
        request.setVisibleInDownloadsUi(true);
        myActivity = getCurrentActivity();
        dm = (DownloadManager) myActivity.getSystemService(Context.DOWNLOAD_SERVICE);
        mDownloadId = dm.enqueue(request);

        //注册广播接收者,监听下载状态
        myActivity.registerReceiver(receiver,
                new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
    }
    //广播接受者,接收下载状态
    private BroadcastReceiver receiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            checkDownloadStatus();//检查下载状态
        }
    };
    //检查下载状态
    private void checkDownloadStatus() {
        System.out.println("检查下载状态");
        DownloadManager.Query query = new DownloadManager.Query();
        query.setFilterById(mDownloadId);//筛选下载任务,传入任务ID,可变参数
        Cursor c = dm.query(query);
        if (c.moveToFirst()) {
            int status = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS));
            switch (status) {
                case DownloadManager.STATUS_PAUSED:
                    Log.i("heeeeeeee",">>>下载暂停");
                    System.out.println("下载暂停");
                case DownloadManager.STATUS_PENDING:
                    Log.i("heeeeeeee",">>>下载延迟");
                    System.out.println("下载延迟");
                case DownloadManager.STATUS_RUNNING:
                    Log.i("heeeeeeee",">>>正在下载");
                    System.out.println("正在下载");
                    break;
                case DownloadManager.STATUS_SUCCESSFUL:
                    Log.i("heeeeeeee",">>>下载完成");
                    //下载完成
                    replaceBundle();
                    break;
                case DownloadManager.STATUS_FAILED:
                    Log.i("heeeeeeee",">>>下载失败");
                    System.out.println("下载失败");
                    break;
            }
        }
    }
    protected void  replaceBundle() {
        System.out.println("下载成功");
        File reactDir = new File(getReactApplicationContext().getExternalCacheDir(),"finalbundle");
        System.out.println(reactDir.getAbsolutePath());
        if(!reactDir.exists()){
            System.out.println("创建");
            reactDir.mkdirs();
        }
        final File saveFile = new File(reactDir,"finalbundle.zip");
        boolean result = unzip(saveFile);
        if(result){//解压成功后保存当前最新bundle的版本
            if(true) {//立即加载bundle
                System.out.println("加载bundle");
                mSP.edit().putString(BUNDLE_VERSION,"1.0.2").apply();
                Activity currActivity = getCurrentActivity();
//                if(currActivity != null){
//                    ((ReactApplication) currActivity.getApplication()).getReactNativeHost().clear();
//                    currActivity.unregisterReceiver(receiver);
//                    currActivity.recreate();
//                }
//                try {
//
//                    Class<?> RIManagerClazz = mReactInstanceManager.getClass();
//
//                    Field f = RIManagerClazz.getDeclaredField("mJSCConfig");
//                    f.setAccessible(true);
//                    JSCConfig jscConfig = (JSCConfig)f.get(mReactInstanceManager);
//
//                    Method method = RIManagerClazz.getDeclaredMethod("recreateReactContextInBackground",
//                            com.facebook.react.cxxbridge.JavaScriptExecutor.Factory.class,
//                            com.facebook.react.cxxbridge.JSBundleLoader.class);
//                    method.setAccessible(true);
//                    method.invoke(mReactInstanceManager,
//                            new com.facebook.react.cxxbridge.JSCJavaScriptExecutor.Factory(jscConfig.getConfigMap()),
//                            com.facebook.react.cxxbridge.JSBundleLoader.createFileLoader(new File(getReactApplicationContext().getExternalCacheDir()+"/finalbundle","index.android.bundle").getAbsolutePath()));
//                } catch (NoSuchMethodException e) {
//                    e.printStackTrace();
//                } catch (IllegalAccessException e) {
//                    e.printStackTrace();
//                } catch (InvocationTargetException e) {
//                    e.printStackTrace();
//                } catch (IllegalArgumentException e) {
//                    e.printStackTrace();
//                } catch (NoSuchFieldException e){
//                    e.printStackTrace();
//                }
//                Toast.makeText(getCurrentActivity(), "Downloading complete", Toast.LENGTH_SHORT).show()
                try {
                    ReactApplication application = (ReactApplication) getCurrentActivity().getApplication();
                    mReactInstanceManager = application.getReactNativeHost().getReactInstanceManager();
                    //builder.setJSBundleFile(bundleFile.getAbsolutePath());
                    Class<?> RIManagerClazz = application.getReactNativeHost().getReactInstanceManager().getClass();
                    Field f = RIManagerClazz.getDeclaredField("mJSCConfig");
                    f.setAccessible(true);
                    JSCConfig jscConfig = (JSCConfig)f.get(mReactInstanceManager);
                    Method method = RIManagerClazz.getDeclaredMethod("recreateReactContextInBackground",
                            JavaScriptExecutor.Factory.class, JSBundleLoader.class);
                    method.setAccessible(true);
                    method.invoke(application.getReactNativeHost().getReactInstanceManager(),
                            new JSCJavaScriptExecutor.Factory(jscConfig.getConfigMap()),
                            JSBundleLoader.createFileLoader(new File(getReactApplicationContext().getExternalCacheDir()+"/finalbundle","index.android.bundle").getAbsolutePath()));
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                } catch (IllegalArgumentException e) {
                    e.printStackTrace();
                } catch (NoSuchFieldException e){
                    e.printStackTrace();
                }
            }
        }else{//解压失败应该删除掉有问题的文件,防止RN加载错误的bundle文件
            System.out.println("解压失败");
            File reactbundleDir = new File(getReactApplicationContext().getExternalCacheDir(),"finalbundle");
            deleteDir(reactbundleDir);
        }
    }
    private static boolean unzip(File zipFile){
        if(zipFile != null && zipFile.exists()){
            ZipInputStream inZip = null;
            try {
                inZip = new ZipInputStream(new FileInputStream(zipFile));
                ZipEntry zipEntry;
                String entryName;
                File dir = zipFile.getParentFile();
                while ((zipEntry = inZip.getNextEntry()) != null) {
                    entryName = zipEntry.getName();
                    if (zipEntry.isDirectory()) {
                        File folder = new File(dir,entryName);
                        folder.mkdirs();
                    } else {
                        File file = new File(dir,entryName);
                        file.createNewFile();

                        FileOutputStream fos = new FileOutputStream(file);
                        int len;
                        byte[] buffer = new byte[1024];
                        while ((len = inZip.read(buffer)) != -1) {
                            fos.write(buffer, 0, len);
                            fos.flush();
                        }
                        fos.close();
                    }
                }
                //("+++++解压完成+++++");
                return true;
            } catch (IOException e) {
                e.printStackTrace();
                //("+++++解压失败+++++");
                return false;
            }finally {
                try {
                    if(inZip != null){
                        inZip.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }else {
            return false;
        }
    }

    private static void deleteDir(File dir){
        if (dir==null||!dir.exists()) {
            return;
        } else {
            if (dir.isFile()) {
                dir.delete();
                return;
            }
        }
        if (dir.isDirectory()) {
            File[] childFile = dir.listFiles();
            if (childFile == null || childFile.length == 0) {
                dir.delete();
                return;
            }
            for (File f : childFile) {
                deleteDir(f);
            }
            dir.delete();
        }
    }
}

TODO:这里遇到一个问题,立即刷新无效,下载和第二次开启app都正常。

遇到问题

1.关于图片加载,如果是asserts文件夹,图片需要在res,如果是外部sd,需要和bundle同级,也就是最好把图片和bundle打在一起,如果单独更新,需要去asserts目录复制到你的目录下,具体可以看看图片更新的流程
2.Android 6.0(sdk>=23)的读写权限,不仅在AndroidManifest.xml配置,还需要在用的时候发出请求,但cache目录是不需要的,建议放在cache目录下。
3.request.setDestinationUri只能是外部存储,不能是data/data下,还有模拟器网络不是wifi,所以设置只是wifi也不会触发下载,这里坑还是挺多的,建议去看看相关文档DownloadManager
4.立即刷新不生效:这个问题只因为在开启本地8081时,优先级比读目录的高,关闭服务,读离线文件就OK了。
5.一些机子上32/64位ibgnustl_shared.so的问题死活就是解决不了。

后续完善

1.首次加载,会出现比较长得白屏
可否预先去判断是否拉增量、预先加载bundle。
2.差量更新
每次只更新变更的,可能需要一些第三方的diff库,在本地做好diff,上传、下载是再想办法合并。

上一篇下一篇

猜你喜欢

热点阅读