美文网首页
桥接原生

桥接原生

作者: zhang_pan | 来源:发表于2024-04-23 14:00 被阅读0次

    为什么需要桥接原生

    实现react层JS实现不了的需求:

    1. 复杂、高性能组件:复杂表格、视频播放等
    2. 原生层开发能力:传感器编程、widget等
    3. 平台属性:系统信息、设备信息等
    4. 对接三方应用:相机、相册、地图等

    桥接原生实现JS调用原生方法

    1. 编写并注册原生层方法

    AppModule.kt

    package com.awesomeproject.rn
    
    import com.awesomeproject.BuildConfig
    import com.awesomeproject.utils.DeviceUtil
    import com.facebook.react.bridge.Promise
    import com.facebook.react.bridge.ReactApplicationContext
    import com.facebook.react.bridge.ReactContextBaseJavaModule
    import com.facebook.react.bridge.ReactMethod
    
    /**
     *
     * created by ZhangPan on 2024/4/22
     */
    class AppModule(context: ReactApplicationContext) : ReactContextBaseJavaModule(context) {
        override fun getName(): String {
            return "App"
        }
    
        @ReactMethod
        fun openGallery() {
            if (currentActivity == null) return
            DeviceUtil.openGallery(currentActivity)
        }
    
        @ReactMethod
        fun getVersionName(promise: Promise) {
            val version = BuildConfig.VERSION_NAME
            if (version == null) {
                promise.reject(Throwable("获取版本失败"))
            } else {
                promise.resolve(version)
            }
        }
    }
    

    DemoReactPackager.kt 使用AppModule

    package com.awesomeproject.rn
    
    import android.view.View
    import com.facebook.react.ReactPackage
    import com.facebook.react.bridge.NativeModule
    import com.facebook.react.bridge.ReactApplicationContext
    import com.facebook.react.uimanager.ReactShadowNode
    import com.facebook.react.uimanager.ViewManager
    import java.util.Collections
    
    /**
     *
     * created by ZhangPan on 2024/4/22
     */
    class DemoReactPackager : ReactPackage {
        override fun createNativeModules(context: ReactApplicationContext): MutableList<NativeModule> {
            return mutableListOf<NativeModule>().apply {
                add(AppModule(context))
            }
        }
    
        override fun createViewManagers(context: ReactApplicationContext): MutableList<ViewManager<View, ReactShadowNode<*>>> {
            return Collections.emptyList()
        }
    }
    

    Application中注册

    package com.awesomeproject
    
    import android.app.Application
    import com.awesomeproject.rn.DemoReactPackager
    import com.facebook.react.PackageList
    import com.facebook.react.ReactApplication
    import com.facebook.react.ReactHost
    import com.facebook.react.ReactNativeHost
    import com.facebook.react.ReactPackage
    import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load
    import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
    import com.facebook.react.defaults.DefaultReactNativeHost
    import com.facebook.react.flipper.ReactNativeFlipper
    import com.facebook.soloader.SoLoader
    
    class MainApplication : Application(), ReactApplication {
    
      override val reactNativeHost: ReactNativeHost =
          object : DefaultReactNativeHost(this) {
            override fun getPackages(): List<ReactPackage> =
                PackageList(this).packages.apply {
                  // Packages that cannot be autolinked yet can be added manually here, for example:
                  // add(MyReactNativePackage())
                  add(DemoReactPackager())
                }
    
            override fun getJSMainModuleName(): String = "index"
    
            override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG
    
            override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
            override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED
          }
    
      override val reactHost: ReactHost
        get() = getDefaultReactHost(this.applicationContext, reactNativeHost)
    
      override fun onCreate() {
        super.onCreate()
        SoLoader.init(this, false)
        if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
          // If you opted-in for the New Architecture, we load the native entry point for this app.
          load()
        }
        ReactNativeFlipper.initializeFlipper(this, reactNativeHost.reactInstanceManager)
      }
    }
    
    
    2. JS层调用原生方法

    NativePage.tsx

    import { Button, NativeModules, StyleSheet, View } from 'react-native'
    
    export default () => {
        return(
            <View style={styles.root}>
                <Button 
                    title='调用原生方法'
                    onPress={() => {
                        const { App } = NativeModules;
                        // App?.openGallery() 
                        App?.getVersionName().then((data: string) => {
                            console.log(data)
                        })
                    }}
                />
            </View>
        );
    
    }
    
    const styles = StyleSheet.create({
        root: {
            "width": '100%',
            "height": '100%',
            backgroundColor: 'red'
        },
    })
    

    桥接原生常量

    override fun getConstants(): Map<String, Any> {
            return mutableMapOf<String, Any>(
                "VersionName" to BuildConfig.VERSION_NAME,
                "VersionCode" to BuildConfig.VERSION_CODE
            )
        }
    

    JS调用原生:

    const { VersionName, VersionCode } = App;
    console.log(`VersionNmae: ${VersionName}, VersionCode: ${VersionCode}`)
    

    桥接原生原子组件

    1. 实现一个原生自定义组件View

    InfoView.kt

    package com.awesomeproject.view
    
    import android.view.LayoutInflater
    import android.widget.LinearLayout
    import com.awesomeproject.R
    import com.facebook.react.uimanager.ThemedReactContext
    
    /**
     *
     * created by ZhangPan on 2024/4/23
     */
    class InfoView(context: ThemedReactContext) : LinearLayout(context) {
        init {
            initView(context)
        }
    
        private fun initView(context: ThemedReactContext) {
            val view = LayoutInflater.from(context).inflate(R.layout.layout_infoview, null, false)
            val layoutParams =
                LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
            this.addView(view, layoutParams)
        }
    }
    

    2. 创建ViewManager,用于接管原生自定义组件的属性和行为,并把ViewManager注册到ReactPackage中。

    InfoViewManager.kt

    package com.awesomeproject.viewManager
    
    import com.awesomeproject.view.InfoView
    import com.facebook.react.uimanager.SimpleViewManager
    import com.facebook.react.uimanager.ThemedReactContext
    
    /**
     *
     * created by ZhangPan on 2024/4/23
     */
    class InfoViewManager: SimpleViewManager<InfoView>() {
        override fun getName(): String {
            return "NativeInfoView"
        }
    
        override fun createViewInstance(context: ThemedReactContext): InfoView {
            return InfoView(context)
        }
    }
    

    DemoReactPackager.kt

    package com.awesomeproject.rn
    
    import com.awesomeproject.viewManager.InfoViewManager
    import com.facebook.react.ReactPackage
    import com.facebook.react.bridge.NativeModule
    import com.facebook.react.bridge.ReactApplicationContext
    import com.facebook.react.uimanager.ViewManager
    
    /**
     *
     * created by ZhangPan on 2024/4/22
     */
    class DemoReactPackager : ReactPackage {
        override fun createNativeModules(context: ReactApplicationContext): MutableList<NativeModule> {
            return mutableListOf<NativeModule>().apply {
                add(AppModule(context))
            }
        }
    
        override fun createViewManagers(context: ReactApplicationContext): MutableList<ViewManager<*, *>> {
            val mutableListOf = mutableListOf<ViewManager<*, *>>()
            mutableListOf.add(InfoViewManager())
            return mutableListOf
        }
    
    }
    

    3. 在JS层导入原生组件,并封装导出JS模块

    import { StyleSheet, ViewProps, requireNativeComponent } from "react-native"
    
    
    type NativeInfoViewType = ViewProps | {
        //这部分是自定义的属性
    }
    
    const NativeInfoView =  requireNativeComponent<NativeInfoViewType>('NativeInfoView');
    
    export default () => {
        return(
            <NativeInfoView style={styles.inforView}/>
        )
    }
    
    const styles = StyleSheet.create({
        inforView: {
            width:'100%',
            height: '100%'
        }
    })
    
    

    4. 使用@ReactProp定义组件属性

    InfoViewManager.kt中:

    @ReactProp(name = "name")
        fun setName(infoView: InfoView, name: String) {
            infoView.setName(name)
        }
    
        @ReactProp(name = "desc")
        fun setDesc(infoView: InfoView, desc: String) {
            infoView.setDesc(desc)
        }
    

    InfoView.kt:

    package com.awesomeproject.view
    
    import android.view.LayoutInflater
    import android.widget.LinearLayout
    import android.widget.TextView
    import com.awesomeproject.R
    import com.facebook.react.uimanager.ThemedReactContext
    
    /**
     *
     * created by ZhangPan on 2024/4/23
     */
    class InfoView(context: ThemedReactContext) : LinearLayout(context) {
        private var tvDesc: TextView? = null
        private var tvName: TextView? = null
    
        init {
            initView(context)
        }
    
        private fun initView(context: ThemedReactContext) {
            val view = LayoutInflater.from(context).inflate(R.layout.layout_infoview, null, false)
            tvName = view.findViewById(R.id.tvName)
            tvDesc = view.findViewById(R.id.tvDesc)
            val layoutParams =
                LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
            this.addView(view, layoutParams)
        }
    
        fun setName(name: String) {
            tvName?.setText(name)
        }
    
        fun setDesc(desc: String) {
            tvDesc?.setText(desc)
        }
    }
    

    js中使用:

    import { StyleSheet, ViewProps, requireNativeComponent } from "react-native"
    
    
    type NativeInfoViewType = ViewProps | {
        //这部分是自定义的属性
        name: string,
        desc: string
    }
    
    const NativeInfoView =  requireNativeComponent<NativeInfoViewType>('NativeInfoView');
    
    export default () => {
        return(
            <NativeInfoView 
                style={styles.inforView}
                name='尼古拉斯-拜登'
                desc="美国总统"
            />
        )
    }
    
    const styles = StyleSheet.create({
        inforView: {
            width:'100%',
            height: '100%'
        }
    })
    
    

    5. 原生组件回调JS层方法

    InfoView.kt 做了一个点击事件,然后回调给JS层:

    override fun onClick(view: View?) {
            if (view == tvName) {
                nameValue = if (nameValue == "尼古拉斯-段坤") {
                    "尼古拉斯-拜登"
                } else {
                    "尼古拉斯-段坤"
                }
                setName(nameValue)
                val params = Arguments.createMap()
                params.putString("value", nameValue)
                val context = context as ReactContext
                context.getJSModule(RCTEventEmitter::class.java).receiveEvent(id, "onValueChanged", params)
            }
        }
    

    InfoViewManager.kt 重写getExportedCustomBubblingEventTypeConstants这个方法:

    override fun getExportedCustomBubblingEventTypeConstants(): MutableMap<String, Any>? {
            return MapBuilder.builder<String, Any>()
                            .put("onValueChanged",
                                    MapBuilder.of(
                                        "phasedRegistrationNames",
                                            MapBuilder.of("bubbled", "onValueChanged")
                                    )
                                ).build()
        }
    

    NativeInfoView.tsx 使用如下:

    import { StyleSheet, ViewProps, requireNativeComponent } from "react-native"
    
    
    type NativeInfoViewType = ViewProps | {
        //这部分是自定义的属性
        name: string,
        desc: string,
        onValueChanged: (e: any) => void
    }
    
    const NativeInfoView =  requireNativeComponent<NativeInfoViewType>('NativeInfoView');
    
    export default () => {
        return(
            <NativeInfoView 
                style={styles.inforView}
                name='尼古拉斯-拜登'
                desc="美国总统"
                onValueChanged={(e: any) => {
                    console.log(e.nativeEvent.value)
                }}
            />
        )
    }
    
    const styles = StyleSheet.create({
        inforView: {
            width:'100%',
            height: '100%'
        }
    })
    
    

    6. 公共原生组件方法给JS层调用

    NativeInfoView.tsx中定义senCommands方法:

    import { useEffect, useRef } from "react";
    import { StyleSheet, UIManager, ViewProps, findNodeHandle, requireNativeComponent } from "react-native"
    
    
    type NativeInfoViewType = ViewProps | {
        //这部分是自定义的属性
        name: string,
        desc: string,
        onValueChanged: (e: any) => void
    }
    
    const NativeInfoView =  requireNativeComponent<NativeInfoViewType>('NativeInfoView');
    
    export default () => {
        const ref = useRef(null);
        useEffect(() => {
            setTimeout(() => {
                sendCommands("setName", ["尼古拉斯-段坤"])
            }, 3000)
        }, [])
    
        const sendCommands = (command: string, params: any[]) => {
            const viewId = findNodeHandle(ref.current);
            // @ts-ignore
            const commands =  UIManager.NativeInfoView.Commands[command].toString();
            UIManager.dispatchViewManagerCommand(viewId, commands, params);
        }
    
        return(
            <NativeInfoView 
                ref={ref}
                style={styles.inforView}
                name='尼古拉斯-拜登'
                desc="美国总统"
                onValueChanged={(e: any) => {
                    console.log("--- " + e.nativeEvent.value)
                }}
            />
        )
    }
    
    const styles = StyleSheet.create({
        inforView: {
            width:'100%',
            height: '100%'
        }
    })
    
    

    InfoViewManager.kt中增加如下代码:

    val SET_NAME_CODE = 100
    
        override fun getCommandsMap(): MutableMap<String, Int>? {
            return MapBuilder.of("setName", SET_NAME_CODE)
        }
    
        override fun receiveCommand(view: InfoView, commandId: String?, args: ReadableArray?) {
            val command = commandId?.toInt()
            if (command == SET_NAME_CODE) {
                if (args != null && args.size() > 0) {
                    val nameValue = args.getString(0)
                    view.setName(nameValue)
                }
            } else {
                super.receiveCommand(view, commandId, args)
            }
        }
    

    InfoView.kt中增加setName方法:

    fun setName(name: String) {
            this.nameValue = name
            val value = if (nameValue == "尼古拉斯-段坤") {
                "尼古拉斯-拜登"
            } else {
                "尼古拉斯-段坤"
            }
            tvName?.setText(value)
        }
    

    桥接原生容器组件

    1. 实现一个原生容器组件

    InfoViewGroup.kt

    package com.awesomeproject.view
    
    import android.widget.LinearLayout
    import com.facebook.react.uimanager.ThemedReactContext
    
    /**
     *
     * created by ZhangPan on 2024/4/24
     */
    class InfoViewGroup(context: ThemedReactContext) : LinearLayout(context) {
    
    }
    
    1. 创建ViewGroupManager,注册行为和ViewManager一致

    InfoViewGroupManager.kt

    package com.awesomeproject.viewManager
    
    import com.awesomeproject.view.InfoViewGroup
    import com.facebook.react.uimanager.ThemedReactContext
    import com.facebook.react.uimanager.ViewGroupManager
    
    /**
     *
     * created by ZhangPan on 2024/4/24
     */
    class InfoViewGroupManager : ViewGroupManager<InfoViewGroup>() {
        override fun getName(): String {
            return "NativeInfoViewGroup"
        }
    
        override fun createViewInstance(context: ThemedReactContext): InfoViewGroup {
            return InfoViewGroup(context)
        }
    }
    

    DemoReactPackager.kt

    package com.awesomeproject.rn
    
    import com.awesomeproject.viewManager.InfoViewGroupManager
    import com.awesomeproject.viewManager.InfoViewManager
    import com.facebook.react.ReactPackage
    import com.facebook.react.bridge.NativeModule
    import com.facebook.react.bridge.ReactApplicationContext
    import com.facebook.react.uimanager.ViewManager
    
    /**
     *
     * created by ZhangPan on 2024/4/22
     */
    class DemoReactPackager : ReactPackage {
        override fun createNativeModules(context: ReactApplicationContext): MutableList<NativeModule> {
            return mutableListOf<NativeModule>().apply {
                add(AppModule(context))
            }
        }
    
        override fun createViewManagers(context: ReactApplicationContext): MutableList<ViewManager<*, *>> {
            val mutableListOf = mutableListOf<ViewManager<*, *>>()
            mutableListOf.add(InfoViewManager())
            mutableListOf.add(InfoViewGroupManager())
            return mutableListOf
        }
    }
    
    1. 在JS层导入原生组件,并封装导出JS模块

    NativeInfoViewGroup.tsx

    import { useEffect, useRef } from "react";
    import { StyleSheet, UIManager, ViewProps, findNodeHandle, requireNativeComponent } from "react-native"
    
    
    type NativeInfoViewGroupType = ViewProps | {
        
    }
    
    const NativeInfoViewGroup =  requireNativeComponent<NativeInfoViewGroupType>('NativeInfoViewGroup');
    
    export default (props : any) => {
        const { children } = props;
    
        return(
            <NativeInfoViewGroup 
                style={styles.inforViewGroup}
            >
                { children }
            </NativeInfoViewGroup>
        )
    }
    
    const styles = StyleSheet.create({
        inforViewGroup: {
            width:'100%',
            height: '100%'
        }
    })
    

    NativePage.tsx

    import { Button, NativeModules, StyleSheet, View } from 'react-native'
    import NativeInfoView from './NativeInfoView';
    import NativeInfoViewGroup from './NativeInfoViewGroup';
    
    export default () => {
        return(
            <View style={styles.root}>
                <Button 
                    title='调用原生方法'
                    onPress={() => {
                        const { App } = NativeModules;
                        // App?.openGallery() 
                        // App?.getVersionName().then((data: string) => {
                        //     console.log(data)
                        // })
                        const { VersionName, VersionCode } = App;
                        console.log(`VersionNmae: ${VersionName}, VersionCode: ${VersionCode}`)
                    }}
                />
                {/* <NativeInfoView /> */}
                <NativeInfoViewGroup>
                    <View style={styles.view}></View>
                </NativeInfoViewGroup>
            </View>
        );
    
    }
    
    const styles = StyleSheet.create({
        root: {
            "width": '100%',
            "height": '100%',
        },
        view: {
            width: 100,
            height: 100,
            backgroundColor: 'red'
        },
    })
    
    1. 属性、方法回调、api调用和ViewManager一致

    相关文章

      网友评论

          本文标题:桥接原生

          本文链接:https://www.haomeiwen.com/subject/udxvxjtx.html