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