快速搭建基于React Native的插件化Android工程

·  阅读 345

快速搭建基于React Native的插件化Android工程

日常的产品研发中,随着公司的快速发展,业务也随着增多,巨多的的开发需求需要让应用快速迭代和上线,这就需要用户频繁的更新应用,造成十分不友好的体验,因此需要应用具有快速上线的能力,即插件化功能。在这里介绍一种基于RN的插件化Android工程搭建方式。

React Native

一种跨平台框架

了解RN

技术背景

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()
        }
    }
    复制代码

运行效果

1.webp

2.webp

工程地址

RnSample2

RnSample1

ReactMuiltModule

分类:
Android
分类:
Android
收藏成功!
已添加到「」, 点击更改