From c0ab2323b89eee881b912c4dfa95d82355a9e88d Mon Sep 17 00:00:00 2001 From: fengyuecanzhu <1021300691@qq.com> Date: Tue, 29 Mar 2022 15:39:50 +0800 Subject: [PATCH] add plugin --- .idea/compiler.xml | 2 +- .idea/gradle.xml | 2 + .idea/misc.xml | 2 +- .idea/modules.xml | 1 + app/build.gradle | 3 + app/libs/dynamic-release.aar | Bin 0 -> 3249 bytes app/src/main/AndroidManifest.xml | 1 + app/src/main/assets/updatelog.fy | 4 + .../xyz/fycz/myreader/application/App.java | 7 +- .../xyz/fycz/myreader/common/APPCONST.java | 4 +- .../xyz/fycz/myreader/common/URLCONST.java | 2 + .../xyz/fycz/myreader/entity/PluginConfig.kt | 13 +++ .../fycz/myreader/util/utils/PluginUtils.kt | 92 ++++++++++++++++++ build.gradle | 2 +- dynamic/.gitignore | 1 + dynamic/build.gradle | 38 ++++++++ dynamic/consumer-rules.pro | 0 dynamic/proguard-rules.pro | 21 ++++ .../fycz/dynamic/ExampleInstrumentedTest.kt | 24 +++++ dynamic/src/main/AndroidManifest.xml | 5 + .../main/java/xyz/fycz/dynamic/AppLoadImpl.kt | 38 ++++++++ .../main/java/xyz/fycz/dynamic/AppParam.java | 18 ++++ .../java/xyz/fycz/dynamic/IAppLoader.java | 9 ++ .../java/xyz/fycz/dynamic/ExampleUnitTest.kt | 17 ++++ settings.gradle | 1 + 25 files changed, 302 insertions(+), 5 deletions(-) create mode 100644 app/libs/dynamic-release.aar create mode 100644 app/src/main/java/xyz/fycz/myreader/entity/PluginConfig.kt create mode 100644 app/src/main/java/xyz/fycz/myreader/util/utils/PluginUtils.kt create mode 100644 dynamic/.gitignore create mode 100644 dynamic/build.gradle create mode 100644 dynamic/consumer-rules.pro create mode 100644 dynamic/proguard-rules.pro create mode 100644 dynamic/src/androidTest/java/xyz/fycz/dynamic/ExampleInstrumentedTest.kt create mode 100644 dynamic/src/main/AndroidManifest.xml create mode 100644 dynamic/src/main/java/xyz/fycz/dynamic/AppLoadImpl.kt create mode 100644 dynamic/src/main/java/xyz/fycz/dynamic/AppParam.java create mode 100644 dynamic/src/main/java/xyz/fycz/dynamic/IAppLoader.java create mode 100644 dynamic/src/test/java/xyz/fycz/dynamic/ExampleUnitTest.kt diff --git a/.idea/compiler.xml b/.idea/compiler.xml index 61a9130..fb7f4a8 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml index eb20a8e..976a899 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -7,11 +7,13 @@ - + diff --git a/.idea/modules.xml b/.idea/modules.xml index ba83cda..35e7452 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -5,6 +5,7 @@ + \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 50ce4ec..dadc77d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -274,6 +274,9 @@ dependencies { exclude module: 'support-v4' exclude group: 'com.android.support' } + + //maple + implementation("me.fycz.maple:maple:1.6") } greendao { diff --git a/app/libs/dynamic-release.aar b/app/libs/dynamic-release.aar new file mode 100644 index 0000000000000000000000000000000000000000..59aa4b8508cfed7f358b0eae82d34a31a1625e2e GIT binary patch literal 3249 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 + versionCode; diff --git a/app/src/main/java/xyz/fycz/myreader/common/APPCONST.java b/app/src/main/java/xyz/fycz/myreader/common/APPCONST.java index 58cd4e6..1fbd492 100644 --- a/app/src/main/java/xyz/fycz/myreader/common/APPCONST.java +++ b/app/src/main/java/xyz/fycz/myreader/common/APPCONST.java @@ -44,6 +44,8 @@ public class APPCONST { + "book_cache" + File.separator; public static String HTML_CACHE_PATH = FileUtils.getCachePath() + File.separator + "html_cache" + File.separator; + public static String PLUGIN_DIR_PATH = App.getmContext().getFilesDir().getParent() + + File.separator + "plugin" + File.separator; public static long exitTime; public static final int exitConfirmTime = 2000; @@ -143,7 +145,7 @@ public class APPCONST { public static final String androidId = getAndroidId(); - public static String getAndroidId(){ + public static String getAndroidId() { return Settings.System.getString(App.getmContext().getContentResolver(), Settings.Secure.ANDROID_ID); } } diff --git a/app/src/main/java/xyz/fycz/myreader/common/URLCONST.java b/app/src/main/java/xyz/fycz/myreader/common/URLCONST.java index 5a66b96..4870894 100644 --- a/app/src/main/java/xyz/fycz/myreader/common/URLCONST.java +++ b/app/src/main/java/xyz/fycz/myreader/common/URLCONST.java @@ -38,6 +38,8 @@ public class URLCONST { public static final String QUOTATION = "https://v1.hitokoto.cn/?encode=json&charset=utf-8"; + public static final String DEFAULT_PLUGIN_CONFIG_URL = "https://fyreader.coding.net/p/img/d/Plugin/git/raw/master/release/config_FYReader.json"; + public static String getDefaultDomain() { return SharedPreUtils.getInstance().getString("domain", "fycz.me"); } diff --git a/app/src/main/java/xyz/fycz/myreader/entity/PluginConfig.kt b/app/src/main/java/xyz/fycz/myreader/entity/PluginConfig.kt new file mode 100644 index 0000000..a6b07c6 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/entity/PluginConfig.kt @@ -0,0 +1,13 @@ +package xyz.fycz.myreader.entity + +/** + * @author fengyue + * @date 2022/3/29 14:55 + */ +data class PluginConfig( + val name: String, + val versionCode: Int, + val version: String, + val url: String, + val changelog: String +) diff --git a/app/src/main/java/xyz/fycz/myreader/util/utils/PluginUtils.kt b/app/src/main/java/xyz/fycz/myreader/util/utils/PluginUtils.kt new file mode 100644 index 0000000..3dcb729 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/utils/PluginUtils.kt @@ -0,0 +1,92 @@ +package xyz.fycz.myreader.util.utils + +import android.content.Context +import android.util.Log +import dalvik.system.DexClassLoader +import xyz.fycz.dynamic.AppParam +import xyz.fycz.dynamic.IAppLoader +import xyz.fycz.myreader.application.App +import xyz.fycz.myreader.common.APPCONST +import xyz.fycz.myreader.common.URLCONST.DEFAULT_PLUGIN_CONFIG_URL +import xyz.fycz.myreader.entity.PluginConfig +import xyz.fycz.myreader.model.third3.Coroutine +import xyz.fycz.myreader.model.third3.http.getProxyClient +import xyz.fycz.myreader.model.third3.http.newCallResponse +import xyz.fycz.myreader.model.third3.http.newCallResponseBody +import xyz.fycz.myreader.model.third3.http.text +import xyz.fycz.myreader.util.SharedPreUtils +import xyz.fycz.myreader.util.ToastUtils +import java.io.File + + +/** + * @author fengyue + * @date 2022/3/29 12:36 + */ +object PluginUtils { + + val TAG = PluginUtils.javaClass.simpleName + + fun init() { + val pluginConfigUrl = + SharedPreUtils.getInstance().getString("pluginConfigUrl", DEFAULT_PLUGIN_CONFIG_URL) + var config: PluginConfig? = null + Coroutine.async { + val configJson = getProxyClient().newCallResponseBody { + url(pluginConfigUrl) + }.text() + config = GSON.fromJsonObject(configJson) + val oldConfig = GSON.fromJsonObject( + SharedPreUtils.getInstance().getString("pluginConfig") + ) ?: PluginConfig("dynamic.dex", 100, "", "", "") + if (config != null) { + if (config!!.versionCode > oldConfig.versionCode) { + downloadPlugin(config!!) + SharedPreUtils.getInstance().putString("pluginConfig", configJson) + } + } else { + config = oldConfig + } + }.onSuccess { + loadAppLoader(App.getmContext(), config) + } + } + + private suspend fun downloadPlugin(config: PluginConfig) { + val res = getProxyClient().newCallResponseBody { + url(config.url) + } + FileUtils.getFile(APPCONST.PLUGIN_DIR_PATH + config.name) + .writeBytes(res.byteStream().readBytes()) + } + + private fun loadAppLoader(context: Context, config: PluginConfig?) { + config?.let { + val pluginPath = APPCONST.PLUGIN_DIR_PATH + it.name + val desFile = File(pluginPath) + if (desFile.exists()) { + val dexClassLoader = DexClassLoader( + pluginPath, + FileUtils.getCachePath(), + null, + context.classLoader + ) + try { + val libClazz = dexClassLoader.loadClass("xyz.fycz.dynamic.AppLoadImpl") + val appLoader = libClazz.newInstance() as IAppLoader? + appLoader?.run { + val appParam = AppParam() + appParam.classLoader = context.classLoader + appParam.packageName = context.packageName + appParam.appInfo = context.applicationInfo + onLoad(appParam) + } + } catch (e: Exception) { + e.printStackTrace() + } + } else { + Log.d(TAG, pluginPath + "文件不存在") + } + } + } +} \ No newline at end of file diff --git a/build.gradle b/build.gradle index 4166b49..caa4464 100644 --- a/build.gradle +++ b/build.gradle @@ -14,7 +14,7 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:4.1.3' classpath 'org.greenrobot:greendao-gradle-plugin:3.3.0' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10" classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/dynamic/.gitignore b/dynamic/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/dynamic/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/dynamic/build.gradle b/dynamic/build.gradle new file mode 100644 index 0000000..27227fc --- /dev/null +++ b/dynamic/build.gradle @@ -0,0 +1,38 @@ +plugins { + id 'com.android.library' + id 'org.jetbrains.kotlin.android' +} + +android { + compileSdkVersion 29 + + defaultConfig { + minSdkVersion 21 + targetSdkVersion 29 + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles "consumer-rules.pro" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.6.0' + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test.ext:junit:1.1.3' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + compileOnly("me.fycz.maple:maple:1.6") +} \ No newline at end of file diff --git a/dynamic/consumer-rules.pro b/dynamic/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/dynamic/proguard-rules.pro b/dynamic/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/dynamic/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/dynamic/src/androidTest/java/xyz/fycz/dynamic/ExampleInstrumentedTest.kt b/dynamic/src/androidTest/java/xyz/fycz/dynamic/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..2a072e6 --- /dev/null +++ b/dynamic/src/androidTest/java/xyz/fycz/dynamic/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package xyz.fycz.dynamic + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("xyz.fycz.dynamic.test", appContext.packageName) + } +} \ No newline at end of file diff --git a/dynamic/src/main/AndroidManifest.xml b/dynamic/src/main/AndroidManifest.xml new file mode 100644 index 0000000..338ca0d --- /dev/null +++ b/dynamic/src/main/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/dynamic/src/main/java/xyz/fycz/dynamic/AppLoadImpl.kt b/dynamic/src/main/java/xyz/fycz/dynamic/AppLoadImpl.kt new file mode 100644 index 0000000..a2e3bc0 --- /dev/null +++ b/dynamic/src/main/java/xyz/fycz/dynamic/AppLoadImpl.kt @@ -0,0 +1,38 @@ +package xyz.fycz.dynamic + +import me.fycz.maple.MapleBridge +import me.fycz.maple.MapleUtils +import me.fycz.maple.MethodHook +import me.fycz.maple.MethodReplacement + +/** + * @author fengyue + * @date 2022/3/29 11:59 + */ +class AppLoadImpl : IAppLoader { + override fun onLoad(appParam: AppParam) { + try { + MapleUtils.findAndHookMethod( + "xyz.fycz.myreader.util.utils.AdUtils", + appParam.classLoader, + "checkHasAd", + Boolean::class.java, + Boolean::class.java, + object : MethodReplacement() { + override fun replaceHookedMethod(param: MapleBridge.MethodHookParam): Any? { + val just = MapleUtils.findMethodExact( + "io.reactivex.Single", + appParam.classLoader, + "just", + Any::class.java + ) + return just.invoke(null, false) + } + } + ) + } catch (e: Exception) { + e.printStackTrace() + MapleUtils.log(e) + } + } +} \ No newline at end of file diff --git a/dynamic/src/main/java/xyz/fycz/dynamic/AppParam.java b/dynamic/src/main/java/xyz/fycz/dynamic/AppParam.java new file mode 100644 index 0000000..3a2cd49 --- /dev/null +++ b/dynamic/src/main/java/xyz/fycz/dynamic/AppParam.java @@ -0,0 +1,18 @@ +package xyz.fycz.dynamic; + +import android.content.pm.ApplicationInfo; + +/** + * @author fengyue + * @date 2022/3/29 11:31 + */ +public class AppParam { + /** The name of the package being loaded. */ + public String packageName; + + /** The ClassLoader used for this package. */ + public ClassLoader classLoader; + + /** More information about the application being loaded. */ + public ApplicationInfo appInfo; +} diff --git a/dynamic/src/main/java/xyz/fycz/dynamic/IAppLoader.java b/dynamic/src/main/java/xyz/fycz/dynamic/IAppLoader.java new file mode 100644 index 0000000..b9c615a --- /dev/null +++ b/dynamic/src/main/java/xyz/fycz/dynamic/IAppLoader.java @@ -0,0 +1,9 @@ +package xyz.fycz.dynamic; + +/** + * @author fengyue + * @date 2022/3/29 11:27 + */ +public interface IAppLoader { + void onLoad(AppParam appParam); +} diff --git a/dynamic/src/test/java/xyz/fycz/dynamic/ExampleUnitTest.kt b/dynamic/src/test/java/xyz/fycz/dynamic/ExampleUnitTest.kt new file mode 100644 index 0000000..e853819 --- /dev/null +++ b/dynamic/src/test/java/xyz/fycz/dynamic/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package xyz.fycz.dynamic + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 9fdc043..31799a6 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,3 @@ include ':app' include ':DialogX' +include ':dynamic'