快速搭建基于React Native的插件化Android工程
2021-04-13 本文已影响0人
hgwxr
快速搭建基于React Native的插件化Android工程
日常的产品研发中,随着公司的快速发展,业务也随着增多,巨多的的开发需求需要让应用快速迭代和上线,这就需要用户频繁的更新应用,造成十分不友好的体验,因此需要应用具有快速上线的能力,即插件化功能。在这里介绍一种基于RN的插件化Android工程搭建方式。
React Native
一种跨平台框架
技术背景
React Native 环境安装(Mac)
Read Native 打离线包
mkdir bundle & cd bundle & npx react-native bundle --platform android --dev false --entry-file index.js --bundle-output bundle/rnsample2.bundle --assets-dest bundle/
zip压缩(macOS)
zip -r rnsample2.zip
搭建步骤
-
新建Android工程
-
添加ReactNative依赖
yarn add react-native
-
Android的工程下build.gradle
allprojects { repositories { maven { // All of React Native (JS, Android binaries) is installed from npm url uri("$rootDir/node_modules/react-native/android") } maven { // Android JSC is installed from npm url uri("$rootDir/node_modules/jsc-android/dist") } google() jcenter() } }
-
app下的build.gradle
implementation "com.facebook.react:react-native:+" // From node_modules implementation "org.webkit:android-jsc:+" implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'//协程使用
-
解压复制Zip
private fun copyToDisk( applicationName: String, suffix: String, ) { val absolutePath = filesDir.absolutePath val fileApp1 = File(absolutePath, applicationName + suffix) if (!fileApp1.exists()) { val inputStream = assets.open(applicationName + suffix) File( filesDir.absolutePath, applicationName + suffix ).writeBytes(inputStream.readBytes()) } }
private fun unZipApplication( applicationName: String, suffix: String, ) { val absolutePath = filesDir.absolutePath val fileApp1 = File(absolutePath, applicationName + suffix) val applicationFile1 = File(absolutePath, applicationName) if (!applicationFile1.exists() || !applicationFile1.isDirectory) { //unzip val zipFile = ZipFile(fileApp1) val entries = zipFile.entries() applicationFile1.mkdirs() val buffer = ByteArray(1024 * 1024 * 2) while (entries.hasMoreElements()) { entries.nextElement()?.apply { val inputStream = zipFile.getInputStream(this) val file = File(applicationFile1, name) if (isDirectory) { file.mkdirs() } else { file.createNewFile() val outputStream = FileOutputStream(file) var len: Int while (inputStream.read(buffer).also { len = it } > 0) { outputStream.write(buffer, 0, len) } inputStream.close() outputStream.close() } } } } }
-
构建简单的ReatInstanceManager
import android.app.Activity import com.facebook.react.ReactInstanceManager import com.facebook.react.ReactInstanceManagerBuilder import com.facebook.react.common.LifecycleState import com.facebook.react.shell.MainReactPackage object ReactInstanceManagerHelper { private val mbMaps = mutableMapOf<Int, ReactInstanceManager>() fun getManager( activity: ContainerActivity, cb: (builder: ReactInstanceManagerBuilder) -> Unit = {} ): ReactInstanceManager? { val keyCode = activity.hashCode() var reactInstanceManager = mbMaps[keyCode] if (reactInstanceManager == null) { val nativeModuleCallExceptionHandler = ReactInstanceManager.builder() .addPackage(MainReactPackage()) .setApplication(activity.application) .setCurrentActivity(activity) .setInitialLifecycleState(LifecycleState.RESUMED) .setNativeModuleCallExceptionHandler { e: Exception -> e.printStackTrace() } cb(nativeModuleCallExceptionHandler) val ct = nativeModuleCallExceptionHandler.build() reactInstanceManager = ct mbMaps[keyCode] = ct } return reactInstanceManager } fun clear(activity: Activity) { mbMaps.remove(activity.hashCode()) } }
-
容器类加载逻辑
import android.app.Activity import android.app.ActivityManager.TaskDescription import android.content.Intent import android.os.Build import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import com.facebook.react.ReactRootView import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler import kotlinx.coroutines.* import java.io.File import java.io.FileOutputStream import java.util.zip.ZipFile class ContainerActivity : AppCompatActivity(), CoroutineScope by MainScope(), DefaultHardwareBackBtnHandler { companion object { private const val appName = "applicationName" private const val mName = "methodName" fun start(activity: Activity, applicationName: String, methodName: String) { val intent = Intent(activity, ContainerActivity::class.java) intent.putExtra(appName, applicationName) intent.putExtra(mName, methodName) intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT); intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); activity.startActivity(intent) } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_content_rn) val applicaiton1 = intent.getStringExtra(appName) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { setTaskDescription(TaskDescription(applicaiton1)) } val methodName = intent.getStringExtra(mName) applicaiton1?.let { val suffix = ".zip" runBlocking { withContext(Dispatchers.IO) { copyToDisk(applicaiton1, suffix) unZipApplication(applicaiton1, suffix) } val jsBundle = ".bundle" val bundlePath = filesDir.absolutePath + File.separator + applicaiton1 + File.separator + applicaiton1 + jsBundle val manager = ReactInstanceManagerHelper.getManager(this@ContainerActivity) { it.setJSBundleFile(bundlePath) it.setDefaultHardwareBackBtnHandler(this@ContainerActivity) // it.setUseDeveloperSupport(BuildConfig.DEBUG) } val reactRootView = findViewById<ReactRootView>(R.id.containerRn) reactRootView?.startReactApplication( manager, methodName, ) } } } private fun unZipApplication( applicationName: String, suffix: String, ) { val absolutePath = filesDir.absolutePath val fileApp1 = File(absolutePath, applicationName + suffix) val applicationFile1 = File(absolutePath, applicationName) if (!applicationFile1.exists() || !applicationFile1.isDirectory) { //unzip val zipFile = ZipFile(fileApp1) val entries = zipFile.entries() applicationFile1.mkdirs() val buffer = ByteArray(1024 * 1024 * 2) while (entries.hasMoreElements()) { entries.nextElement()?.apply { val inputStream = zipFile.getInputStream(this) val file = File(applicationFile1, name) if (isDirectory) { file.mkdirs() } else { file.createNewFile() val outputStream = FileOutputStream(file) var len: Int while (inputStream.read(buffer).also { len = it } > 0) { outputStream.write(buffer, 0, len) } inputStream.close() outputStream.close() } } } } } private fun copyToDisk( applicationName: String, suffix: String, ) { val absolutePath = filesDir.absolutePath val fileApp1 = File(absolutePath, applicationName + suffix) if (!fileApp1.exists()) { val inputStream = assets.open(applicationName + suffix) File( filesDir.absolutePath, applicationName + suffix ).writeBytes(inputStream.readBytes()) } } // fun override fun onDestroy() { super.onDestroy() cancel() ReactInstanceManagerHelper.getManager(this@ContainerActivity)?.onHostDestroy(this) ReactInstanceManagerHelper.clear(this) } override fun invokeDefaultOnBackPressed() { super.onBackPressed() } override fun onPause() { super.onPause() ReactInstanceManagerHelper.getManager(this@ContainerActivity)?.onHostPause(this) } override fun onResume() { super.onResume() ReactInstanceManagerHelper.getManager(this@ContainerActivity)?.onHostResume(this, this) } override fun onBackPressed() { ReactInstanceManagerHelper.getManager(this@ContainerActivity)?.onBackPressed() } }