feat: 构建flutter 基座

This commit is contained in:
Booker
2026-05-18 13:36:54 +07:00
parent ce8ce5ec13
commit 37e4d73861
75 changed files with 3887 additions and 132 deletions

View File

@@ -0,0 +1,111 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 网络权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<!-- 存储权限 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<!-- Image Cropper 权限 -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<!-- 相机和麦克风权限 -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<!-- 通知权限 -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<!-- 系统窗口权限 -->
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<!-- 设备信息权限 -->
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<!-- 位置权限(如果需要) -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- 蓝牙权限(音视频通话可能需要) -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<!-- 音频焦点权限 -->
<uses-permission android:name="android.permission.AUDIO_FOCUS" />
<!-- 前台服务权限 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_PHONE_CALL" />
<!-- 安装应用权限(升级功能) -->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<!-- 查询其他应用权限 -->
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
<application
android:label="集中营"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
android:networkSecurityConfig="@xml/network_security_config"
android:usesCleartextTraffic="true">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- File Provider for APK install/update flows inherited from the old app. -->
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
</manifest>

View File

@@ -0,0 +1,5 @@
package io.openim.flutter.im_webview_app
import io.flutter.embedding.android.FlutterActivity
class MainActivity : FlutterActivity()

View File

@@ -0,0 +1,146 @@
package io.openim.flutter.openim
import android.content.Intent
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.util.Log
import androidx.core.content.FileProvider
import io.flutter.embedding.android.FlutterActivity
import org.conscrypt.Conscrypt
import java.security.Security
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import java.io.File
class MainActivity : FlutterActivity() {
private val CHANNEL = "io.openim.flutter.openim/apk_info"
private val TAG = "MainActivity"
override fun onCreate(savedInstanceState: android.os.Bundle?) {
// 华为/荣耀/OPPO 等国产设备:在任意网络请求之前同步安装 Conscrypt修复 SSL 握手失败(无 GMS 时系统 SSL 实现不完整)
try {
Security.insertProviderAt(Conscrypt.newProvider(), 1)
Log.d(TAG, "Conscrypt security provider installed (SSL fix for domestic devices)")
} catch (e: Exception) {
Log.w(TAG, "Conscrypt provider install failed: ${e.message}")
}
super.onCreate(savedInstanceState)
}
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
when (call.method) {
"getApkPackageName" -> {
val apkPath = call.argument<String>("apkPath")
if (apkPath != null) {
try {
val packageName = getApkPackageName(apkPath)
result.success(packageName)
} catch (e: Exception) {
result.error("ERROR", "无法解析APK包名: ${e.message}", null)
}
} else {
result.error("ERROR", "APK路径为空", null)
}
}
"getCurrentPackageName" -> {
try {
val packageName = packageName
result.success(packageName)
} catch (e: Exception) {
result.error("ERROR", "无法获取当前包名: ${e.message}", null)
}
}
"canRequestPackageInstalls" -> {
try {
val canInstall = canRequestPackageInstalls()
result.success(canInstall)
} catch (e: Exception) {
result.error("ERROR", "无法检查安装权限: ${e.message}", null)
}
}
"installApk" -> {
val apkPath = call.argument<String>("apkPath")
if (apkPath != null) {
try {
val success = installApk(apkPath)
result.success(success)
} catch (e: Exception) {
result.error("ERROR", "安装APK失败: ${e.message}", null)
}
} else {
result.error("ERROR", "APK路径为空", null)
}
}
else -> {
result.notImplemented()
}
}
}
}
private fun getApkPackageName(apkPath: String): String? {
return try {
val packageManager = packageManager
val packageInfo: PackageInfo = packageManager.getPackageArchiveInfo(
apkPath,
PackageManager.GET_ACTIVITIES
) ?: return null
packageInfo.packageName
} catch (e: Exception) {
null
}
}
/// 检查是否可以请求安装包权限Android 8.0+
private fun canRequestPackageInstalls(): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// Android 8.0+ 需要检查安装权限
packageManager.canRequestPackageInstalls()
} else {
// Android 8.0 以下版本默认允许安装
true
}
}
/// 使用 Intent 直接打开安装界面
private fun installApk(apkPath: String): Boolean {
return try {
val file = File(apkPath)
if (!file.exists()) {
return false
}
val intent = Intent(Intent.ACTION_VIEW).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
val apkUri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
// Android 7.0+ 使用 FileProvider
FileProvider.getUriForFile(
this@MainActivity,
"${packageName}.fileprovider",
file
)
} else {
// Android 7.0 以下直接使用 file://
Uri.fromFile(file)
}
setDataAndType(apkUri, "application/vnd.android.package-archive")
// Android 7.0+ 需要添加临时读取权限
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
}
startActivity(intent)
true
} catch (e: Exception) {
false
}
}
}

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">本地打包</string>
</resources>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="."/>
<cache-path name="cache" path="."/>
<external-cache-path name="external_cache" path="."/>
<files-path name="files" path="."/>
</paths>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
国内网络 / 国产手机华为、荣耀、OPPO、小米等兼容配置
- cleartextTrafficPermitted允许 HTTP与后端是否 HTTPS 无关,部分 ROM 会严格检查
- trust-anchors system + user系统证书 + 用户证书,兼容企业/校园网等需安装根证书的环境
-->
<network-security-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system" />
<!-- 兼容国内企业/校园网等需安装根证书或代理证书的环境(华为/荣耀/OPPO 等部分场景) -->
<certificates src="user" />
</trust-anchors>
</base-config>
</network-security-config>