Android React Native 热更新 自己搭建热更新平台

使用react native 大家最主要的是看重脸书推图的热更新功能,然而现在目前大多数的热更新都是基于第三方框架上实现的,假使某一天第三方框架停止维护,或者收费你是不是觉得很坑呢。

     我们都知道react native是使用bundle实现热更新的,那么我们只需要替换bundle文件就可以实现更新操作了。安装包中的bundle文件是在asset目录下的,而asset目录我们是没有写权限的,所以我们不能修改安装包中的bundle文件。好在RN中提供了修改读取bundle路径的方法。以android为例(ios的类似),在ReactActivity类中有这么一个方法

/**
 * Returns a custom path of the bundle file. This is used in cases the bundle should be loaded
 * from a custom path. By default it is loaded from Android assets, from a path specified
 * by {@link getBundleAssetName}.
 * e.g. "file://sdcard/myapp_cache/index.android.bundle"
 */
protected @Nullable String getJSBundleFile() {
  return null;
}

该方法返回了一个自定义的bundle文件路径,如果返回默认值null,RN会读取asset里的bundle。我们在MainActivity类中重写这个方法,返回可写目录一下的bundle文件路径:

@Override
protected @Nullable String getJSBundleFile() {
    String jsBundleFile = getFilesDir().getAbsolutePath() + "/index.android.bundle";
    File file = new File(jsBundleFile);
    return file != null && file.exists() ? jsBundleFile : null;
}

如果可写目录下没有bundle文件,还是返回null,RN依然读取的是asset中的bundle,如果可写目录下存在bundle,RN就会读取可写目录下的bundle文件。

我们在RN项目根目执行以下命令来得到bundle文件和图片资源:

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

所以我们只需要知道这一点就可以实现自己的热更新需求啦,具体的下载和生成bundle和资源文件可以参考http://www.jianshu.com/p/2cb3eb9604ca。


1、打包生成bundle文件,其中资源文件也需要打包:

在rn项目根目录执行以下命令:

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


会得到如此目录的文件,其中图片也在里面。


2,将上面的文件放入文件夹中压缩,放到自己的服务器上。(这里我自己搭建了一个入服务器)


3,将文件下载到本地,进行解压

package com.topdraw.melody.download;

import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.os.AsyncTask;
import android.util.Log;

import com.topdraw.melody.MainActivity;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;

public class DownLoaderTask extends AsyncTask<Void, Integer, Long> {
   private final String TAG = "DownLoaderTask";
   private URL mUrl;
   private File mFile;
   private ProgressDialog mDialog;
   private int mProgress = 0;
   private ProgressReportingOutputStream mOutputStream;
   private Context mContext;
   public DownLoaderTask(String url,String out,Context context){
      super();
      if(context!=null){
         mDialog = new ProgressDialog(context);
         mContext = context;
      }
      else{
         mDialog = null;
      }

      try {
         mUrl = new URL(url);
         String fileName = new File(mUrl.getFile()).getName();
         mFile = new File(out, fileName);
         Log.d(TAG, "out="+out+", name="+fileName+",mUrl.getFile()="+mUrl.getFile());
      } catch (MalformedURLException e) {
         // TODO Auto-generated catch block
         e.printStackTrace();
      }

   }

   @Override
   protected void onPreExecute() {
      // TODO Auto-generated method stub
      //super.onPreExecute();
      if(mDialog!=null){
         mDialog.setTitle("Downloading...");
         mDialog.setMessage(mFile.getName());
         mDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
         mDialog.setOnCancelListener(new OnCancelListener() {

            @Override
            public void onCancel(DialogInterface dialog) {
               // TODO Auto-generated method stub
               cancel(true);
            }
         });
         mDialog.show();
      }
   }

   @Override
   protected Long doInBackground(Void... params) {
      // TODO Auto-generated method stub
      return download();
   }

   @Override
   protected void onProgressUpdate(Integer... values) {
      // TODO Auto-generated method stub
      //super.onProgressUpdate(values);
      if(mDialog==null)
         return;
      if(values.length>1){
         int contentLength = values[1];
         if(contentLength==-1){
            mDialog.setIndeterminate(true);
         }
         else{
            mDialog.setMax(contentLength);
         }
      }
      else{
         mDialog.setProgress(values[0].intValue());
      }
   }

