[ReactNative翻译]用Kotlin、Flutter和React Native制作一个安卓应用。

436 阅读8分钟

本文由 简悦SimpRead 转码,原文地址 medium.com

用这些强大的技术创建一个应用程序的意义何在?更加强大的是,...... 的力量

image.png

照片由Pavan TrikutamUnsplash

用这些强大的技术创建一个应用程序的意义何在?甚至更多的力量,自由的力量,不受一个框架的限制。

然而,这并不适合所有的产品的情况。使用这3种技术的应用程序的建议使得多个团队的产品与他们选择的框架同步工作。了解这一点的一个好方法是看Facebook的这个视频。

www.youtube.com/watch?v=NCA…

这使得不同的产品团队在开发一个App时,可以自由地单独选择他们将使用的技术。例如,一个团队可以选择React Native作为他们的开发技术,另一个团队可以选择Flutter,根据每个团队的熟悉程度和偏好。

你突然跳进这篇文章,有两个原因。

  • 你很好奇,就像我一样 🤓。
  • 你想做一个拥有这种技术堆栈的大型APP 👊。

恭喜你,这篇文章将帮助你了解原生端如何通过一个小的POC来处理Flutter和React Native。在未来的文章中,我将探讨最佳实践和模块共享。

Prerequisites

首先,原生开发的基础知识在这里很重要,所以如果你从来没有接触过原生端,我在下面丢了一个链接,里面有一个Udacity课程。其次,如果你从未使用过React Native、Flutter或两者,请关注这些入门的文章,它们有助于设置正确的环境,使你的机器与框架很好地工作。

Udacity课程

React Native

Flutter

假设你已经得到了所有的工作和清洁,测试了React Native和Flutter,我们就可以开始了。

Foundation

首先,我们需要我们的核心是一个Android项目,打开Android Studio并创建一个新项目。

image.png

选择空的活动模板,所以我们已经开始有一个活动、布局和清单了。

image.png

给你的项目取个名字。

image.png

将保存位置改为应用程序名称,并将该项目放在android文件夹内,就像这样。

image.png

点击完成,在项目创建和Gradle同步后,打开activity_main.xml并添加两个按钮。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/btn_react_native"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Open React Native!" />

    <Button
        android:id="@+id/btn_flutter"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Open Flutter!" />

</LinearLayout>

很好,现在我们的活动布局有了两个按钮,将来可以打开我们各自的框架。现在让我们准备我们的MainActivity,我们只需要为你刚刚添加的按钮添加监听器。

package com.somehugeapp

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        btn_flutter.setOnClickListener {
            //start flutter activity
        }

        btn_react_native.setOnClickListener {
            //start react native activity
        }

    }
}

Flutter

随着我们的核心准备好接收这两个框架,我们要开始添加Flutter了 有两种方法可以将Flutter添加到我们的项目中。

  • 使用Android Studio上的Flutter插件自动添加
  • 使用Flutter CLI手动添加

自动添加

自动方式只能用Android Studio 3.6来处理,目前还处于测试阶段,所以如果您想采用这种方式,可以下载测试版本这里

Android studio将处理所有的配置,一个重要的提示是在执行任何步骤之前初始化你的Android项目的源码控制,以查看什么在变化。本地差异显示了这些变化。

首先,我们需要在应用程序build.gradle中设置abiFilters,因为Flutter目前只支持为armeabi-v7aarm64-v8a构建提前编译的库。如果你想知道Flutter运行什么设备,你可以找到它这里

你不需要模拟ARM设备,Flutter在调试时正常运行,因为引擎有x86x86_64版本。对于发布模式,我建议你使用你的手机来获得完整的体验。

编辑你的应用程序build.gradle并在android { }中加入这些行。

android {
  //...
  defaultConfig {
    //...
     ndk {
       // Filter for architectures supported by Flutter.
       abiFilters 'armeabi-v7a', 'arm64-v8a'
     }
  }
}

NDK是一个工具集,可以让你使用C和C++等语言在本地代码中实现应用程序的部分内容。

现在进入File > New > New Module,你应该看到这样一个屏幕。

image.png

向下滚动,你会看到一个的Flutter模块

image.png

为您的Flutter模块设置一个名称。

image.png

设置包域并完成。

image.png

等待Android Studio构建新的模块,我们就可以准备配置核心来接收Flutter包了。我在手动模式后继续。

手动

在我们现有的Android应用上输入your/path/SomeApp,并且你想让我们的Flutter项目作为一个兄弟姐妹。

$ cd some/path/SomeApp
$ flutter create -t module --org com.example my_flutter

