初始化
@@ -0,0 +1,14 @@
|
||||
gradle-wrapper.jar
|
||||
/.gradle
|
||||
/captures/
|
||||
/gradlew
|
||||
/gradlew.bat
|
||||
/local.properties
|
||||
GeneratedPluginRegistrant.java
|
||||
.cxx/
|
||||
|
||||
# Remember to never publicly share your keystore.
|
||||
# See https://flutter.dev/to/reference-keystore
|
||||
key.properties
|
||||
**/*.keystore
|
||||
**/*.jks
|
||||
@@ -0,0 +1,13 @@
|
||||
arguments=
|
||||
auto.sync=false
|
||||
build.scans.enabled=false
|
||||
connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER)
|
||||
connection.project.dir=
|
||||
eclipse.preferences.version=1
|
||||
gradle.user.home=
|
||||
java.home=
|
||||
jvm.arguments=
|
||||
offline.mode=false
|
||||
override.workspace.settings=false
|
||||
show.console.view=false
|
||||
show.executions.view=false
|
||||
@@ -0,0 +1,140 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'dev.flutter.flutter-gradle-plugin'
|
||||
}
|
||||
|
||||
apply plugin: 'top.niunaijun.blackobfuscator'
|
||||
apply plugin: 'walle'
|
||||
|
||||
def bundledReleaseKeystore = file('../keystore/nut.jks')
|
||||
def legacyBundledReleaseKeystore = file('../keystore/nut.jks.old')
|
||||
def resolvedReleaseKeystore = bundledReleaseKeystore.exists() ? bundledReleaseKeystore : legacyBundledReleaseKeystore
|
||||
|
||||
android {
|
||||
namespace "com.xvproject"
|
||||
compileSdk 36
|
||||
ndkVersion "28.2.13676358"
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_17
|
||||
targetCompatibility JavaVersion.VERSION_17
|
||||
coreLibraryDesugaringEnabled true
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_17.toString()
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main.java.srcDirs += 'src/main/kotlin'
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.noporn.newchatone_twelve_five"
|
||||
minSdk = 24
|
||||
targetSdk = 36
|
||||
versionCode = 1
|
||||
versionName = "1.12.6"
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
release {
|
||||
storeFile resolvedReleaseKeystore
|
||||
storePassword 'jiahua123456'
|
||||
storeType 'jks'
|
||||
keyAlias 'nut'
|
||||
keyPassword 'jiahua123456'
|
||||
v1SigningEnabled false
|
||||
v2SigningEnabled true
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
shrinkResources true
|
||||
signingConfig signingConfigs.release
|
||||
}
|
||||
debug {
|
||||
minifyEnabled false
|
||||
shrinkResources false
|
||||
versionNameSuffix "test"
|
||||
}
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
jniLibs {
|
||||
excludes += ['**/armeabi-v7a/**', '**/x86/**', '**/x86_64/**']
|
||||
}
|
||||
}
|
||||
|
||||
applicationVariants.configureEach { variant ->
|
||||
println "variant.name= ${variant.name}"
|
||||
variant.outputs.configureEach {
|
||||
//只处理生产版本
|
||||
if (buildType.name == 'release') {
|
||||
println "buildType= ${variant.buildType.getName()}"
|
||||
def outputFileDir = "${project.projectDir.absolutePath}/release/"
|
||||
def createTime = new Date().format("MM-dd-HH-mm", TimeZone.getTimeZone("GMT+08:00"))
|
||||
println "outputFileDir= ${outputFileDir}"
|
||||
def abi = filters.find { it.filterType.toString() == "ABI" }?.identifier
|
||||
def abiSuffix = (variant.outputs.size() > 1 && abi) ? "_${abi}" : ""
|
||||
def path = "hgdj" + '_release' + "_" + variant.versionName + abiSuffix + "_" + createTime + ".apk"
|
||||
println("path= " + path)
|
||||
outputFileName = path
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flutter {
|
||||
source '../..'
|
||||
}
|
||||
|
||||
configurations.configureEach {
|
||||
resolutionStrategy {
|
||||
force 'androidx.core:core-ktx:1.6.0'
|
||||
}
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
resolutionStrategy {
|
||||
force 'com.tencent:mmkv:1.2.14'
|
||||
}
|
||||
}
|
||||
|
||||
// 加入混淆配置
|
||||
BlackObfuscator {
|
||||
// 是否启用
|
||||
enabled true
|
||||
// 混淆深度
|
||||
depth 3
|
||||
// 需要混淆的包或者类(匹配前面一段)
|
||||
obfClass = ["lib"]
|
||||
// blackClass中的包或者类不会进行混淆(匹配前面一段)
|
||||
// blackClass = ["top.niunaijun.black"]
|
||||
}
|
||||
|
||||
dependencies {
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.5'
|
||||
// android 读取渠道号码
|
||||
implementation 'com.mcxiaoke.packer-ng:helper:2.0.1'
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation 'androidx.core:core-ktx:1.12.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-common:2.6.2'
|
||||
def camerax_version = "1.0.0-beta11"
|
||||
implementation "androidx.camera:camera-core:${camerax_version}"
|
||||
implementation "androidx.camera:camera-camera2:${camerax_version}"
|
||||
implementation "androidx.camera:camera-lifecycle:${camerax_version}"
|
||||
implementation 'com.squareup.okhttp3:okhttp:4.9.3'
|
||||
// 协程核心库
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"
|
||||
// 协程Android支持库
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
|
||||
// 协程Java8支持库
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.6.4"
|
||||
implementation 'com.qiniu:happy-dns:2.0.1'
|
||||
implementation 'com.github.Petterpx.walle:library:1.0.5'
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
运行 ./resp_genkey.sh xxxxx
|
||||
xxxxx 为alias/密码和密钥的名称;
|
||||
如果提示输入密钥,直接输入xxxxx即可
|
||||
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
if [ $# -ne 1 ]; then
|
||||
printf -- "Please input alias.\n\n"
|
||||
exit
|
||||
fi
|
||||
echo "输入参数密钥$1\n"
|
||||
keystoreName="$1.jks"
|
||||
# echo "test code 2"
|
||||
|
||||
printf "\n开始生成key:[$keystoreName]...\n\n"
|
||||
`keytool -genkey -alias $1 -keyalg RSA -validity 10000 -keystore $keystoreName`
|
||||
printf "\n[$keystoreName]正在进行pkcs12重新加密...\n\n"
|
||||
`keytool -importkeystore -srckeystore $keystoreName -destkeystore $keystoreName -deststoretype pkcs12`
|
||||
printf "\n删除老的key:[$keystoreName.old]...okay\n\n"
|
||||
`rm -rf $keystoreName.old`
|
||||
|
||||
cat /dev/null > ../../key.properties
|
||||
echo "storePassword=$1" >> ../../key.properties
|
||||
echo "keyPassword=$1" >> ../../key.properties
|
||||
echo "keyAlias=$1" >> ../../key.properties
|
||||
echo "storeFile=./key/$keystoreName" >> ../../key.properties
|
||||
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/expect
|
||||
#功能:自动填充生成androidkey功能
|
||||
#注意:expect 是用c语言写的 所以用argc和argv接收参数
|
||||
#注意:expect 只接受shell而不是bash 请./运行
|
||||
|
||||
# expect 常用命令
|
||||
# spawn 交互程序开始后面跟命令或者指定程序
|
||||
# expect 获取匹配信息匹配成功则执行expect后面的程序动作
|
||||
# send exp_send 用于发送指定的字符串信息
|
||||
# exp_continue 在expect中多次匹配就需要用到
|
||||
# send_user 用来打印输出 相当于shell中的echo
|
||||
# exit 退出expect脚本
|
||||
# eof expect执行结束 退出
|
||||
# set 定义变量
|
||||
# puts 输出变量
|
||||
# set timeout 设置超时时间
|
||||
|
||||
if {$argc < 1} {
|
||||
puts "Please input alias.\n\n"
|
||||
exit 1
|
||||
}
|
||||
|
||||
set password [lindex $argv 0]
|
||||
# send_user $password
|
||||
puts stderr $password
|
||||
# spawn [exec bash ./genkey.sh $password]
|
||||
spawn bash ./genkey.sh $password
|
||||
|
||||
# expect "输入密钥库口令:"
|
||||
# send "$password\r"
|
||||
# expect "再次输入新口令:"
|
||||
# send "$password\r"
|
||||
# expect "是什么?"
|
||||
# send "$password\r"
|
||||
# expect "是什么?"
|
||||
# send "$password\r"
|
||||
# expect "是什么?"
|
||||
# send "$password\r"
|
||||
# expect "是什么?"
|
||||
# send "$password\r"
|
||||
# expect "是什么?"
|
||||
# send "$password\r"
|
||||
# expect "是什么?"
|
||||
# send "$password\r"
|
||||
# expect "是否正确?"
|
||||
# send "y\r"
|
||||
# expect "按回车):"
|
||||
# send "\r"
|
||||
# expect "输入源密钥库口令:"
|
||||
# send "$password\r"
|
||||
# # expect "输入目标密钥库口令:"
|
||||
# # send "$password\r"
|
||||
# # expect "再次输入新口令:"
|
||||
# # send "$password\r"
|
||||
|
||||
expect {
|
||||
"输入密钥库口令:"
|
||||
{ send "$password\r"; exp_continue }
|
||||
"再次输入新口令:"
|
||||
{send "$password\r"; exp_continue}
|
||||
"是什么?"
|
||||
{send "$password\r"; exp_continue}
|
||||
"是否正确?"
|
||||
{send "y\r"; exp_continue}
|
||||
"按回车):"
|
||||
{send "\r"; exp_continue}
|
||||
"输入源密钥库口令:"
|
||||
{send "$password\r";exp_continue}
|
||||
"输入目标密钥库口令:"
|
||||
{send "$password\r";exp_continue}
|
||||
"再次输入新口令:"
|
||||
{send "$password\r";exp_continue}
|
||||
}
|
||||
# interact
|
||||
@@ -0,0 +1,21 @@
|
||||
-dontwarn javax.imageio.spi.ImageInputStreamSpi
|
||||
-dontwarn javax.imageio.spi.ImageOutputStreamSpi
|
||||
-dontwarn javax.imageio.spi.ImageReaderSpi
|
||||
-dontwarn javax.imageio.spi.ImageWriterSpi
|
||||
-dontwarn org.bouncycastle.jsse.BCSSLParameters
|
||||
-dontwarn org.bouncycastle.jsse.BCSSLSocket
|
||||
-dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider
|
||||
-dontwarn org.conscrypt.Conscrypt$Version
|
||||
-dontwarn org.conscrypt.Conscrypt
|
||||
-dontwarn org.conscrypt.ConscryptHostnameVerifier
|
||||
-dontwarn org.openjsse.javax.net.ssl.SSLParameters
|
||||
-dontwarn org.openjsse.javax.net.ssl.SSLSocket
|
||||
-dontwarn org.openjsse.net.ssl.OpenJSSE
|
||||
|
||||
# ffmpeg_kit_flutter_new_https_gpl:原生库 JNI_OnLoad 通过 RegisterNatives 绑定 AbiDetect.getNativeCpuAbi 等方法,
|
||||
# release 下 R8 混淆改名会导致绑定失败 → JNI_OnLoad 返回 0 → 启动 SIGSEGV 闪退,必须 keep。
|
||||
-keep class com.antonkarpenko.ffmpegkit.** { *; }
|
||||
# 通用兜底:任何含 native 方法的类,保留其 native 方法名(防止其它 JNI 库重蹈覆辙)
|
||||
-keepclasseswithmembernames,includedescriptorclasses class * {
|
||||
native <methods>;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- The INTERNET permission is required for development. Specifically,
|
||||
the Flutter tool needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
||||
@@ -0,0 +1,318 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="com.xvproject">
|
||||
|
||||
<!-- release 包只用 main manifest,INTERNET 必须在这里声明;
|
||||
否则 release 无网络权限,启动选线 host lookup errno=7,弹"线路检测失败" -->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
|
||||
<application
|
||||
android:label="黄果短剧"
|
||||
android:name=".JPApplication"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:usesCleartextTraffic="true"
|
||||
android:largeHeap="true"
|
||||
android:hardwareAccelerated="true"
|
||||
android:extractNativeLibs="true"
|
||||
android:requestLegacyExternalStorage="true">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop"
|
||||
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>
|
||||
<activity-alias
|
||||
android:name=".lld"
|
||||
android:targetActivity=".MainActivity"
|
||||
android:label="黄果短剧"
|
||||
android:enabled="false"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity-alias>
|
||||
|
||||
<activity-alias
|
||||
android:name=".aiyinyue"
|
||||
android:targetActivity=".MainActivity"
|
||||
android:label="爱音乐"
|
||||
android:enabled="false"
|
||||
android:icon="@mipmap/icon_1"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity-alias>
|
||||
|
||||
|
||||
<activity-alias
|
||||
android:name=".aiyuedu"
|
||||
android:targetActivity=".MainActivity"
|
||||
android:label="爱阅读"
|
||||
android:enabled="false"
|
||||
android:icon="@mipmap/icon_2"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity-alias>
|
||||
|
||||
<activity-alias
|
||||
android:name=".alipay"
|
||||
android:targetActivity=".MainActivity"
|
||||
android:label="支付宝"
|
||||
android:enabled="false"
|
||||
android:icon="@mipmap/icon_3"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity-alias>
|
||||
|
||||
<activity-alias
|
||||
android:name=".bilibili"
|
||||
android:targetActivity=".MainActivity"
|
||||
android:label="哔哩哔哩"
|
||||
android:enabled="false"
|
||||
android:icon="@mipmap/icon_4"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity-alias>
|
||||
|
||||
<activity-alias
|
||||
android:name=".binggandazhuozhan"
|
||||
android:targetActivity=".MainActivity"
|
||||
android:label="金铲铲之战"
|
||||
android:enabled="false"
|
||||
android:icon="@mipmap/icon_5"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity-alias>
|
||||
|
||||
<activity-alias
|
||||
android:name=".meisijiejian"
|
||||
android:targetActivity=".MainActivity"
|
||||
android:label="美食借鉴"
|
||||
android:enabled="false"
|
||||
android:icon="@mipmap/icon_6"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity-alias>
|
||||
|
||||
<activity-alias
|
||||
android:name=".myworld"
|
||||
android:targetActivity=".MainActivity"
|
||||
android:label="我的世界"
|
||||
android:enabled="false"
|
||||
android:icon="@mipmap/icon_7"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity-alias>
|
||||
|
||||
|
||||
<activity-alias
|
||||
android:name=".shenxuanzhizhan"
|
||||
android:targetActivity=".MainActivity"
|
||||
android:label="神选之战"
|
||||
android:enabled="false"
|
||||
android:icon="@mipmap/icon_8"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity-alias>
|
||||
|
||||
|
||||
<activity-alias
|
||||
android:name=".shuixinwuxian"
|
||||
android:targetActivity=".MainActivity"
|
||||
android:label="随心无线"
|
||||
android:enabled="false"
|
||||
android:icon="@mipmap/icon_9"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity-alias>
|
||||
|
||||
|
||||
<activity-alias
|
||||
android:name=".texuanxiaoshuo"
|
||||
android:targetActivity=".MainActivity"
|
||||
android:label="特轩小说"
|
||||
android:enabled="false"
|
||||
android:icon="@mipmap/icon_10"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity-alias>
|
||||
|
||||
<activity-alias
|
||||
android:name=".tiantianmajiang"
|
||||
android:targetActivity=".MainActivity"
|
||||
android:label="天天麻将"
|
||||
android:enabled="false"
|
||||
android:icon="@mipmap/icon_11"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity-alias>
|
||||
|
||||
<activity-alias
|
||||
android:name=".tuyou"
|
||||
android:targetActivity=".MainActivity"
|
||||
android:label="途游"
|
||||
android:enabled="false"
|
||||
android:icon="@mipmap/icon_12"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity-alias>
|
||||
|
||||
<activity-alias
|
||||
android:name=".weichat"
|
||||
android:targetActivity=".MainActivity"
|
||||
android:label="微信"
|
||||
android:enabled="false"
|
||||
android:icon="@mipmap/icon_13"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity-alias>
|
||||
|
||||
<activity-alias
|
||||
android:name=".wendao"
|
||||
android:targetActivity=".MainActivity"
|
||||
android:label="问道"
|
||||
android:enabled="false"
|
||||
android:icon="@mipmap/icon_14"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity-alias>
|
||||
|
||||
<activity-alias
|
||||
android:name=".yinyutusujiao"
|
||||
android:targetActivity=".MainActivity"
|
||||
android:label="英语图书角"
|
||||
android:enabled="false"
|
||||
android:icon="@mipmap/icon_15"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity-alias>
|
||||
|
||||
<activity-alias
|
||||
android:name=".icon_16"
|
||||
android:targetActivity=".MainActivity"
|
||||
android:label="91PO黄果短剧RN"
|
||||
android:enabled="false"
|
||||
android:icon="@mipmap/icon_16"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity-alias>
|
||||
|
||||
<activity-alias
|
||||
android:name=".icon_17"
|
||||
android:targetActivity=".MainActivity"
|
||||
android:label="黄果短剧"
|
||||
android:enabled="false"
|
||||
android:icon="@mipmap/icon_17"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity-alias>
|
||||
|
||||
<activity-alias
|
||||
android:name=".icon_18"
|
||||
android:targetActivity=".MainActivity"
|
||||
android:label="黄果短剧"
|
||||
android:enabled="false"
|
||||
android:icon="@mipmap/icon_18"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity-alias>
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.fileprovider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true"
|
||||
tools:replace="android:authorities">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/totalfilepath"
|
||||
tools:replace="android:resource" />
|
||||
</provider>
|
||||
|
||||
<provider
|
||||
android:name="com.pichillilorenzo.flutter_inappwebview_android.InAppWebViewFileProvider"
|
||||
android:authorities="${applicationId}.flutter_inappwebview_android.fileprovider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/provider_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>
|
||||
</manifest>
|
||||
@@ -0,0 +1,104 @@
|
||||
package io.flutter.plugins;
|
||||
|
||||
import android.media.MediaMetadataRetriever;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
|
||||
/// 本地视频文件信息:时长 / 分辨率 / 比例 / 高度 / 比特率
|
||||
/// 注:MediaMetadataRetriever 持有 native 资源,每次用完必须 release,否则多次调用后取不到任何信息
|
||||
public class JPFileUtils {
|
||||
|
||||
/**
|
||||
* 获取视频时长(秒)
|
||||
*/
|
||||
public static int getVideoDuration(String filePath) {
|
||||
String ms = extract(filePath, MediaMetadataRetriever.METADATA_KEY_DURATION);
|
||||
if (TextUtils.isEmpty(ms) || !TextUtils.isDigitsOnly(ms)) return 0;
|
||||
return Integer.parseInt(ms) / 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取视频分辨率,格式 "宽*高",取不到返回空串
|
||||
*/
|
||||
public static String getVideoResolution(String filePath) {
|
||||
int[] size = videoSize(filePath);
|
||||
return size[0] > 0 && size[1] > 0 ? size[0] + "*" + size[1] : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取视频宽高比
|
||||
*/
|
||||
public static String getVideoRatio(String filePath) {
|
||||
int[] size = videoSize(filePath);
|
||||
if (size[0] <= 0 || size[1] <= 0) return "0";
|
||||
return String.valueOf((float) size[0] / size[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取视频高度
|
||||
*/
|
||||
public static String getVideoHeight(String filePath) {
|
||||
int[] size = videoSize(filePath);
|
||||
if (size[0] <= 0 || size[1] <= 0) return "0";
|
||||
return String.valueOf(size[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取比特率
|
||||
*/
|
||||
public static String getVideoBitrate(String filePath) {
|
||||
String bitrate = extract(filePath, MediaMetadataRetriever.METADATA_KEY_BITRATE);
|
||||
return TextUtils.isEmpty(bitrate) ? "0" : bitrate;
|
||||
}
|
||||
|
||||
/**
|
||||
* 取单个 metadata;用 FD 而非路径设源,避开部分机型对中文/特殊字符路径的兼容问题
|
||||
*/
|
||||
private static String extract(String filePath, int key) {
|
||||
if (TextUtils.isEmpty(filePath)) return null;
|
||||
MediaMetadataRetriever mmr = new MediaMetadataRetriever();
|
||||
try (FileInputStream in = new FileInputStream(filePath)) {
|
||||
mmr.setDataSource(in.getFD());
|
||||
return mmr.extractMetadata(key);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
} finally {
|
||||
release(mmr);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 视频宽高,取不到返回 {0,0}。宽高一次打开取完,避免为了宽和高开两次文件
|
||||
*/
|
||||
private static int[] videoSize(String filePath) {
|
||||
int[] empty = {0, 0};
|
||||
if (TextUtils.isEmpty(filePath)) return empty;
|
||||
MediaMetadataRetriever mmr = new MediaMetadataRetriever();
|
||||
try (FileInputStream in = new FileInputStream(filePath)) {
|
||||
mmr.setDataSource(in.getFD());
|
||||
int width = parseInt(mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH));
|
||||
int height = parseInt(mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT));
|
||||
return new int[]{width, height};
|
||||
} catch (Exception e) {
|
||||
return empty;
|
||||
} finally {
|
||||
release(mmr);
|
||||
}
|
||||
}
|
||||
|
||||
private static int parseInt(String value) {
|
||||
try {
|
||||
return Integer.parseInt(value);
|
||||
} catch (Exception e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static void release(MediaMetadataRetriever mmr) {
|
||||
try {
|
||||
mmr.release();
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package io.flutter.plugins;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.media.MediaCodecInfo;
|
||||
import android.media.MediaCodecList;
|
||||
import android.media.MediaFormat;
|
||||
import android.os.Build;
|
||||
|
||||
import com.meituan.android.walle.WalleChannelReader;
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity;
|
||||
import io.flutter.embedding.engine.FlutterEngine;
|
||||
import io.flutter.plugin.common.MethodCall;
|
||||
import io.flutter.plugin.common.MethodChannel;
|
||||
|
||||
/// 本地插件:设备号 / 视频文件信息 / 渠道号 / H265 硬解能力
|
||||
public class JPNativePlugin implements MethodChannel.MethodCallHandler {
|
||||
|
||||
private static final String CHANNEL = "com.yinse/device";
|
||||
|
||||
private final Activity activity;
|
||||
|
||||
private JPNativePlugin(Activity activity) {
|
||||
this.activity = activity;
|
||||
}
|
||||
|
||||
/// 注册
|
||||
public static void registerWith(FlutterActivity flutterActivity, FlutterEngine engine) {
|
||||
MethodChannel channel = new MethodChannel(engine.getDartExecutor().getBinaryMessenger(), CHANNEL);
|
||||
channel.setMethodCallHandler(new JPNativePlugin(flutterActivity));
|
||||
}
|
||||
|
||||
/// 调用
|
||||
@Override
|
||||
public void onMethodCall(MethodCall call, MethodChannel.Result result) {
|
||||
switch (call.method) {
|
||||
case "getVideoDuration":
|
||||
result.success(JPFileUtils.getVideoDuration(call.argument("filePath")));
|
||||
break;
|
||||
case "getVideoResolution":
|
||||
result.success(JPFileUtils.getVideoResolution(call.argument("filePath")));
|
||||
break;
|
||||
case "getVideoRatio":
|
||||
result.success(JPFileUtils.getVideoRatio(call.argument("filePath")));
|
||||
break;
|
||||
case "getVideoHeight":
|
||||
result.success(JPFileUtils.getVideoHeight(call.argument("filePath")));
|
||||
break;
|
||||
case "getVideoBitrate":
|
||||
result.success(JPFileUtils.getVideoBitrate(call.argument("filePath")));
|
||||
break;
|
||||
case "getChannel": // 获取android 渠道的东西
|
||||
result.success(getChannel(activity));
|
||||
break;
|
||||
case "isH265HardwareSupported":
|
||||
result.success(isHevcHardwareSupported());
|
||||
break;
|
||||
default:
|
||||
// 不回 result 的话 Dart 那边的 Future 永远不完成
|
||||
result.notImplemented();
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取渠道号
|
||||
public static String getChannel(Context context) {
|
||||
return WalleChannelReader.getChannel(context);
|
||||
}
|
||||
|
||||
/// 本机是否**硬解** H.265(HEVC)。只认硬解:软解(google/c2.android)当不支持,回落 264。
|
||||
/// 查询编解码器能力,任何异常都按不支持处理。
|
||||
private static boolean isHevcHardwareSupported() {
|
||||
try {
|
||||
MediaCodecList list = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
|
||||
for (MediaCodecInfo info : list.getCodecInfos()) {
|
||||
if (info.isEncoder() || !supportsHevc(info)) continue;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
if (info.isHardwareAccelerated()) return true; // API29+ 直接问硬解
|
||||
} else {
|
||||
String name = info.getName().toLowerCase(); // 旧版本靠名字排除软解
|
||||
if (!name.startsWith("omx.google.") && !name.startsWith("c2.android.")) return true;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean supportsHevc(MediaCodecInfo info) {
|
||||
for (String type : info.getSupportedTypes()) {
|
||||
if (type.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_HEVC)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.xvproject
|
||||
|
||||
import io.flutter.app.FlutterApplication
|
||||
|
||||
class JPApplication : FlutterApplication()
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.xvproject
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.ActivityManager
|
||||
import android.content.ComponentName
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import io.flutter.plugins.JPNativePlugin
|
||||
|
||||
class MainActivity : FlutterActivity() {
|
||||
|
||||
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||
super.configureFlutterEngine(flutterEngine)
|
||||
JPNativePlugin.registerWith(this, flutterEngine)
|
||||
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
|
||||
.setMethodCallHandler { call, result ->
|
||||
when (call.method) {
|
||||
METHOD_CHANGE_ICON -> {
|
||||
val index = call.argument<Int>(ARG_ICON_INDEX) ?: -1
|
||||
ICON_ALIASES.getOrNull(index)?.let { switchIcon("$ALIAS_PACKAGE.$it") }
|
||||
result.success(true)
|
||||
}
|
||||
// 不回 result 的话 Dart 那边的 Future 永远不完成
|
||||
else -> result.notImplemented()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 切换启动图标:禁用当前入口 + 启用目标 alias,再杀掉桌面进程让图标立刻刷新
|
||||
private fun switchIcon(alias: String) {
|
||||
val pm = packageManager
|
||||
pm.setComponentEnabledSetting(
|
||||
componentName,
|
||||
PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP
|
||||
)
|
||||
pm.setComponentEnabledSetting(
|
||||
ComponentName(this, alias),
|
||||
PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP
|
||||
)
|
||||
restartLauncher(pm)
|
||||
}
|
||||
|
||||
/// 杀掉桌面进程,强制它重读图标
|
||||
@SuppressLint("ServiceCast")
|
||||
private fun restartLauncher(pm: PackageManager) {
|
||||
val am = getSystemService(ACTIVITY_SERVICE) as ActivityManager
|
||||
val intent = Intent(Intent.ACTION_MAIN)
|
||||
.addCategory(Intent.CATEGORY_HOME)
|
||||
.addCategory(Intent.CATEGORY_DEFAULT)
|
||||
for (info in pm.queryIntentActivities(intent, 0)) {
|
||||
am.killBackgroundProcesses(info.activityInfo?.packageName ?: continue)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
//这个跟flutter的通道名称保持一致
|
||||
private const val CHANNEL = "com.yinse/request"
|
||||
|
||||
private const val METHOD_CHANGE_ICON = "changeIcon"
|
||||
private const val ARG_ICON_INDEX = "iconIndex"
|
||||
|
||||
/// 图标 alias 所在包名:AndroidManifest 里 activity-alias 写的是相对名(manifest package=com.xvproject),
|
||||
/// 与 applicationId 不同,不能用 packageName 拼
|
||||
private const val ALIAS_PACKAGE = "com.xvproject"
|
||||
|
||||
/// 下标即 Flutter 传来的 iconIndex,顺序必须与 manifest 里 activity-alias 的声明顺序一致
|
||||
private val ICON_ALIASES = listOf(
|
||||
"lld", "aiyinyue", "aiyuedu", "alipay", "bilibili", "binggandazhuozhan",
|
||||
"meisijiejian", "myworld", "shenxuanzhizhan", "shuixinwuxian", "texuanxiaoshuo",
|
||||
"tiantianmajiang", "tuyou", "weichat", "wendao", "yinyutusujiao",
|
||||
"icon_16", "icon_17", "icon_18", //清明 / 端午 / 劳动
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -0,0 +1,74 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector
|
||||
android:height="108dp"
|
||||
android:width="108dp"
|
||||
android:viewportHeight="108"
|
||||
android:viewportWidth="108"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#3DDC84"
|
||||
android:pathData="M0,0h108v108h-108z"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
</vector>
|
||||
@@ -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>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?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"
|
||||
android:opacity="opaque">
|
||||
<item android:gravity="center">
|
||||
<bitmap
|
||||
android:gravity="center_horizontal"
|
||||
android:mipMap="true"
|
||||
android:src="@mipmap/ic_splash_bg" />
|
||||
</item>
|
||||
|
||||
</layer-list>
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 208 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 8.3 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 7.4 KiB |
|
After Width: | Height: | Size: 7.1 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
@@ -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>
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:windowBackground">@drawable/launch_bg</item>
|
||||
<item name="android:colorPrimary">#12131C</item>
|
||||
<item name="colorPrimaryDark">#12131C</item>
|
||||
<item name="colorAccent">#12131C</item>
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
<item name="windowActionBar">false</item>
|
||||
</style>
|
||||
<!-- Flutter UI 初始化时及运行时的窗口背景;缺失会导致浅色模式下窗口透明 -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
<paths>
|
||||
<external-path
|
||||
name="external-path"
|
||||
path="." />
|
||||
<external-cache-path
|
||||
name="external-cache-path"
|
||||
path="." />
|
||||
<external-files-path
|
||||
name="external-files-path"
|
||||
path="." />
|
||||
<files-path
|
||||
name="files_path"
|
||||
path="." />
|
||||
<cache-path
|
||||
name="cache-path"
|
||||
path="." />
|
||||
<root-path
|
||||
name="name"
|
||||
path="." />
|
||||
<external-path
|
||||
name="external_storage_root"
|
||||
path="." />
|
||||
</paths>
|
||||
@@ -0,0 +1,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- The INTERNET permission is required for development. Specifically,
|
||||
the Flutter tool needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
||||
@@ -0,0 +1,128 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
maven { url 'https://jitpack.io' } // 混淆
|
||||
maven { url 'https://maven.aliyun.com/nexus/content/groups/public/' }
|
||||
maven { url 'https://maven.aliyun.com/nexus/content/repositories/jcenter' }
|
||||
maven { url 'https://maven.aliyun.com/nexus/content/repositories/google' }
|
||||
maven { url 'https://maven.aliyun.com/nexus/content/repositories/gradle-plugin' }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath "com.github.CodingGay:BlackObfuscator-ASPlugin:3.9"
|
||||
classpath 'com.github.Petterpx.walle:plugin:1.0.5'
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
maven { url 'https://maven.aliyun.com/nexus/content/groups/public/' }
|
||||
maven { url 'https://maven.aliyun.com/nexus/content/repositories/jcenter' }
|
||||
maven { url 'https://maven.aliyun.com/nexus/content/repositories/google' }
|
||||
maven { url 'https://maven.aliyun.com/nexus/content/repositories/gradle-plugin' }
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.buildDir = '../build'
|
||||
subprojects {
|
||||
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
||||
}
|
||||
|
||||
def ensureAndroidNamespace = { project ->
|
||||
def androidExtension = project.extensions.findByName("android")
|
||||
if (androidExtension == null || !androidExtension.hasProperty("namespace") || androidExtension.namespace != null) {
|
||||
return
|
||||
}
|
||||
|
||||
def manifestFile = project.file("src/main/AndroidManifest.xml")
|
||||
if (manifestFile.exists()) {
|
||||
def manifestPackage = new XmlSlurper(false, false).parse(manifestFile).@package?.text()
|
||||
if (manifestPackage) {
|
||||
androidExtension.namespace = manifestPackage
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (project.group) {
|
||||
androidExtension.namespace = project.group.toString()
|
||||
}
|
||||
}
|
||||
|
||||
def setLibraryCompileSdk = { project ->
|
||||
def androidExtension = project.extensions.findByName("android")
|
||||
if (androidExtension == null) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
if (androidExtension.hasProperty("compileSdk")) {
|
||||
androidExtension.compileSdk = 36
|
||||
} else if (androidExtension.hasProperty("compileSdkVersion")) {
|
||||
androidExtension.compileSdkVersion 36
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
// AGP 已锁定 compileSdk,跳过;该模块用自己声明的值即可
|
||||
}
|
||||
}
|
||||
|
||||
def alignAndroidJvmTargets = { project ->
|
||||
def androidExtension = project.extensions.findByName("android")
|
||||
if (androidExtension == null) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
if (androidExtension.hasProperty("compileOptions")) {
|
||||
androidExtension.compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_17
|
||||
targetCompatibility JavaVersion.VERSION_17
|
||||
}
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
|
||||
try {
|
||||
if (androidExtension.hasProperty("kotlinOptions")) {
|
||||
androidExtension.kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_17.toString()
|
||||
}
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
|
||||
def finalizeAndroidAlignment = { project ->
|
||||
if (project.plugins.hasPlugin("com.android.application") || project.plugins.hasPlugin("com.android.library")) {
|
||||
ensureAndroidNamespace(project)
|
||||
alignAndroidJvmTargets(project)
|
||||
}
|
||||
// afterEvaluate 阶段再强制一次 library 的 compileSdk,覆盖个别老插件
|
||||
// (如 flutter_custom_dialog 在自身 build.gradle 写死 compileSdkVersion 28)
|
||||
// 在 plugin apply 之后声明的低版本,否则配 Java 17 编译会报需 compileSdk≥30
|
||||
if (project.plugins.hasPlugin("com.android.library")) {
|
||||
setLibraryCompileSdk(project)
|
||||
}
|
||||
}
|
||||
|
||||
subprojects {
|
||||
plugins.withId("com.android.application") {
|
||||
ensureAndroidNamespace(project)
|
||||
// app 自己 build.gradle 已声明 compileSdk 36,这里不再重设(AGP 8.x 在 plugin apply 后会立即锁定 compileSdk)
|
||||
}
|
||||
plugins.withId("com.android.library") {
|
||||
ensureAndroidNamespace(project)
|
||||
setLibraryCompileSdk(project)
|
||||
}
|
||||
if (project.state.executed) {
|
||||
finalizeAndroidAlignment(project)
|
||||
} else {
|
||||
afterEvaluate {
|
||||
finalizeAndroidAlignment(project)
|
||||
}
|
||||
}
|
||||
project.evaluationDependsOn(':app')
|
||||
}
|
||||
|
||||
tasks.register("clean", Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
# build apk
|
||||
# author: @sudong
|
||||
# 使用方法:./build_release.sh 文件服务 http://in.test.tsmgsoce.com/sudong1/
|
||||
./gradlew clean
|
||||
rm -rf ./app/release/
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "gradle clean success"
|
||||
./gradlew assembleRelease
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "gradle build success"
|
||||
filename=`ls -t ./app/release|head -n1|awk '{print $0}'`
|
||||
echo "filename is $filename"
|
||||
cd ./app/release
|
||||
# pwd
|
||||
# scp -P 22889 $filename root@35.220.142.29:/data/test-apk/sudong1
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
#!/bin/bash
|
||||
# 生成密钥脚本
|
||||
# author: @sudong
|
||||
# 使用方法:./generatekey.sh alias
|
||||
|
||||
if [ $# -ne 1 ]; then
|
||||
printf -- "Please input alias.\n\n"
|
||||
exit
|
||||
fi
|
||||
echo "输入参数密钥$1\n"
|
||||
keystoreName="$1.jks"
|
||||
echo "$keystoreName"
|
||||
echo "$1"
|
||||
if [ ! -d "./keystore" ]; then
|
||||
mkdir ./keystore
|
||||
fi
|
||||
keystoreName=`uuidgen |cut -c 1-6`
|
||||
keytool -genkeypair -alias $1 -keyalg "RSA" -keysize 2048 -keypass jiahua123456 -validity 3650 -keystore ./keystore/$1.jks -storepass jiahua123456 -dname "CN='中国', OU='广东', O='广东', L='广东', ST='广东', C='中国'"
|
||||
echo jiahua123456 | keytool -importkeystore -srckeystore ./keystore/$1.jks -destkeystore ./keystore/$1.jks -deststoretype pkcs12
|
||||
|
||||
if [ ! -f "./key.properties" ]; then
|
||||
touch ./key.properties
|
||||
fi
|
||||
# shellcheck disable=SC2129
|
||||
echo "storePassword=jiahua123456" >> ./key.properties
|
||||
echo "keyPassword=jiahua123456" >> ./key.properties
|
||||
echo "keyAlias=$1" >> ./key.properties
|
||||
echo "storeFile=./keystore/$1.jks" >> ./key.properties
|
||||
@@ -0,0 +1,4 @@
|
||||
org.gradle.jvmargs=-Xmx4g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
android.enableR8=true
|
||||
@@ -0,0 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip
|
||||
@@ -0,0 +1,25 @@
|
||||
pluginManagement {
|
||||
def flutterSdkPath = {
|
||||
def properties = new Properties()
|
||||
file("local.properties").withInputStream { properties.load(it) }
|
||||
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
||||
return flutterSdkPath
|
||||
}()
|
||||
|
||||
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
|
||||
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||
id "com.android.application" version "8.9.1" apply false
|
||||
id "org.jetbrains.kotlin.android" version "2.1.0" apply false
|
||||
}
|
||||
|
||||
include ":app"
|
||||