   @Override
   protected void onPostExecute(Long result) {
      // TODO Auto-generated method stub
      //super.onPostExecute(result);
      if(mDialog!=null&&mDialog.isShowing()){
         mDialog.dismiss();
      }
      if(isCancelled())
         return;
      ((MainActivity)mContext).showUnzipDialog();
   }

   private long download(){
      URLConnection connection = null;
      int bytesCopied = 0;
      try {
         connection = mUrl.openConnection();
         int length = connection.getContentLength();
         if(mFile.exists()&&length == mFile.length()){
            Log.d(TAG, "file "+mFile.getName()+" already exits!!");
            mFile.delete();
         }
         mOutputStream = new ProgressReportingOutputStream(mFile);
         publishProgress(0,length);
         bytesCopied =copy(connection.getInputStream(),mOutputStream);
         if(bytesCopied!=length&&length!=-1){
            Log.e(TAG, "Download incomplete bytesCopied="+bytesCopied+", length"+length);
         }
         mOutputStream.close();
      } catch (IOException e) {
         // TODO Auto-generated catch block
         e.printStackTrace();
      }
      return bytesCopied;
   }
   private int copy(InputStream input, OutputStream output){
      byte[] buffer = new byte[1024*8];
      BufferedInputStream in = new BufferedInputStream(input, 1024*8);
      BufferedOutputStream out  = new BufferedOutputStream(output, 1024*8);
      int count =0,n=0;
      try {
         while((n=in.read(buffer, 0, 1024*8))!=-1){
            out.write(buffer, 0, n);
            count+=n;
         }
         out.flush();
      } catch (IOException e) {
         // TODO Auto-generated catch block
         e.printStackTrace();
      }finally{
         try {
            out.close();
         } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
         }
         try {
            in.close();
         } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
         }
      }
      return count;
   }
   private final class ProgressReportingOutputStream extends FileOutputStream{

      public ProgressReportingOutputStream(File file)
            throws FileNotFoundException {
         super(file);
         // TODO Auto-generated constructor stub
      }

      @Override
      public void write(byte[] buffer, int byteOffset, int byteCount)
            throws IOException {
         // TODO Auto-generated method stub
         super.write(buffer, byteOffset, byteCount);
         mProgress += byteCount;
         publishProgress(mProgress);
      }

   }
}

这个是下载文件的类。



package com.topdraw.melody.download;

import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.os.AsyncTask;
import android.util.Log;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;

public class ZipExtractorTask extends AsyncTask<Void, Integer, Long> {
   private final String TAG = "ZipExtractorTask";
   private final File mInput;
   private final File mOutput;
   private final File deletmOutput;
   private final ProgressDialog mDialog;
   private int mProgress = 0;
   private final Context mContext;
   private boolean mReplaceAll;
   public ZipExtractorTask(String in, String out, Context context, boolean replaceAll){
      super();
      mInput = new File(in);
      mOutput = new File(out);
      deletmOutput= new File(out+"/bundle");
      if(!mOutput.exists()){
         if(!mOutput.mkdirs()){
            Log.e(TAG, "Failed to make directories:"+mOutput.getAbsolutePath());
         }
      }
      if(context!=null){
         mDialog = new ProgressDialog(context);
      }
      else{
         mDialog = null;
      }
      mContext = context;
      mReplaceAll = replaceAll;
   }
   @Override
   protected Long doInBackground(Void... params) {
      // TODO Auto-generated method stub
      return unzip();
   }