Flutter CLI将创建一个新模块,你的工作区文件夹应该是这样的。

image.png

在尝试将你的Flutter模块项目连接到你的主机Android应用之前,确保你的主机Android应用在你的应用的build.gradle文件中,在android { }块下声明了以下源兼容性。

android {
  //...
  compileOptions {
    sourceCompatibility 1.8
    targetCompatibility 1.8
  }
}

Flutter Android引擎使用Java 8特性。

现在将Flutter模块作为一个子项目包含在主机应用的settings.gradle中。

rootProject.name='SomeHugeApp'
include ':app' // assumed existing content
setBinding(new Binding([gradle: this]))                                 
evaluate(new File(
        settingsDir.parentFile,
        'my_flutter/.android/include_flutter.groovy'
))

假设my_flutterSomeHugeApp的一个兄弟姐妹。

绑定和脚本评估允许Flutter模块在你的settings.gradle的评估上下文中包括自己(作为:flutter)和模块使用的任何Flutter插件(作为:package_info:video_player,等等)。

让我们在应用程序build.gradle:中把Flutter作为一个依赖项加入。

dependecies {
  //....
  implementation project(':flutter')
}

打开一个Flutter屏幕!

Flutter提供了FlutterActivity以在Android应用中显示Flutter体验。像其他的Activity一样,FlutterActivity必须在你的AndroidManifest.xml中注册。在你的AndroidManifest.xml文件中的application标签下添加以下XML,你的AndroidManifest.xml应该看起来像这样。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.somehugeapp">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>

        </activity>

        <activity
            android:name="io.flutter.embedding.android.FlutterActivity"
            android:theme="@style/Theme.AppCompat.NoActionBar"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
            android:hardwareAccelerated="true"
            android:windowSoftInputMode="adjustResize"
            />

    </application>

</manifest>

现在让我们在本文开始时创建的监听器上调用我们的FlutterActivity。回到MainActivity,在Flutter按钮点击监听器中添加startActivity方法。

package com.somehugeapp

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import io.flutter.embedding.android.FlutterActivity
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        btn_flutter.setOnClickListener {
            //start flutter activity

            startActivity(
                    FlutterActivity.createDefaultIntent(this)
            )
        }

        btn_react_native.setOnClickListener {
            //start react native activity
        }

    }
}

结果。

1.gif

它工作正常。在打开活动时仍然有很大的延迟,有一些原因

  • 调试模式
  • 不是缓存的引擎

你可以看到什么是缓存引擎这里

React Native

假设你已经阅读了Getting Started guide来配置你的开发环境。

按照React Native的代码集成文档。

为了确保流畅的体验,为你的集成React Native项目创建一个新的文件夹,然后把你现有的Android项目复制到/android子文件夹中。

我们已经将我们的Android核心项目设置在android文件夹中,我不会在本文中讨论模块化,因为我会失去重点,在下一篇文章中我将深入探讨模块化问题。

安装JS依赖项

确保你已经安装了yarn包管理器

到你项目的根目录下,创建一个新的package.json文件,内容如下。

{
  "name": "somehugeapp",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "start": "yarn react-native start"
  }
}

为了安装React Native包,在项目根目录下的终端或命令提示符上运行这一行。

$ yarn add react-native

这将打印出类似以下的信息(在yarn输出中向上滚动以看到它)。

警告 "> react-native@0.61.5" 有未满足的同行依赖 "react@16.9.0"。

这意味着我们还需要安装React。

$ yarn add react@VERSION_ABOVE

你会注意到一个node_modules文件夹,这个文件夹包含了一些Android(Gradle文件)的依赖,以使React Native在我们已经建立的核心项目中工作。

现在我们要在我们的Android项目中添加一个对node_modules中这些依赖项的引用。将React Native的依赖关系添加到你的应用程序的build.gradle文件中。

dependencies {
     //...
     implementation "com.facebook.react:react-native:+"
}

build.gradle中添加本地React Native maven目录的条目。一定要把它添加到allprojects块中,在其他maven仓库之上。

allprojects {
    repositories {
        google()
        jcenter()
       maven {
           url "$rootDir/../node_modules/react-native/android"
       }
    }
}

这就把React Native提供的所有android公共文件暴露在我们的项目中。

如果你需要访问DevSettingsActivity,请添加到你的AndroidManifest

<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />

这只是在开发模式下从开发服务器重新加载JavaScript时使用,所以如果需要的话,你可以在发布版本中剥离这个。

代码集成

首先,在项目的根目录下创建一个空的index.js文件,把这段代码放进去。

import React from 'react';
import { AppRegistry, StyleSheet, Text, View } from 'react-native';

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center'
  },
  hello: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10
  }
});

