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/misc.xml b/.idea/misc.xml
index 88ef7a3..bba6cb8 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -18,7 +18,7 @@
-
+
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 0000000..59aa4b8
Binary files /dev/null and b/app/libs/dynamic-release.aar differ
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 82d6116..47eec0b 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -43,6 +43,7 @@
+
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'