您的位置:首页 > 其它

欢迎使用CSDN-markdown编辑器

2016-03-31 14:40 323 查看

React Native Android入门实战及深入源码分析系列(3)——热部署加载离线JSBundle文件

本Markdown编辑器使用[StackEdit][6]修改而来,用它写博客,将会带来全新的体验哦:

本文为老曾原创,转载需注明出处:http://blog.csdn.net/minimicall?viewmode=contents

上两节我们已经学些了如何编译源码,这一节我们来学习如何实施热部署。也就是更新我们的应用的时候,不需要发布版本,而是把JsBundle放到服务器上,然后下载下来,进行加载。

要实现这一步骤的第一个前提就是,加载离线JsBundle。

- 默认的MainActivity

我们首先来看工程默认的MainActivity 如下:

public class MainActivity extends ReactActivity {

/**
* Returns the name of the main component registered from JavaScript.
* This is used to schedule rendering of the component.
*/
@Override
protected String getMainComponentName() {
return "videolegend";
}

/**
* Returns whether dev mode should be enabled.
* This enables e.g. the dev menu.
*/
@Override
protected boolean getUseDeveloperSupport() {
// return BuildConfig.DEBUG;
return false;
}

/**
* A list of packages used by the app. If the app uses additional views
* or modules besides the default ones, add more packages here.
*/
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage()
);
}
}


然而,这段代码并不能看出什么鬼。只是注册了了一个MainReactPackage而已。

但是,它继承了ReactActivity。所以,我们可以把注意力放到ReactActivity上。

- ReactActivity

在ReactActivity里面有两个方法:

/**
* Returns the name of the bundle in assets. If this is null, and no file path is specified for
* the bundle, the app will only work with {@code getUseDeveloperSupport} enabled and will
* always try to load the JS bundle from the packager server.
* 这句话很重要,如果 getUseDeveloperSupport开关被打开,那么它总是会从packager server拉取JS bundle文件。
* e.g. "index.android.bundle"
*/
protected @Nullable String getBundleAssetName() {
return "index.android.bundle";
};

/**
* 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这句话就是说,如果你要自定义bundle加载,那么就修改这个地方的返回。
* by {@link }.getBundleAssetName
* e.g. "file://sdcard/myapp_cache/index.android.bundle"
*/
protected @Nullable String getJSBundleFile() {
Toast.makeText(this,JS_BUNDLE_LOCAL_PATH,Toast.LENGTH_SHORT);
return null;
}


上面,我已经备注了。你要从手机的/sdcard/里面加载jsbundle,必须满足两个条件:

1、getJSBundleFile这里设置好返jsbundle的路径;

2、getUseDeveloperSupport开关必须要关闭。因为如果asset里面没有jsbundle,而且开关打开了,那么程序会从packager server获取jsbundle。

根据上面两个点,我们进行改动。这里贴出我们改动后的代码。

- 改动后的MainActivity

改动后的MainActivity主要是要关闭开发支持开关。

具体代码如下:

package com.videolegend;

import com.facebook.react.ReactPackage;
import com.facebook.react.shell.MainReactPackage;

import java.util.Arrays;
import java.util.List;

public class MainActivity extends KKReactBaseActivity {

/**
* Returns the name of the main component registered from JavaScript.
* This is used to schedule rendering of the component.
*/
@Override
protected String getMainComponentName() {
return "videolegend";
}

/**
* Returns whether dev mode should be enabled.
* This enables e.g. the dev menu.
*/
@Override
protected boolean getUseDeveloperSupport() {
// return BuildConfig.DEBUG;关闭开发支持开关
return false;
}

/**
* A list of packages used by the app. If the app uses additional views
* or modules besides the default ones, add more packages here.
*/
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage()
);
}
}


注意到这里我们的MainActivity不在是继承ReactActivity

而是继承KKReactBaseActivity,我们基本是拷贝了ReactActivity的代码。然后修改的。

package com.videolegend;

/**
* Created by zengjinlong on 16/3/30.
*/

import android.app.Activity;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.provider.Settings;
import android.util.Log;
import android.view.KeyEvent;
import android.widget.EditText;
import android.widget.Toast;

import com.facebook.common.logging.FLog;
import com.facebook.react.LifecycleState;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactPackage;
import com.facebook.react.ReactRootView;
import com.facebook.react.common.ReactConstants;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;

import java.io.File;
import java.util.List;

import javax.annotation.Nullable;

