LoadRNActivity.java 11.7 KB
package com.drp.util.react;

import android.content.Intent;
import android.os.Bundle;
import android.os.Environment;
import android.view.KeyEvent;
import android.widget.TextView;
import butterknife.Bind;
import butterknife.ButterKnife;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.drp.R;
import com.drp.mobliemall.app.GlobalContext;
import com.drp.mobliemall.config.SysConstant;
import com.drp.mobliemall.ui.base.BaseActivity;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import com.fish.drp.div.util.UIUtil;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;

import java.io.*;
import java.math.BigInteger;
import java.net.URL;
import java.net.URLConnection;
import java.security.MessageDigest;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import static com.drp.util.ZipExtractorTask.copy;


public class LoadRNActivity extends BaseActivity implements DefaultHardwareBackBtnHandler {

    private static final String TAG = "LoadRNActivity";
    private static final int LAYOUT_ID = R.layout.activity_load_rn;
    private static LoadRNActivity loadRNActivity;

    private static final String APP_ROOT_PATH = Environment.getExternalStorageDirectory().toString() + File.separator ;//+ BuildConfig.APPLICATION_ID;
    private static final String APP_BUNDLE_PATH = APP_ROOT_PATH + File.separator + "app";
    private static final String APP_RUNTIME_PATH = APP_ROOT_PATH + File.separator + "runtime";

    public static Map<String, String> rnGlobalParams;

    private ReactInstanceManager mReactInstanceManager;

    @Bind(R.id.tv_load_rn_center_tip)
    TextView textView;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        GlobalContext.getInstance().setActivity(this);
        super.onCreate(savedInstanceState);
        setContentView(LAYOUT_ID);
        ButterKnife.bind(this);
        loadRNActivity = this;
        initView();
        initData(savedInstanceState);
        // doInit(savedInstanceState);
    }