function HelloWorld() {
  return (
    <View style={styles.container}>
      <Text style={styles.hello}>Hello, World</Text>
    </View>
  );
}

AppRegistry.registerComponent('SomeHugeApp', () => HelloWorld);

第二,配置开发错误覆盖的权限。

如果你的应用程序的目标是Android API级别23或更高,确保你在开发构建时启用了android.permission.SYSTEM_ALERT_WINDOW的权限。你可以用Settings.canDrawOverlays(this);来检查这个。这在开发构建中是必须的,因为React Native开发错误必须显示在所有其他窗口之上。由于在API级别23(Android M)中引入了新的权限系统,用户需要批准它。这可以通过在你的Activity的onCreate()方法中添加以下代码来实现。

我们需要在清单中加入android.permission.INTERNETandroid.permission.SYSTEM_ALERT_WINDOW

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>

第三,把下面的代码复制到你的MainActivity中,它将配置系统警报窗口的权限检查。

package com.somehugeapp

import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import androidx.appcompat.app.AppCompatActivity
import io.flutter.embedding.android.FlutterActivity
import kotlinx.android.synthetic.main.activity_main.*


class MainActivity : AppCompatActivity() {

    private val OVERLAY_PERMISSION_REQ_CODE = 22

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)


        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (!Settings.canDrawOverlays(this)) {
                val intent = Intent(
                    Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
                    Uri.parse("package:$packageName")
                )
                startActivityForResult(intent, OVERLAY_PERMISSION_REQ_CODE)
            }
        }


        btn_flutter.setOnClickListener {
            //start flutter activity

            startActivity(
                FlutterActivity.createDefaultIntent(this)
            )
        }

        btn_react_native.setOnClickListener {
            //start react native activity

        }

    }

    override fun onActivityResult(
        requestCode: Int,
        resultCode: Int,
        data: Intent?
    ) {
        super.onActivityResult(requestCode, resultCode, data)
        if (requestCode == OVERLAY_PERMISSION_REQ_CODE) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                if (!Settings.canDrawOverlays(this)) { // SYSTEM_ALERT_WINDOW permission not granted
                }
            }
        }
    }

}

The React Activity

在android项目中创建一个名为 "ReactActivity "的Kotlin类,并在其中添加这个。

package com.somehugeapp

import android.app.Activity
import android.os.Bundle
import android.view.KeyEvent
import com.facebook.react.ReactInstanceManager
import com.facebook.react.ReactPackage
import com.facebook.react.ReactRootView
import com.facebook.react.common.LifecycleState
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler
import com.facebook.react.shell.MainReactPackage

class ReactActivity : Activity(), DefaultHardwareBackBtnHandler {

    private var mReactRootView: ReactRootView? = null
    private var mReactInstanceManager: ReactInstanceManager? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val packages: List<ReactPackage> = arrayListOf(
            MainReactPackage()
        )

        mReactRootView = ReactRootView(this)
        mReactInstanceManager = ReactInstanceManager.builder()
            .setApplication(application)
            .setCurrentActivity(this)
            .setBundleAssetName("index.android.bundle")
            .setJSMainModulePath("index")
            .addPackages(packages)
            .setUseDeveloperSupport(BuildConfig.DEBUG)
            .setInitialLifecycleState(LifecycleState.RESUMED)
            .build()

        mReactRootView!!.startReactApplication(mReactInstanceManager, "SomeHugeApp", null)
        setContentView(mReactRootView)
    }

    override fun invokeDefaultOnBackPressed() {
        super.onBackPressed()
    }

    override fun onPause() {
        super.onPause()
        if (mReactInstanceManager != null) {
            mReactInstanceManager!!.onHostPause(this)
        }
    }

    override fun onResume() {
        super.onResume()
        if (mReactInstanceManager != null) {
            mReactInstanceManager!!.onHostResume(this, this)
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        if (mReactInstanceManager != null) {
            mReactInstanceManager!!.onHostDestroy(this)
        }
        if (mReactRootView != null) {
            mReactRootView!!.unmountReactApplication()
        }
    }

    override fun onBackPressed() {
        if (mReactInstanceManager != null) {
            mReactInstanceManager!!.onBackPressed()
        } else {
            super.onBackPressed()
        }
    }

    override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {
        if (keyCode == KeyEvent.KEYCODE_MENU && mReactInstanceManager != null) {
            mReactInstanceManager!!.showDevOptionsDialog()
            return true
        }
        return super.onKeyUp(keyCode, event)
    }

}