/**
* @Author: zengjinlong
* @Date: 2016-03-30
* @purpose: 基于ReactActivity来改的,为了支持热部署,就最好支持离线bundle
*/
public abstract class KKReactBaseActivity extends Activity implements DefaultHardwareBackBtnHandler {
private static final String TAG = "KKReactBaseActivity";
private static final String REDBOX_PERMISSION_MESSAGE =
"Overlay permissions needs to be granted in order for react native apps to run in dev mode";
public static final String JS_BUNDLE_LOCAL_FILE = "videolegend.bundle";
//JS bundle的本地加载路径
public static final String JS_BUNDLE_LOCAL_PATH = Environment.getExternalStorageDirectory().toString() + File.separator + JS_BUNDLE_LOCAL_FILE;

private @Nullable
ReactInstanceManager mReactInstanceManager;
private LifecycleState mLifecycleState = LifecycleState.BEFORE_RESUME;
private boolean mDoRefresh = false;

/**
* Returns the name of the bundle in assets. If this is null, and no file path is specified for
* the bundle, the app will only work with {@code getUseDeveloperSupport} enabled and will
* always try to load the JS bundle from the packager server.
* e.g. "index.android.bundle"
*/
protected @Nullable String getBundleAssetName() {
return "index.android.bundle";
};

/**
* 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() {
Toast.makeText(this,JS_BUNDLE_LOCAL_PATH,Toast.LENGTH_SHORT);
return JS_BUNDLE_LOCAL_PATH;//这里返回jsbundle文件路径
// return null;
}

/**
* Returns the name of the main module. Determines the URL used to fetch the JS bundle
* from the packager server. It is only used when dev support is enabled.
* This is the first file to be executed once the {@link ReactInstanceManager} is created.
* e.g. "index.android"
*/
protected String getJSMainModuleName() {
return "index.android";
}

/**
* Returns the launchOptions which will be passed to the {@link ReactInstanceManager}
* when the application is started. By default, this will return null and an empty
* object will be passed to your top level component as its initial props.
* If your React Native application requires props set outside of JS, override
* this method to return the Android.os.Bundle of your desired initial props.
*/
protected @Nullable
Bundle getLaunchOptions() {
return null;
}

/**
* Returns the name of the main component registered from JavaScript.
* This is used to schedule rendering of the component.
* e.g. "MoviesApp"
*/
protected abstract String getMainComponentName();

/**
* Returns whether dev mode should be enabled. This enables e.g. the dev menu.
*/
protected abstract boolean getUseDeveloperSupport();

/**
* Returns a list of {@link ReactPackage} used by the app.
* You'll most likely want to return at least the {@code MainReactPackage}.
* If your app uses additional views or modules besides the default ones,
* you'll want to include more packages here.
*/
protected abstract List<ReactPackage> getPackages();

/**
* A subclass may override this method if it needs to use a custom instance.
*/
protected ReactInstanceManager createReactInstanceManager() {
ReactInstanceManager.Builder builder = ReactInstanceManager.builder()
.setApplication(getApplication())
.setJSMainModuleName(getJSMainModuleName())
.setUseDeveloperSupport(getUseDeveloperSupport())
.setInitialLifecycleState(mLifecycleState);

for (ReactPackage reactPackage : getPackages()) {
builder.addPackage(reactPackage);
}

String jsBundleFile = getJSBundleFile();
Log.d(TAG,"jsBundleFile:"+jsBundleFile);
if (jsBundleFile != null) {
Log.d(TAG,"setJSBundleFile now");
builder.setJSBundleFile(jsBundleFile);
} else {
builder.setBundleAssetName(getBundleAssetName());
}

return builder.build();
}

/**
* A subclass may override this method if it needs to use a custom {@link ReactRootView}.
*/
protected ReactRootView createRootView() {
return new ReactRootView(this);
}

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

if (getUseDeveloperSupport() && Build.VERSION.SDK_INT >= 23) {
// Get permission to show redbox in dev builds.
if (!Settings.canDrawOverlays(this)) {
Intent serviceIntent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
startActivity(serviceIntent);
FLog.w(ReactConstants.TAG, REDBOX_PERMISSION_MESSAGE);
Toast.makeText(this, REDBOX_PERMISSION_MESSAGE, Toast.LENGTH_LONG).show();
}
}

mReactInstanceManager = createReactInstanceManager();
ReactRootView mReactRootView = createRootView();
mReactRootView.startReactApplication(mReactInstanceManager, getMainComponentName(), getLaunchOptions());
setContentView(mReactRootView);
}

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

mLifecycleState = LifecycleState.BEFORE_RESUME;

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

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

mLifecycleState = LifecycleState.RESUMED;

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

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

if (mReactInstanceManager != null) {
mReactInstanceManager.destroy();
}
}

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (mReactInstanceManager != null) {
mReactInstanceManager.onActivityResult(requestCode, resultCode, data);
}
}

@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (mReactInstanceManager != null &&
mReactInstanceManager.getDevSupportManager().getDevSupportEnabled()) {
if (keyCode == KeyEvent.KEYCODE_MENU) {
mReactInstanceManager.showDevOptionsDialog();
return true;
}
if (keyCode == KeyEvent.KEYCODE_R && !(getCurrentFocus() instanceof EditText)) {
// Enable double-tap-R-to-reload
if (mDoRefresh) {
mReactInstanceManager.getDevSupportManager().handleReloadJS();
mDoRefresh = false;
} else {
mDoRefresh = true;
new Handler().postDelayed(
new Runnable() {
@Override
public void run() {
mDoRefresh = false;
}
},
200);
}
}
}
return super.onKeyUp(keyCode, event);
}

@Override
public void onBackPressed() {
if (mReactInstanceManager != null) {
mReactInstanceManager.onBackPressed();
} else {
super.onBackPressed();
}
}

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


这样,我们就支持了离线的jsbundle加载,这为热部署提供了支持。下面一节,我们实现我们的热部署细节。

包括版本管理。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  native 热部署