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 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'