ReactInstanceManager将处理JS端的所有配置,ReactRootView在其中启动一个React应用程序并将其设置为主要内容视图。

和Android项目中的所有活动一样,将其添加到清单中。

<activity android:name=".ReactActivity"   android:label="@string/app_name"   android:theme="@style/Theme.AppCompat.Light.NoActionBar"> </activity>

在位于MainActivity的按钮监听器上添加startActivity方法,最终版本应该是这样的。

package com.somehugeapp

import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import androidx.appcompat.app.AppCompatActivity
import com.facebook.soloader.SoLoader
import io.flutter.embedding.android.FlutterActivity
import kotlinx.android.synthetic.main.activity_main.*


class MainActivity : AppCompatActivity() {

    private val OVERLAY_PERMISSION_REQ_CODE = 22

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        SoLoader.init(this, false)

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (!Settings.canDrawOverlays(this)) {
                val intent = Intent(
                    Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
                    Uri.parse("package:$packageName")
                )
                startActivityForResult(intent, OVERLAY_PERMISSION_REQ_CODE)
            }
        }


        btn_flutter.setOnClickListener {
            //start flutter activity

            startActivity(
                FlutterActivity.createDefaultIntent(this)
            )
        }

        btn_react_native.setOnClickListener {
            //start react native activity
            startActivity(
                Intent(this,ReactActivity::class.java)
            )
        }

    }

    override fun onActivityResult(
        requestCode: Int,
        resultCode: Int,
        data: Intent?
    ) {
        super.onActivityResult(requestCode, resultCode, data)
        if (requestCode == OVERLAY_PERMISSION_REQ_CODE) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                if (!Settings.canDrawOverlays(this)) { // SYSTEM_ALERT_WINDOW permission not granted
                }
            }
        }
    }

}

启用Hermes

Hermes是一个为在Android上快速启动RN应用程序而优化的JavaScript引擎。它的特点是提前静态优化和紧凑的字节码。

此外,我们将为每个_buildType_定义一个NDK,以便使React Native应用程序在模拟器中运行,因为flutter在调试模式下接受这种ABI。

你的应用build.gradle应该是这样的,以便启用Hermes和buildType。

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

project.ext.react = [
        enableHermes: true,  // clean and rebuild if changing
]

apply from: "../../node_modules/react-native/react.gradle"

def enableSeparateBuildPerCPUArchitecture = false
def jscFlavor = 'org.webkit:android-jsc:+'
def enableHermes = project.ext.react.get("enableHermes", false);

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.2"

    defaultConfig {
        applicationId "com.somehugeapp"
        minSdkVersion 21
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"
        
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        debug {
            ndk {
                // Filter for architectures supported by Flutter and React Native.
                abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86'
            }
        }
        release {
            ndk {
                // Filter for architectures supported by Flutter.
                abiFilters 'armeabi-v7a', 'arm64-v8a'
            }
        }
    }

    splits {
        abi {
            reset()
            enable enableSeparateBuildPerCPUArchitecture
            universalApk false  // If true, also generate a universal APK
            include "armeabi-v7a", "x86", "arm64-v8a"
        }
    }

    compileOptions {
        sourceCompatibility = 1.8
        targetCompatibility = 1.8
    }

}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.core:core-ktx:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'

    implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-alpha03'

    implementation "com.facebook.react:react-native:+"

    if (enableHermes) {
        def hermesPath = "../../node_modules/hermes-engine/android/";
        debugImplementation files(hermesPath + "hermes-debug.aar")
        releaseImplementation files(hermesPath + "hermes-release.aar")
    } else {
        implementation jscFlavor
    }

    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
    implementation project(path: ':flutter')
}

apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle")

现在在终端的android文件夹中运行。

./gradlew clean

测试我们的集成

现在,在项目根目录下的终端或命令提示符上运行这一行。

在发布模式下运行的重要注意事项:由于flutter只支持发布模式下的ARM架构,你应该确保你的模拟器支持它,考虑用你自己的设备运行该项目以测试发布版本 :D

yarn start

在android studio上构建应用程序并等待它出现。

当应用程序运行时,你会看到这个屏幕。

image.png

这是因为React有一个显示在当前应用上的调试屏幕(在发布模式下,这不会发生!)。

1.gif

IT WORKS!!!

这只是一个开始,但我们已经有了应用程序的雏形,我们已经准备好真正开始添加功能(导航和模块化)。敬请关注,我们将继续发布更新和添加演练。同样,如果你有任何建议或想法,请联系我。我们很希望得到一些反馈!

Git repo有一些导航和多个捆绑实例。 github.com/linzera/bro…


www.deepl.com 翻译