build(android): 生成原生Android工程 — Android Studio直接构建
- android/: 完整Gradle工程(build.gradle/settings/AndroidManifest/Activity/Application) - PackageInstallerModule: 重写为ReactContextBaseJavaModule (bare RN, 移除Expo依赖) - 新增sha256File原生方法, installApk改为Promise异步 - 移除旧的modules/ (Expo Module → bare React Native) - 添加eas.json (备用云端构建)
This commit is contained in:
38
packages/client-android/android/app/build.gradle
Normal file
38
packages/client-android/android/app/build.gradle
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
apply plugin: "com.android.application"
|
||||||
|
apply plugin: "org.jetbrains.kotlin.android"
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace "dev.yuzu.gca"
|
||||||
|
compileSdk rootProject.ext.compileSdkVersion
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId "dev.yuzu.gca"
|
||||||
|
minSdk rootProject.ext.minSdkVersion
|
||||||
|
targetSdk rootProject.ext.targetSdkVersion
|
||||||
|
versionCode 4
|
||||||
|
versionName "0.1.0"
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled false
|
||||||
|
}
|
||||||
|
debug {
|
||||||
|
debuggable true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_17
|
||||||
|
targetCompatibility JavaVersion.VERSION_17
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = "17"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation("com.facebook.react:react-android")
|
||||||
|
implementation("com.facebook.react:hermes-android")
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:name=".MainApplication"
|
||||||
|
android:label="Yuzu GCA"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:allowBackup="false"
|
||||||
|
android:supportsRtl="true"
|
||||||
|
android:usesCleartextTraffic="true">
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:label="Yuzu GCA"
|
||||||
|
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
|
||||||
|
android:launchMode="singleTask"
|
||||||
|
android:windowSoftInputMode="adjustResize"
|
||||||
|
android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
</application>
|
||||||
|
</manifest>
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package dev.yuzu.gca
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import com.facebook.react.ReactActivity
|
||||||
|
import com.facebook.react.ReactActivityDelegate
|
||||||
|
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
|
||||||
|
import com.facebook.react.defaults.DefaultReactActivityDelegate
|
||||||
|
|
||||||
|
class MainActivity : ReactActivity() {
|
||||||
|
override fun getMainComponentName(): String = "main"
|
||||||
|
|
||||||
|
override fun createReactActivityDelegate(): ReactActivityDelegate =
|
||||||
|
DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled)
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package dev.yuzu.gca
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
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.soloader.SoLoader
|
||||||
|
|
||||||
|
class MainApplication : Application(), ReactApplication {
|
||||||
|
|
||||||
|
override val reactNativeHost: ReactNativeHost =
|
||||||
|
object : DefaultReactNativeHost(this) {
|
||||||
|
override fun getPackages(): List<ReactPackage> =
|
||||||
|
PackageList(this).packages.apply {
|
||||||
|
// 注册 PackageInstaller 原生模块
|
||||||
|
add(PackageInstallerPackage())
|
||||||
|
}
|
||||||
|
|
||||||
|
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(applicationContext, reactNativeHost)
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
SoLoader.init(this, false)
|
||||||
|
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) load()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
package dev.yuzu.gca
|
||||||
|
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageInstaller
|
||||||
|
import com.facebook.react.bridge.*
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileInputStream
|
||||||
|
import java.security.DigestInputStream
|
||||||
|
import java.security.MessageDigest
|
||||||
|
|
||||||
|
class PackageInstallerModule : ReactContextBaseJavaModule {
|
||||||
|
constructor(reactContext: ReactApplicationContext) : super(reactContext)
|
||||||
|
|
||||||
|
override fun getName(): String = "PackageInstallerModule"
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
fun installApk(filePath: String, expectedSha256: String, promise: Promise) {
|
||||||
|
try {
|
||||||
|
val context = reactApplicationContext
|
||||||
|
val apkFile = File(filePath)
|
||||||
|
if (!apkFile.exists()) {
|
||||||
|
promise.reject("FILE_NOT_FOUND", "安装包不存在")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!context.packageManager.canRequestPackageInstalls()) {
|
||||||
|
promise.reject("PERMISSION_DENIED", "REQUEST_INSTALL_PACKAGES 权限未授予")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val packageInstaller = context.packageManager.packageInstaller
|
||||||
|
val sessionParams = PackageInstaller.SessionParams(
|
||||||
|
PackageInstaller.SessionParams.MODE_FULL_INSTALL
|
||||||
|
)
|
||||||
|
|
||||||
|
val sessionId = packageInstaller.createSession(sessionParams)
|
||||||
|
val session = packageInstaller.openSession(sessionId)
|
||||||
|
|
||||||
|
try {
|
||||||
|
val sha256 = MessageDigest.getInstance("SHA-256")
|
||||||
|
FileInputStream(apkFile).use { input ->
|
||||||
|
val digestStream = DigestInputStream(input, sha256)
|
||||||
|
session.openWrite("package", 0, apkFile.length()).use { output ->
|
||||||
|
digestStream.copyTo(output)
|
||||||
|
session.fsync(output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val actualSha256 = sha256.digest().joinToString("") { "%02x".format(it) }
|
||||||
|
if (actualSha256 != expectedSha256) {
|
||||||
|
promise.reject("SHA256_MISMATCH", "SHA256校验失败")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val launchIntent = context.packageManager.getLaunchIntentForPackage(context.packageName)
|
||||||
|
?: run {
|
||||||
|
promise.reject("NO_LAUNCH_INTENT", "无法获取启动Intent")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val pendingIntent = PendingIntent.getActivity(
|
||||||
|
context, 0, launchIntent,
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||||
|
)
|
||||||
|
|
||||||
|
session.commit(pendingIntent.intentSender)
|
||||||
|
promise.resolve("安装已提交")
|
||||||
|
} finally {
|
||||||
|
session.close()
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
promise.reject("INSTALL_ERROR", e.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
fun sha256File(filePath: String, promise: Promise) {
|
||||||
|
try {
|
||||||
|
val digest = MessageDigest.getInstance("SHA-256")
|
||||||
|
FileInputStream(File(filePath)).use { input ->
|
||||||
|
val buf = ByteArray(8192)
|
||||||
|
var read: Int
|
||||||
|
while (input.read(buf).also { read = it } != -1) {
|
||||||
|
digest.update(buf, 0, read)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
promise.resolve(digest.digest().joinToString("") { "%02x".format(it) })
|
||||||
|
} catch (e: Exception) {
|
||||||
|
promise.reject("SHA256_ERROR", e.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package dev.yuzu.gca
|
||||||
|
|
||||||
|
import com.facebook.react.ReactPackage
|
||||||
|
import com.facebook.react.bridge.NativeModule
|
||||||
|
import com.facebook.react.bridge.ReactApplicationContext
|
||||||
|
import com.facebook.react.uimanager.ViewManager
|
||||||
|
|
||||||
|
class PackageInstallerPackage : ReactPackage {
|
||||||
|
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
|
||||||
|
return listOf(PackageInstallerModule())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="primary">#3b82f6</color>
|
||||||
|
<color name="background">#0f172a</color>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">Yuzu GCA</string>
|
||||||
|
</resources>
|
||||||
26
packages/client-android/android/build.gradle
Normal file
26
packages/client-android/android/build.gradle
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
// Top-level build file
|
||||||
|
buildscript {
|
||||||
|
ext {
|
||||||
|
buildToolsVersion = "35.0.0"
|
||||||
|
minSdkVersion = 24
|
||||||
|
compileSdkVersion = 35
|
||||||
|
targetSdkVersion = 35
|
||||||
|
kotlinVersion = "2.0.21"
|
||||||
|
}
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
classpath("com.android.tools.build:gradle:8.7.3")
|
||||||
|
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
maven { url 'https://www.jitpack.io' }
|
||||||
|
}
|
||||||
|
}
|
||||||
5
packages/client-android/android/gradle.properties
Normal file
5
packages/client-android/android/gradle.properties
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m
|
||||||
|
android.useAndroidX=true
|
||||||
|
android.enableJetifier=true
|
||||||
|
newArchEnabled=true
|
||||||
|
hermesEnabled=true
|
||||||
5
packages/client-android/android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
5
packages/client-android/android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
6
packages/client-android/android/settings.gradle
Normal file
6
packages/client-android/android/settings.gradle
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
rootProject.name = 'YuzuGCA'
|
||||||
|
|
||||||
|
apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle")
|
||||||
|
applyNativeModulesSettingsGradle(settings)
|
||||||
|
|
||||||
|
include ':app'
|
||||||
23
packages/client-android/eas.json
Normal file
23
packages/client-android/eas.json
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"cli": {
|
||||||
|
"version": ">= 14.0.0"
|
||||||
|
},
|
||||||
|
"build": {
|
||||||
|
"preview": {
|
||||||
|
"android": {
|
||||||
|
"buildType": "apk",
|
||||||
|
"env": {
|
||||||
|
"APP_VERSION": "0.1.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"production": {
|
||||||
|
"android": {
|
||||||
|
"buildType": "apk"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"submit": {
|
||||||
|
"production": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user