   public void deleteAllFiles(File root) {
      File files[] = root.listFiles();
      if (files != null)
         for (File f : files) {
            if (f.isDirectory()) { // 判断是否为文件夹
               deleteAllFiles(f);
               try {
                  f.delete();
               } catch (Exception e) {
               }
            } else {
               if (f.exists()) { // 判断是否存在
                  deleteAllFiles(f);
                  try {
                     f.delete();
                  } catch (Exception e) {
                  }
               }
            }
         }
   }
   @Override
   protected void onPostExecute(Long result) {
      // TODO Auto-generated method stub
      //super.onPostExecute(result);
      if(mDialog!=null&&mDialog.isShowing()){
         mDialog.dismiss();
      }
      if(isCancelled())
         return;
   }
   @Override
   protected void onPreExecute() {
      // TODO Auto-generated method stub
      //super.onPreExecute();
      if(mDialog!=null){
         mDialog.setTitle("Extracting");
         mDialog.setMessage(mInput.getName());
         mDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
         mDialog.setOnCancelListener(new OnCancelListener() {

            @Override
            public void onCancel(DialogInterface dialog) {
               // TODO Auto-generated method stub
               cancel(true);
            }
         });
         mDialog.show();
      }
   }
   @Override
   protected void onProgressUpdate(Integer... values) {
      // TODO Auto-generated method stub
      //super.onProgressUpdate(values);
      if(mDialog==null)
         return;
      if(values.length>1){
         int max=values[1];
         mDialog.setMax(max);
      }
      else
         mDialog.setProgress(values[0].intValue());
   }
   private long unzip(){
      long extractedSize = 0L;
      Enumeration<ZipEntry> entries;
      ZipFile zip = null;
      deleteAllFiles(deletmOutput);
      try {
         zip = new ZipFile(mInput);
         long uncompressedSize = getOriginalSize(zip);
         publishProgress(0, (int) uncompressedSize);

         entries = (Enumeration<ZipEntry>) zip.entries();
         while(entries.hasMoreElements()){
            ZipEntry entry = entries.nextElement();
            if(entry.isDirectory()){
               continue;
            }
            File destination = new File(mOutput, entry.getName());
            if(!destination.getParentFile().exists()){
               Log.e(TAG, "make="+destination.getParentFile().getAbsolutePath());
               destination.getParentFile().mkdirs();
            }
            if(destination.exists()&&mContext!=null&&!mReplaceAll){

            }
            ProgressReportingOutputStream outStream = new ProgressReportingOutputStream(destination);
            extractedSize+=copy(zip.getInputStream(entry),outStream);
            outStream.close();
         }
      } catch (ZipException e) {
         // TODO Auto-generated catch block
         e.printStackTrace();
      } catch (IOException e) {
         // TODO Auto-generated catch block
         e.printStackTrace();
      }finally{
         try {
            zip.close();
         } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
         }
      }

      return extractedSize;
   }

   private long getOriginalSize(ZipFile file){
      Enumeration<ZipEntry> entries = (Enumeration<ZipEntry>) file.entries();
      long originalSize = 0l;
      while(entries.hasMoreElements()){
         ZipEntry entry = entries.nextElement();
         if(entry.getSize()>=0){
            originalSize+=entry.getSize();
         }
      }
      return originalSize;
   }

   private int copy(InputStream input, OutputStream output){
      byte[] buffer = new byte[1024*8];
      BufferedInputStream in = new BufferedInputStream(input, 1024*8);
      BufferedOutputStream out  = new BufferedOutputStream(output, 1024*8);
      int count =0,n=0;
      try {
         while((n=in.read(buffer, 0, 1024*8))!=-1){
            out.write(buffer, 0, n);
            count+=n;
         }
         out.flush();
      } catch (IOException e) {
         // TODO Auto-generated catch block
         e.printStackTrace();
      }finally{
         try {
            out.close();
         } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
         }
         try {
            in.close();
         } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
         }
      }
      return count;
   }

   private final class ProgressReportingOutputStream extends FileOutputStream{

      public ProgressReportingOutputStream(File file)
            throws FileNotFoundException {
         super(file);
         // TODO Auto-generated constructor stub
      }

      @Override
      public void write(byte[] buffer, int byteOffset, int byteCount)
            throws IOException {
         // TODO Auto-generated method stub
         super.write(buffer, byteOffset, byteCount);
         mProgress += byteCount;
         publishProgress(mProgress);
      }

   }
}

这个是解压的类。



4.重写此方法,改变bundle读取的位置



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值