package com.doubanmovie; 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.net.Uri; import android.os.Bundle; import android.os.Environment; import android.util.Log; import android.view.KeyEvent; import android.widget.Toast; import com.facebook.react.LifecycleState; import com.facebook.react.ReactInstanceManager; import com.facebook.react.ReactRootView; import com.facebook.react.bridge.JSBundleLoader; import com.facebook.react.bridge.JSCJavaScriptExecutor; import com.facebook.react.bridge.JavaScriptExecutor; import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler; import com.facebook.react.shell.MainReactPackage; import java.io.File; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** * Base react native which can load js bundle file from remote server to update * * @author fengjun */ public class BaseReactActivity extends Activity implements DefaultHardwareBackBtnHandler { private static final String TAG = "BaseReactActivity"; public static final String JS_BUNDLE_REMOTE_URL = "https://raw.githubusercontent.com/fengjundev/React-Native-Remote-Update/master/remote/index.android.bundle"; public static final String JS_BUNDLE_LOCAL_FILE = "index.android.bundle"; public static final String JS_BUNDLE_LOCAL_PATH = Environment.getExternalStorageDirectory().toString() + File.separator + JS_BUNDLE_LOCAL_FILE; private ReactInstanceManager mReactInstanceManager; private ReactRootView mReactRootView; private CompleteReceiver mDownloadCompleteReceiver; private long mDownloadId; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); iniReactRootView(); setContentView(mReactRootView); initDownloadManager(); updateBundle(); } private void initDownloadManager() { mDownloadCompleteReceiver = new CompleteReceiver(); registerReceiver(mDownloadCompleteReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)); } private void iniReactRootView() { ReactInstanceManager.Builder builder = ReactInstanceManager.builder() .setApplication(getApplication()) .setJSMainModuleName("index.android") .addPackage(new MainReactPackage()) .setInitialLifecycleState(LifecycleState.RESUMED); File file = new File(JS_BUNDLE_LOCAL_PATH); if(file != null && file.exists()){ builder.setJSBundleFile(JS_BUNDLE_LOCAL_PATH); Log.i(TAG, "load bundle from local cache"); }else{ builder.setBundleAssetName(JS_BUNDLE_LOCAL_FILE); Log.i(TAG, "load bundle from asset"); } mReactRootView = new ReactRootView(this); mReactInstanceManager = builder.build(); mReactRootView.startReactApplication(mReactInstanceManager, "DoubanMovie", null); } @Override protected void onDestroy() { super.onDestroy(); unregisterReceiver(mDownloadCompleteReceiver); } private void updateBundle() { // Should add version check here, if bundle file // is the newest , we do not need to update File file = new File(JS_BUNDLE_LOCAL_PATH); if(file != null && file.exists()){ Log.i(TAG, "newest bundle exists !"); return; } //Toast.makeText(BaseReactActivity.this, "Start downloading update", Toast.LENGTH_SHORT).show(); DownloadManager.Request request = new DownloadManager.Request(Uri.parse(JS_BUNDLE_REMOTE_URL)); request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI); request.setDestinationUri(Uri.parse("file://" + JS_BUNDLE_LOCAL_PATH)); DownloadManager dm = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE); mDownloadId = dm.enqueue(request); Log.i(TAG, "start download remote bundle"); } private class CompleteReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { long completeDownloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1); if(completeDownloadId == mDownloadId){ onJSBundleLoadedFromServer(); } } }; private void onJSBundleLoadedFromServer() { File file = new File(JS_BUNDLE_LOCAL_PATH); 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(BaseReactActivity.this, "Downloading complete", Toast.LENGTH_SHORT).show(); try { Class<?> RIManagerClazz = mReactInstanceManager.getClass(); Method method = RIManagerClazz.getDeclaredMethod("recreateReactContextInBackground", JavaScriptExecutor.class, JSBundleLoader.class); method.setAccessible(true); method.invoke(mReactInstanceManager, new JSCJavaScriptExecutor(), JSBundleLoader.createFileLoader(getApplicationContext(), JS_BUNDLE_LOCAL_PATH)); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } } @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() { if (mReactInstanceManager != null) { mReactInstanceManager.onBackPressed(); } else { super.onBackPressed(); } } @Override public void invokeDefaultOnBackPressed() { super.onBackPressed(); } @Override protected void onPause() { super.onPause(); if (mReactInstanceManager != null) { mReactInstanceManager.onPause(); } } @Override protected void onResume() { super.onResume(); if (mReactInstanceManager != null) { mReactInstanceManager.onResume(this, new DefaultHardwareBackBtnHandler() { @Override public void invokeDefaultOnBackPressed() { finish(); } }); } } }