java 替换 rn_RN热更新之Android篇

前言

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

1.reactnative中文网的pushy

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

原理

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

实现

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

我们打好包jsbundle文件放到远程服务器上。

请求服务器接口,当接口中返回的版本号跟我们rn中存储的版本号不一致的时候,那么这个时候就需要更新版本了。

下载服务器上的jshundle,替换掉当前版本的jsbundle文件。

下次打开生效或者执行某个方法立即更新。

打包

回顾一下打包命令

$ 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加了张图片,以便验证资源是否能热加载成功。

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文件夹中就生成了我们打好的包,压缩好上传到服务器即可。

961088f1647d

更新和下载

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

前端发Ajax请求,在回调里拿到版本号,比出不同,再调用android代码执行下载、替换。

也可以全部逻辑都在android原生的代码做掉,js端不用给任何反应。

两者的区别其实就是需不需要让用户有感知,但第一种好像更灵活一点,另外的区别就是版本号存放的位置和比对状态的区别。

第一种比较清晰,每次在入口的JS把客户端的版本号和服务端比就行了,不一致就更新,下次比对就一致了,当然就需要你在打包时的版本号和插入服务端的一致;

而第二种麻烦一点,因为它只能拿到你随包打的版本号,更新后没前端发给后端,它是拿不到新的版本号,所以需要后端的一个存储机制在更新后把更新的版本后记下来,所以比较的逻辑应该就是优先拿更新过的版本号和服务端的比,没有更新过的才用原始随包的版本号和服务的比。

我先来试试第二种:

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

961088f1647d

然后重新编译,在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来获取随包打的版本号。

第二,开始判断更新:

大致的思路是

先去SD(我们打算存放的位置)找bundle

没有才去找默认的assets

然后才是异步判断版本,下载、更新替换

我们开始改造一下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端触发更新,比第一种其实就多了两点

需要一个update的modules,打通前端与原生

在更新后需要存储更新状态

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 getConstants() {

final Map 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,上传、下载是再想办法合并。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值