//    /**
//     * 初始化视图
//     */

    protected void initView() {
        UIUtil.showWaitDialog(LoadRNActivity.this);
    }

    int downLoadFileSize;
    int fileSize;

    /**
     * 初始化数据
     *
     * @param savedInstanceState
     */

    protected void initData(Bundle savedInstanceState) {
        // 完整流程
        // 1. 获取应用的版本信息
        // 2. 判断本地是否存在此版本
        //  1. 不存在
        //   1. 准备相关目录
        //   2. 下载压缩文件
        //   3. 解压文件至相关目录
        //  2. 已存在
        //   1. 直接启动应用
        // 3. 切换显示画面
        // 流程结束

        // 获取并处理参数
        String rnBaseUrl = getIntent().getStringExtra(SysConstant.RN_LOCATION);
        // 版本文件地址
        final String versionUrl;
        if(!rnBaseUrl.endsWith("/")) {
            versionUrl = rnBaseUrl + "/android.json";
        } else {
            versionUrl = rnBaseUrl + "android.json";
        }
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    // 获取最新版本信息
                    HttpClient client = new DefaultHttpClient();
                    HttpGet httpGet = new HttpGet(versionUrl);
                    HttpResponse httpResponse = client.execute(httpGet);
                    if(httpResponse.getStatusLine().getStatusCode() == 200) {
                        HttpEntity entity = httpResponse.getEntity();
                        String text = EntityUtils.toString(entity, "UTF-8");
                        JSONArray array = JSON.parseArray(text);
                        Map<Integer, JSONObject> jsonMap = new HashMap<>();
                        int maxVersion=0;
                        for(int i=0; i<array.size(); i++) {
                            JSONObject json = (JSONObject) array.get(i);
                            Integer version = json.getInteger("version");
                            if(version > maxVersion) {
                                // TODO 后续添加最小APP版本的卡控
                                maxVersion = version;
                            }
                            jsonMap.put(version, json);
                        }
                        JSONObject json = jsonMap.get(maxVersion);

                        //{
                        //   "version":1,
                        //   "bundleName":"index.android.bundle",
                        //   "isForce":false,
                        //   "minVersion":0,
                        //   "zipUrl":"http://xn-static.oss-cn-hangzhou.aliyuncs.com/rn-app/edward/v1/index.android.bundle.zip"
                        //}
                        String version = json.getString("version");
                        String bundleName = json.getString("bundleName");
                        Boolean isForce = json.getBoolean("isForce");
                        String minVersion = json.getString("minVersion");
                        String fileUrl = json.getString("zipUrl");
                        String md5 = json.getString("md5");
                        boolean skipDownload = false;
                        boolean skipUnzip = false;

                        // 本地文件存储路径
                        String rnModule = getIntent().getStringExtra(SysConstant.RN_MODULE);
                        String localFile = APP_BUNDLE_PATH + File.separator + rnModule + File.separator + version + ".android.zip";

                        final File localZipFile = new File(localFile);

                        // 创建文件夹
                        File appDir = localZipFile.getParentFile();
                        if (appDir != null && !appDir.exists()) {
                            appDir.mkdirs();
                        }
                        File runtimeFolder = new File(APP_RUNTIME_PATH + File.separator + rnModule + File.separator + version + File.separator);
                        if(!runtimeFolder.exists()) {
                            runtimeFolder.mkdirs();
                        }

                        // 判断是否要删除已有文件
                        if(localZipFile.exists()) {
                            // 如果文件已经存在,先计算文件的MD5,如果MD5匹配,则直接返回,否则,删除后重新下载
                            String fileMd5 = getFileMD5(localZipFile);
                            if(!fileMd5.equalsIgnoreCase(md5)) {
                                localZipFile.delete();
                                deleteFolder(runtimeFolder);
                            } else {
                                skipDownload = true;
                            }
                        } else {
                            deleteFolder(runtimeFolder);
                        }

                        // 下载文件
                        if(!skipDownload) {
                            downloadFile(fileUrl, localZipFile);
                        }

                        // 解压文件
                        ZipFile zipFile = new ZipFile(localZipFile);
                        Enumeration<ZipEntry> entries = (Enumeration<ZipEntry>) zipFile.entries();
                        while(entries.hasMoreElements()) {
                            ZipEntry entry = entries.nextElement();
                            if(entry.isDirectory()) {
                                continue;
                            }
                            File destination = new File(runtimeFolder, entry.getName());
                            if(!destination.getParentFile().exists()) {
                                destination.getParentFile().mkdirs();
                            }
                            FileOutputStream outStream = new FileOutputStream(destination);
                            copy(zipFile.getInputStream(entry), outStream);
                            outStream.close();
                        }

                        // 启动RN
                        Intent intent = new Intent(LoadRNActivity.this, CommonReactNativeActivity.class);
                        intent.putExtra("componentName", rnModule);
                        intent.putExtra("version", version);
                        intent.putExtra("path", new File(runtimeFolder, bundleName).getPath());
                        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
                        startActivity(intent);
                        loadRNActivity.finish();
                    }
                } catch(Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    private void deleteFolder(File folder) {
        if(folder.isDirectory()) {
            for(File file : folder.listFiles()) {
                if(file.isDirectory()) {
                    deleteFolder(file);
                } else {
                    file.delete();
                }
            }
        }
        folder.delete();
    }

    /**
     * 获取文件MD5的值
     * @param file  文件
     * @return      文件的MD5
     */
    private String getFileMD5(File file) {
        if (!file.isFile()) {
            return null;
        }
        MessageDigest digest = null;
        FileInputStream in = null;
        byte buffer[] = new byte[1024];
        int len;
        try {
            digest = MessageDigest.getInstance("MD5");
            in = new FileInputStream(file);
            while ((len = in.read(buffer, 0, 1024)) != -1) {
                digest.update(buffer, 0, len);
            }
            in.close();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        BigInteger bigInt = new BigInteger(1, digest.digest());
        return bigInt.toString(16);
    }

    /**
     * 下载文件至本地
     * @param remoteUrl 远程文件地址
     * @param localFile 本地保存路径
     * @throws IOException
     */
    private void downloadFile(String remoteUrl, File localFile) throws IOException {
        URL url = new URL(remoteUrl);
        // 打开连接
        URLConnection con = url.openConnection();
        //获得文件的长度
        fileSize = con.getContentLength();
        // 输入流
        InputStream is = con.getInputStream();
        // 1K的数据缓冲
        byte[] bs = new byte[1024];
        // 读取到的数据长度
        int len;
        // 输出的文件流
        OutputStream os = new FileOutputStream(localFile);
        // 开始读取
        downLoadFileSize = 0;
        while ((len = is.read(bs)) != -1) {
            os.write(bs, 0, len);
            downLoadFileSize += len;
        }
        // 完毕,关闭所有链接
        os.close();
        is.close();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
    }

    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_MENU && mReactInstanceManager != null) {
            mReactInstanceManager.showDevOptionsDialog();
            return true;
        }
        return super.onKeyUp(keyCode, event);
    }

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

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

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

    @Override
    protected void onResume() {
        super.onResume();
    }
}