diff --git a/projects/sdk/core/gradle-plugin/build.gradle b/projects/sdk/core/gradle-plugin/build.gradle index b3f479cd7..1339d6feb 100644 --- a/projects/sdk/core/gradle-plugin/build.gradle +++ b/projects/sdk/core/gradle-plugin/build.gradle @@ -16,6 +16,7 @@ dependencies { implementation "com.android.tools.build:gradle:$build_gradle_version" implementation 'com.googlecode.json-simple:json-simple:1.1' implementation project(':transform') + implementation project(':manifest-parser') testImplementation 'junit:junit:4.12' testImplementation gradleTestKit() diff --git a/projects/sdk/core/gradle-plugin/src/main/kotlin/com/tencent/shadow/core/gradle/ShadowPlugin.kt b/projects/sdk/core/gradle-plugin/src/main/kotlin/com/tencent/shadow/core/gradle/ShadowPlugin.kt index 665fb4ba4..f568c4999 100644 --- a/projects/sdk/core/gradle-plugin/src/main/kotlin/com/tencent/shadow/core/gradle/ShadowPlugin.kt +++ b/projects/sdk/core/gradle-plugin/src/main/kotlin/com/tencent/shadow/core/gradle/ShadowPlugin.kt @@ -18,14 +18,18 @@ package com.tencent.shadow.core.gradle +import com.android.build.gradle.AppExtension import com.android.build.gradle.AppPlugin import com.android.build.gradle.BaseExtension +import com.android.build.gradle.tasks.ProcessMultiApkApplicationManifest import com.tencent.shadow.core.gradle.extensions.PackagePluginExtension +import com.tencent.shadow.core.manifest_parser.generatePluginManifest import com.tencent.shadow.core.transform.ShadowTransform import com.tencent.shadow.core.transform_kit.AndroidClassPoolBuilder import com.tencent.shadow.core.transform_kit.ClassPoolBuilder import org.gradle.api.* import org.gradle.api.plugins.BasePlugin +import org.gradle.api.provider.Property import java.io.File import kotlin.reflect.full.declaredFunctions import kotlin.reflect.jvm.isAccessible @@ -61,6 +65,8 @@ class ShadowPlugin : Plugin { initAndroidClassPoolBuilder(baseExtension, project) createPackagePluginTasks(project) + + createGeneratePluginManifestTasks(project) } } @@ -83,6 +89,42 @@ class ShadowPlugin : Plugin { } } + private fun createGeneratePluginManifestTasks(project: Project) { + val appExtension: AppExtension = project.extensions.getByType(AppExtension::class.java) + appExtension.applicationVariants.filter { variant -> + variant.productFlavors.any { flavor -> + flavor.dimension == ShadowTransform.DimensionName && + flavor.name == ShadowTransform.ApplyShadowTransformFlavorName + } + }.forEach { pluginVariant -> + val output = pluginVariant.outputs.first() + val processManifestTask = output.processManifestProvider.get() + val manifestFile = (processManifestTask as ProcessMultiApkApplicationManifest).mainMergedManifest.get().asFile + val variantName = manifestFile.parentFile.name + val outputDir = File(project.buildDir, "generated/source/pluginManifest/$variantName") + + // 添加生成PluginManifest.java任务 + val task = project.tasks.register("generate${variantName.capitalize()}PluginManifest") { + it.dependsOn(processManifestTask) + it.inputs.file(manifestFile) + it.outputs.dir(outputDir).withPropertyName("outputDir") + val packageForR = (project.tasks.getByName("processPluginDebugResources").property("namespace") as Property).get() + it.doLast { + generatePluginManifest(manifestFile, + outputDir, + "com.tencent.shadow.core.manifest_parser", + packageForR) + } + } + project.tasks.getByName("compile${variantName.capitalize()}JavaWithJavac").dependsOn(task) + + // 把PluginManifest.java添加为源码 + appExtension.sourceSets.getByName(variantName).java { + srcDir(outputDir) + } + } + } + private fun addFlavorForTransform(baseExtension: BaseExtension) { baseExtension.flavorDimensionList.add(ShadowTransform.DimensionName) try { diff --git a/projects/sdk/core/gradle-plugin/src/test/kotlin/com/tencent/shadow/core/gradle/PackageMultiPluginTest.kt b/projects/sdk/core/gradle-plugin/src/test/kotlin/com/tencent/shadow/core/gradle/PackageMultiPluginTest.kt index b3c22bda4..fe962d9a5 100644 --- a/projects/sdk/core/gradle-plugin/src/test/kotlin/com/tencent/shadow/core/gradle/PackageMultiPluginTest.kt +++ b/projects/sdk/core/gradle-plugin/src/test/kotlin/com/tencent/shadow/core/gradle/PackageMultiPluginTest.kt @@ -47,6 +47,7 @@ class PackageMultiPluginTest { .withProjectDir(ROOT_PROJECT_DIR) .withPluginClasspath() .withArguments(listOf( + "-xgeneratePluginDebugPluginManifest", "-Pdisable_shadow_transform=true", ":plugin1:PackageMultiPlugin" )) diff --git a/projects/sdk/core/gradle-plugin/src/test/kotlin/com/tencent/shadow/core/gradle/PackageOnlyPluginTest.kt b/projects/sdk/core/gradle-plugin/src/test/kotlin/com/tencent/shadow/core/gradle/PackageOnlyPluginTest.kt index 76f247cc5..750d6d72f 100644 --- a/projects/sdk/core/gradle-plugin/src/test/kotlin/com/tencent/shadow/core/gradle/PackageOnlyPluginTest.kt +++ b/projects/sdk/core/gradle-plugin/src/test/kotlin/com/tencent/shadow/core/gradle/PackageOnlyPluginTest.kt @@ -46,6 +46,7 @@ class PackageOnlyPluginTest { .withProjectDir(PLUGIN1_PROJECT_DIR) .withPluginClasspath() .withArguments(listOf( + "-xgeneratePluginDebugPluginManifest", "-Pdisable_shadow_transform=true", ":plugin1:packageOnlyApkPlugin" )) diff --git a/projects/sdk/core/gradle-plugin/src/test/kotlin/com/tencent/shadow/core/gradle/PackagePluginTaskTest.kt b/projects/sdk/core/gradle-plugin/src/test/kotlin/com/tencent/shadow/core/gradle/PackagePluginTaskTest.kt index ffb80c1d0..cd3e7cf2c 100644 --- a/projects/sdk/core/gradle-plugin/src/test/kotlin/com/tencent/shadow/core/gradle/PackagePluginTaskTest.kt +++ b/projects/sdk/core/gradle-plugin/src/test/kotlin/com/tencent/shadow/core/gradle/PackagePluginTaskTest.kt @@ -48,6 +48,7 @@ class PackagePluginTaskTest { .withProjectDir(ROOT_PROJECT_DIR) .withPluginClasspath() .withArguments(listOf( + "-xgeneratePluginDebugPluginManifest", "-Pdisable_shadow_transform=true", ":plugin1:packageDebugPlugin" )) diff --git a/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/ShadowPluginLoader.kt b/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/ShadowPluginLoader.kt index 3bc68bde4..eb9507e3d 100644 --- a/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/ShadowPluginLoader.kt +++ b/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/ShadowPluginLoader.kt @@ -19,7 +19,6 @@ package com.tencent.shadow.core.loader import android.content.Context -import android.content.pm.PackageInfo import android.os.Handler import android.os.Looper import android.os.Parcel @@ -69,11 +68,6 @@ abstract class ShadowPluginLoader(hostAppContext: Context) : DelegateProvider, D */ abstract fun getComponentManager():ComponentManager - /** - * @GuardedBy("mLock") - */ - private val mPluginPackageInfoSet: MutableSet = hashSetOf() - private lateinit var mPluginServiceManager: PluginServiceManager private val mPluginContentProviderManager: PluginContentProviderManager = PluginContentProviderManager() @@ -158,8 +152,6 @@ abstract class ShadowPluginLoader(hostAppContext: Context) : DelegateProvider, D return LoadPluginBloc.loadPlugin( mExecutorService, - mPluginPackageInfoSet, - ::allPluginPackageInfo, mComponentManager, mLock, mPluginPartsMap, @@ -168,12 +160,6 @@ abstract class ShadowPluginLoader(hostAppContext: Context) : DelegateProvider, D loadParameters) } - private fun allPluginPackageInfo(): Array { - mLock.withLock { - return mPluginPackageInfoSet.toTypedArray() - } - } - override fun getHostActivityDelegate(aClass: Class): HostActivityDelegate { return if (HostNativeActivityDelegator::class.java.isAssignableFrom(aClass)) { ShadowNativeActivityDelegate(this) diff --git a/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/blocs/ParsePluginApkBloc.kt b/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/blocs/CheckPackageNameBloc.kt similarity index 54% rename from projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/blocs/ParsePluginApkBloc.kt rename to projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/blocs/CheckPackageNameBloc.kt index e51baf19f..e19306596 100644 --- a/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/blocs/ParsePluginApkBloc.kt +++ b/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/blocs/CheckPackageNameBloc.kt @@ -19,33 +19,16 @@ package com.tencent.shadow.core.loader.blocs import android.content.Context -import android.content.pm.PackageInfo -import android.os.Build -import com.tencent.shadow.core.load_parameters.LoadParameters import com.tencent.shadow.core.loader.exceptions.ParsePluginApkException -import com.tencent.shadow.core.loader.infos.* +import com.tencent.shadow.core.runtime.PluginManifest -/** - * 解析插件apk逻辑 - * - * @author cubershi - */ -object ParsePluginApkBloc { - /** - * 解析插件apk - * - * @param pluginFile 插件apk文件 - * @return 解析信息 - * @throws ParsePluginApkException 解析失败时抛出 - */ +object CheckPackageNameBloc { @Throws(ParsePluginApkException::class) - fun parse( - packageArchiveInfo: PackageInfo, - manifestInfo: ManifestInfo, - loadParameters: LoadParameters, - hostAppContext: Context - ): PluginInfo { - if (packageArchiveInfo.applicationInfo.packageName != hostAppContext.packageName) { + fun check( + pluginManifest: PluginManifest, + hostAppContext: Context + ) { + if (pluginManifest.applicationPackageName != hostAppContext.packageName) { /* 要求插件和宿主包名一致有两方面原因: 1.正常的构建过程中,aapt会将包名写入到arsc文件中。插件正常安装运行时,如果以 @@ -63,41 +46,7 @@ object ParsePluginApkBloc { 我们也可以始终认为Shadow App是宿主的扩展代码,使用是宿主的一部分,那么采用宿主的包名就是理所应当的了。 */ - throw ParsePluginApkException("插件和宿主包名不一致。宿主:${hostAppContext.packageName} 插件:${packageArchiveInfo.applicationInfo.packageName}") - } - - /* - partKey的作用是用来区分一个Component是来自于哪个插件apk的 - */ - val partKey = loadParameters.partKey - - val pluginInfo = PluginInfo( - loadParameters.businessName, - partKey, - packageArchiveInfo.applicationInfo.packageName, - packageArchiveInfo.applicationInfo.className - ) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - pluginInfo.appComponentFactory = packageArchiveInfo.applicationInfo.appComponentFactory - } - packageArchiveInfo.activities?.forEach { - pluginInfo.putActivityInfo(PluginActivityInfo(it.name, it.themeResource, it)) - } - - val receiveMap = manifestInfo.receivers.map { it.name to it }.toMap() - packageArchiveInfo.receivers?.forEach { - pluginInfo.putReceiverInfo( - PluginReceiverInfo( - it.name, - it, - receiveMap[it.name]?.actions() - ) - ) - } - packageArchiveInfo.services?.forEach { pluginInfo.putServiceInfo(PluginServiceInfo(it.name)) } - packageArchiveInfo.providers?.forEach { - pluginInfo.putPluginProviderInfo(PluginProviderInfo(it.name, it.authority, it)) + throw ParsePluginApkException("插件和宿主包名不一致。宿主:${hostAppContext.packageName} 插件:${pluginManifest.applicationPackageName}") } - return pluginInfo } } diff --git a/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/blocs/CreateApplicationBloc.kt b/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/blocs/CreateApplicationBloc.kt index f985b558d..7b83f08ca 100644 --- a/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/blocs/CreateApplicationBloc.kt +++ b/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/blocs/CreateApplicationBloc.kt @@ -21,10 +21,11 @@ package com.tencent.shadow.core.loader.blocs import android.content.Context import android.content.pm.ApplicationInfo import android.content.res.Resources +import com.tencent.shadow.core.load_parameters.LoadParameters import com.tencent.shadow.core.loader.classloaders.PluginClassLoader import com.tencent.shadow.core.loader.exceptions.CreateApplicationException -import com.tencent.shadow.core.loader.infos.PluginInfo import com.tencent.shadow.core.loader.managers.ComponentManager +import com.tencent.shadow.core.runtime.PluginManifest import com.tencent.shadow.core.runtime.ShadowAppComponentFactory import com.tencent.shadow.core.runtime.ShadowApplication @@ -37,33 +38,32 @@ object CreateApplicationBloc { @Throws(CreateApplicationException::class) fun createShadowApplication( pluginClassLoader: PluginClassLoader, - pluginInfo: PluginInfo, + loadParameters: LoadParameters, + pluginManifest: PluginManifest, resources: Resources, hostAppContext: Context, componentManager: ComponentManager, - applicationInfo: ApplicationInfo, + pluginApplicationInfo: ApplicationInfo, appComponentFactory: ShadowAppComponentFactory ): ShadowApplication { try { - val appClassName = pluginInfo.applicationClassName - ?: ShadowApplication::class.java.name + val appClassName = pluginManifest.applicationClassName + ?: ShadowApplication::class.java.name val shadowApplication = - appComponentFactory.instantiateApplication(pluginClassLoader, appClassName) - val partKey = pluginInfo.partKey + appComponentFactory.instantiateApplication(pluginClassLoader, appClassName) + val partKey = loadParameters.partKey shadowApplication.setPluginResources(resources) shadowApplication.setPluginClassLoader(pluginClassLoader) shadowApplication.setPluginComponentLauncher(componentManager) - shadowApplication.setBroadcasts(pluginInfo.mReceivers.map { receiveInfo -> - receiveInfo.className!! to receiveInfo.actions - }.toMap()) + shadowApplication.setBroadcasts(pluginManifest.receivers) shadowApplication.setAppComponentFactory(appComponentFactory) - shadowApplication.applicationInfo = applicationInfo - shadowApplication.setBusinessName(pluginInfo.businessName) + shadowApplication.applicationInfo = pluginApplicationInfo + shadowApplication.setBusinessName(loadParameters.businessName) shadowApplication.setPluginPartKey(partKey) //和ShadowActivityDelegate.initPluginActivity一样,attachBaseContext放到最后 shadowApplication.setHostApplicationContextAsBase(hostAppContext) - shadowApplication.setTheme(applicationInfo.theme) + shadowApplication.setTheme(pluginManifest.applicationTheme) return shadowApplication } catch (e: Exception) { throw CreateApplicationException(e) diff --git a/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/blocs/CreatePluginApplicationInfoBloc.kt b/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/blocs/CreatePluginApplicationInfoBloc.kt new file mode 100644 index 000000000..ee736b056 --- /dev/null +++ b/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/blocs/CreatePluginApplicationInfoBloc.kt @@ -0,0 +1,43 @@ +package com.tencent.shadow.core.loader.blocs + +import android.content.Context +import android.content.pm.ApplicationInfo +import android.os.Build +import com.tencent.shadow.core.common.InstalledApk +import com.tencent.shadow.core.load_parameters.LoadParameters +import com.tencent.shadow.core.runtime.PluginManifest +import com.tencent.shadow.core.runtime.ShadowContext +import java.io.File + +object CreatePluginApplicationInfoBloc { + fun create(installedApk: InstalledApk, + loadParameters: LoadParameters, + pluginManifest: PluginManifest, + hostAppContext: Context): ApplicationInfo { + val result = ApplicationInfo(hostAppContext.applicationInfo) + result.sourceDir = installedApk.apkFilePath + result.nativeLibraryDir = installedApk.libraryPath + result.dataDir = makeDataDir(loadParameters, hostAppContext).absolutePath + + result.packageName = pluginManifest.applicationPackageName + result.className = pluginManifest.applicationClassName + result.theme = pluginManifest.applicationTheme + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + result.appComponentFactory = pluginManifest.appComponentFactory + } + return result + } + + fun makeDataDir(loadParameters: LoadParameters, hostAppContext: Context): File { + val tempContext = ShadowContext(hostAppContext, 0).apply { + setBusinessName(loadParameters.businessName) + } + val dataDir = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + tempContext.dataDir + } else { + File(tempContext.filesDir, "dataDir") + } + dataDir.mkdirs() + return dataDir + } +} \ No newline at end of file diff --git a/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/blocs/CreateResourceBloc.kt b/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/blocs/CreateResourceBloc.kt index 32e05abd1..0d28c8b9d 100644 --- a/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/blocs/CreateResourceBloc.kt +++ b/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/blocs/CreateResourceBloc.kt @@ -19,7 +19,7 @@ package com.tencent.shadow.core.loader.blocs import android.content.Context -import android.content.pm.PackageInfo +import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import android.content.res.Resources import android.os.Handler @@ -28,7 +28,7 @@ import android.webkit.WebView import java.util.concurrent.CountDownLatch object CreateResourceBloc { - fun create(packageArchiveInfo: PackageInfo, archiveFilePath: String, hostAppContext: Context): Resources { + fun create(archiveFilePath: String, hostAppContext: Context): Resources { //先用宿主context初始化一个WebView,以便WebView的逻辑去修改sharedLibraryFiles,将webview.apk添加进去 val latch = CountDownLatch(1) Handler(Looper.getMainLooper()).post { @@ -38,11 +38,14 @@ object CreateResourceBloc { latch.await() val packageManager = hostAppContext.packageManager - packageArchiveInfo.applicationInfo.publicSourceDir = archiveFilePath - packageArchiveInfo.applicationInfo.sourceDir = archiveFilePath - packageArchiveInfo.applicationInfo.sharedLibraryFiles = hostAppContext.applicationInfo.sharedLibraryFiles + val applicationInfo = ApplicationInfo() + applicationInfo.packageName = hostAppContext.applicationInfo.packageName + applicationInfo.uid = hostAppContext.applicationInfo.uid + applicationInfo.publicSourceDir = archiveFilePath + applicationInfo.sourceDir = archiveFilePath + applicationInfo.sharedLibraryFiles = hostAppContext.applicationInfo.sharedLibraryFiles try { - return packageManager.getResourcesForApplication(packageArchiveInfo.applicationInfo) + return packageManager.getResourcesForApplication(applicationInfo) } catch (e: PackageManager.NameNotFoundException) { throw RuntimeException(e) } diff --git a/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/blocs/LoadPluginBloc.kt b/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/blocs/LoadPluginBloc.kt index 183e0bf4a..d9b0b51a1 100644 --- a/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/blocs/LoadPluginBloc.kt +++ b/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/blocs/LoadPluginBloc.kt @@ -19,9 +19,6 @@ package com.tencent.shadow.core.loader.blocs import android.content.Context -import android.content.pm.PackageInfo -import android.content.pm.PackageManager -import android.os.Build import com.tencent.shadow.core.common.InstalledApk import com.tencent.shadow.core.load_parameters.LoadParameters import com.tencent.shadow.core.loader.exceptions.LoadPluginException @@ -31,7 +28,6 @@ import com.tencent.shadow.core.loader.managers.PluginPackageManagerImpl import com.tencent.shadow.core.runtime.PluginPartInfo import com.tencent.shadow.core.runtime.PluginPartInfoManager import com.tencent.shadow.core.runtime.ShadowAppComponentFactory -import com.tencent.shadow.core.runtime.ShadowContext import java.io.File import java.util.concurrent.Callable import java.util.concurrent.ExecutorService @@ -43,8 +39,6 @@ object LoadPluginBloc { @Throws(LoadPluginException::class) fun loadPlugin( executorService: ExecutorService, - pluginPackageInfoSet: MutableSet, - allPluginPackageInfo: () -> (Array), componentManager: ComponentManager, lock: ReentrantLock, pluginPartsMap: MutableMap, @@ -61,67 +55,45 @@ object LoadPluginBloc { } }) - val getPackageInfo = executorService.submit(Callable { - val archiveFilePath = installedApk.apkFilePath - val packageManager = hostAppContext.packageManager - - val packageArchiveInfo = packageManager.getPackageArchiveInfo( - archiveFilePath, - PackageManager.GET_ACTIVITIES - or PackageManager.GET_META_DATA - or PackageManager.GET_SERVICES - or PackageManager.GET_PROVIDERS - or PackageManager.GET_RECEIVERS - or PackageManager.GET_SIGNATURES - ) - ?: throw NullPointerException("getPackageArchiveInfo return null.archiveFilePath==$archiveFilePath") - - val tempContext = ShadowContext(hostAppContext, 0).apply { - setBusinessName(loadParameters.businessName) - } - val dataDir = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - tempContext.dataDir - } else { - File(tempContext.filesDir, "dataDir") - } - dataDir.mkdirs() - - packageArchiveInfo.applicationInfo.nativeLibraryDir = installedApk.libraryPath - packageArchiveInfo.applicationInfo.dataDir = dataDir.absolutePath - packageArchiveInfo.applicationInfo.processName = - hostAppContext.applicationInfo.processName - packageArchiveInfo.applicationInfo.uid = hostAppContext.applicationInfo.uid - - lock.withLock { pluginPackageInfoSet.add(packageArchiveInfo) } - packageArchiveInfo - }) - - val buildManifestInfo = executorService.submit(Callable { - ParseManifestBloc.parse(hostAppContext, installedApk) + val buildPluginManifest = executorService.submit(Callable { + val pluginClassLoader = buildClassLoader.get() + val pluginManifest = pluginClassLoader.loadPluginManifest() + CheckPackageNameBloc.check(pluginManifest, hostAppContext) + pluginManifest }) - val buildPluginInfo = executorService.submit(Callable { - val packageInfo = getPackageInfo.get() - val manifestInfo = buildManifestInfo.get() - ParsePluginApkBloc.parse(packageInfo, manifestInfo, loadParameters, hostAppContext) + val buildPluginApplicationInfo = executorService.submit(Callable { + val pluginManifest = buildPluginManifest.get() + val pluginApplicationInfo = CreatePluginApplicationInfoBloc.create( + installedApk, + loadParameters, + pluginManifest, + hostAppContext + ) + pluginApplicationInfo }) val buildPackageManager = executorService.submit(Callable { - val packageInfo = getPackageInfo.get() + val pluginApplicationInfo = buildPluginApplicationInfo.get() val hostPackageManager = hostAppContext.packageManager - PluginPackageManagerImpl(hostPackageManager, packageInfo, allPluginPackageInfo) + PluginPackageManagerImpl( + pluginApplicationInfo, + installedApk.apkFilePath, + componentManager, + hostPackageManager, + ) }) val buildResources = executorService.submit(Callable { - val packageInfo = getPackageInfo.get() - CreateResourceBloc.create(packageInfo, installedApk.apkFilePath, hostAppContext) + CreateResourceBloc.create(installedApk.apkFilePath, hostAppContext) }) - val buildAppComponentFactory = executorService.submit(Callable { + val buildAppComponentFactory = executorService.submit(Callable { val pluginClassLoader = buildClassLoader.get() - val pluginInfo = buildPluginInfo.get() - if (pluginInfo.appComponentFactory != null) { - val clazz = pluginClassLoader.loadClass(pluginInfo.appComponentFactory) + val pluginManifest = buildPluginManifest.get() + val appComponentFactory = pluginManifest.appComponentFactory + if (appComponentFactory != null) { + val clazz = pluginClassLoader.loadClass(appComponentFactory) ShadowAppComponentFactory::class.java.cast(clazz.newInstance()) } else ShadowAppComponentFactory() }) @@ -129,17 +101,18 @@ object LoadPluginBloc { val buildApplication = executorService.submit(Callable { val pluginClassLoader = buildClassLoader.get() val resources = buildResources.get() - val pluginInfo = buildPluginInfo.get() - val packageInfo = getPackageInfo.get() val appComponentFactory = buildAppComponentFactory.get() + val pluginManifest = buildPluginManifest.get() + val pluginApplicationInfo = buildPluginApplicationInfo.get() CreateApplicationBloc.createShadowApplication( pluginClassLoader, - pluginInfo, + loadParameters, + pluginManifest, resources, hostAppContext, componentManager, - packageInfo.applicationInfo, + pluginApplicationInfo, appComponentFactory ) }) @@ -151,17 +124,20 @@ object LoadPluginBloc { val pluginPackageManager = buildPackageManager.get() val pluginClassLoader = buildClassLoader.get() val resources = buildResources.get() - val pluginInfo = buildPluginInfo.get() val shadowApplication = buildApplication.get() val appComponentFactory = buildAppComponentFactory.get() + val pluginManifest = buildPluginManifest.get() lock.withLock { - componentManager.addPluginApkInfo(pluginInfo) - pluginPartsMap[pluginInfo.partKey] = PluginParts( + componentManager.addPluginApkInfo( + pluginManifest, + loadParameters, + installedApk.apkFilePath, + ) + pluginPartsMap[loadParameters.partKey] = PluginParts( appComponentFactory, shadowApplication, pluginClassLoader, resources, - pluginInfo.businessName, pluginPackageManager ) PluginPartInfoManager.addPluginInfo(pluginClassLoader, PluginPartInfo(shadowApplication, resources, diff --git a/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/blocs/ParseManifestBloc.kt b/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/blocs/ParseManifestBloc.kt deleted file mode 100644 index b296f23f8..000000000 --- a/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/blocs/ParseManifestBloc.kt +++ /dev/null @@ -1,90 +0,0 @@ -package com.tencent.shadow.core.loader.blocs - -import android.content.Context -import android.content.IntentFilter -import android.content.res.Resources -import com.tencent.shadow.core.common.InstalledApk -import com.tencent.shadow.core.loader.infos.ManifestInfo -import com.tencent.shadow.core.loader.infos.ManifestInfo.Receiver -import com.tencent.shadow.core.loader.infos.ManifestInfo.ReceiverIntentInfo -import org.xmlpull.v1.XmlPullParser - -/** - * 解析插件的AndroidManifest.xml文件 - * 由于系统开放接口不提供广播的action信息,此处采用手动解析的方式处理,减少插件化的适配工作 - * 后续对于AndroidManifest.xml的处理可在此基础上扩展 - * - * @author xuedizi2009@163.com - */ -object ParseManifestBloc { - - private const val ANDROID_RESOURCES = "http://schemas.android.com/apk/res/android" - private const val TAG_RECEIVER = "receiver" - private const val TAG_INTENT_FILTER = "intent-filter" - private const val TAG_ACTION = "action" - private const val ATTR_NAME = "name" - - @Throws(Exception::class) - fun parse(context: Context, installedApk: InstalledApk): ManifestInfo = ManifestInfo().apply { - val resources: Resources = newResource(context, installedApk) - val parser = resources.assets.openXmlResourceParser("AndroidManifest.xml") - val outerDepth = parser.depth - var type: Int - while (parser.next().also { type = it } != XmlPullParser.END_DOCUMENT - && (type != XmlPullParser.END_TAG || parser.depth > outerDepth) - ) { - if (type == XmlPullParser.START_TAG) { - parseBroadcastReceiver(parser, this) - } - } - } - - /** - * 此处如果使用[LoadPluginBloc.loadPlugin]构建的resources,将包含WebView的resources,故需要重新创建 - */ - private fun newResource(context: Context, installedApk: InstalledApk): Resources { - val packageArchiveInfo = - context.packageManager.getPackageArchiveInfo(installedApk.apkFilePath, 0)!! - val packageManager = context.packageManager - packageArchiveInfo.applicationInfo?.publicSourceDir = installedApk.apkFilePath - packageArchiveInfo.applicationInfo?.sourceDir = installedApk.apkFilePath - return packageManager.getResourcesForApplication(packageArchiveInfo.applicationInfo) - } - - private fun parseBroadcastReceiver(parser: XmlPullParser, manifestInfo: ManifestInfo) { - if (TAG_RECEIVER == parser.name) { - val receiver = Receiver(parser.getAttributeValue(ANDROID_RESOURCES, ATTR_NAME)) - val outerDepth = parser.depth - var type: Int - while (parser.next().also { type = it } != XmlPullParser.END_DOCUMENT - && (type != XmlPullParser.END_TAG - || parser.depth > outerDepth)) { - if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { - continue - } - if (TAG_INTENT_FILTER == parser.name) { - val receiverInfo = ReceiverIntentInfo() - parserIntent(parser, receiverInfo) - receiver.intents.add(receiverInfo) - } - } - manifestInfo.receivers.add(receiver) - } - } - - private fun parserIntent(parser: XmlPullParser, intentFilter: IntentFilter) { - val outerDepth = parser.depth - var type: Int - while (parser.next().also { type = it } != XmlPullParser.END_DOCUMENT - && (type != XmlPullParser.END_TAG || parser.depth > outerDepth) - ) { - if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { - continue - } - if (TAG_ACTION == parser.name) { - val value = parser.getAttributeValue(ANDROID_RESOURCES, ATTR_NAME) - intentFilter.addAction(value) - } - } - } -} \ No newline at end of file diff --git a/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/classloaders/PluginClassLoader.kt b/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/classloaders/PluginClassLoader.kt index ea0e76948..4352b5b2f 100644 --- a/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/classloaders/PluginClassLoader.kt +++ b/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/classloaders/PluginClassLoader.kt @@ -19,6 +19,7 @@ package com.tencent.shadow.core.loader.classloaders import android.os.Build +import com.tencent.shadow.core.runtime.PluginManifest import dalvik.system.BaseDexClassLoader import org.jetbrains.annotations.TestOnly import java.io.File @@ -111,6 +112,11 @@ class PluginClassLoader( return clazz } + internal fun loadPluginManifest(): PluginManifest { + val clazz = findClass("com.tencent.shadow.core.manifest_parser.PluginManifest") + return PluginManifest::class.java.cast(clazz.newInstance()) + } + } private fun String.subStringBeforeDot() = substringBeforeLast('.', "") diff --git a/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/delegates/ShadowActivityDelegate.kt b/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/delegates/ShadowActivityDelegate.kt index 64fe64492..d954f38df 100644 --- a/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/delegates/ShadowActivityDelegate.kt +++ b/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/delegates/ShadowActivityDelegate.kt @@ -31,7 +31,6 @@ import android.view.LayoutInflater import android.view.WindowManager import com.tencent.shadow.coding.java_build_config.BuildConfig import com.tencent.shadow.core.common.LoggerFactory -import com.tencent.shadow.core.loader.infos.PluginActivityInfo import com.tencent.shadow.core.loader.managers.ComponentManager.Companion.CM_ACTIVITY_INFO_KEY import com.tencent.shadow.core.loader.managers.ComponentManager.Companion.CM_BUSINESS_NAME_KEY import com.tencent.shadow.core.loader.managers.ComponentManager.Companion.CM_CALLING_ACTIVITY_KEY @@ -39,10 +38,7 @@ import com.tencent.shadow.core.loader.managers.ComponentManager.Companion.CM_CLA import com.tencent.shadow.core.loader.managers.ComponentManager.Companion.CM_EXTRAS_BUNDLE_KEY import com.tencent.shadow.core.loader.managers.ComponentManager.Companion.CM_LOADER_BUNDLE_KEY import com.tencent.shadow.core.loader.managers.ComponentManager.Companion.CM_PART_KEY -import com.tencent.shadow.core.runtime.MixResources -import com.tencent.shadow.core.runtime.PluginActivity -import com.tencent.shadow.core.runtime.ShadowActivity -import com.tencent.shadow.core.runtime.ShadowActivityLifecycleCallbacks +import com.tencent.shadow.core.runtime.* import com.tencent.shadow.core.runtime.container.HostActivityDelegate import com.tencent.shadow.core.runtime.container.HostActivityDelegator @@ -84,7 +80,7 @@ open class ShadowActivityDelegate(private val mDI: DI) : GeneratedShadowActivity private lateinit var mCurrentConfiguration: Configuration private var mPluginHandleConfigurationChange: Int = 0 private var mCallingActivity: ComponentName? = null - protected lateinit var mPluginActivityInfo: PluginActivityInfo + protected lateinit var mPluginActivityInfo: PluginManifest.ActivityInfo override fun onCreate(savedInstanceState: Bundle?) { val pluginInitBundle = savedInstanceState ?: mHostActivityDelegator.intent.extras!! @@ -102,12 +98,12 @@ open class ShadowActivityDelegate(private val mDI: DI) : GeneratedShadowActivity mBundleForPluginLoader = bundleForPluginLoader bundleForPluginLoader.classLoader = this.javaClass.classLoader val pluginActivityClassName = bundleForPluginLoader.getString(CM_CLASS_NAME_KEY)!! - val pluginActivityInfo: PluginActivityInfo = bundleForPluginLoader.getParcelable(CM_ACTIVITY_INFO_KEY)!! + val pluginActivityInfo: PluginManifest.ActivityInfo = bundleForPluginLoader.getParcelable(CM_ACTIVITY_INFO_KEY)!! mPluginActivityInfo = pluginActivityInfo mCurrentConfiguration = Configuration(resources.configuration) mPluginHandleConfigurationChange = - (pluginActivityInfo.activityInfo!!.configChanges + (pluginActivityInfo.configChanges or ActivityInfo.CONFIG_SCREEN_SIZE//系统本身就会单独对待这个属性,不声明也不会重启Activity。 or ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE//系统本身就会单独对待这个属性,不声明也不会重启Activity。 or 0x20000000 //见ActivityInfo.CONFIG_WINDOW_CONFIGURATION 系统处理属性 @@ -135,7 +131,7 @@ open class ShadowActivityDelegate(private val mDI: DI) : GeneratedShadowActivity mHostActivityDelegator.window.callback = pluginActivity //设置插件AndroidManifest.xml 中注册的WindowSoftInputMode - mHostActivityDelegator.window.setSoftInputMode(pluginActivityInfo.activityInfo.softInputMode) + mHostActivityDelegator.window.setSoftInputMode(pluginActivityInfo.softInputMode) //Activity.onCreate调用之前应该先收到onWindowAttributesChanged。 if (mCallOnWindowAttributesChanged) { @@ -158,7 +154,7 @@ open class ShadowActivityDelegate(private val mDI: DI) : GeneratedShadowActivity } } - private fun initPluginActivity(pluginActivity: PluginActivity, pluginActivityInfo: PluginActivityInfo) { + private fun initPluginActivity(pluginActivity: PluginActivity, pluginActivityInfo: PluginManifest.ActivityInfo) { pluginActivity.setHostActivityDelegator(mHostActivityDelegator) pluginActivity.setPluginResources(mPluginResources) pluginActivity.setPluginClassLoader(mPluginClassLoader) @@ -176,7 +172,13 @@ open class ShadowActivityDelegate(private val mDI: DI) : GeneratedShadowActivity //有可能会执行业务Activity覆盖的逻辑。 //所以,这个调用要放在最后。 pluginActivity.setHostContextAsBase(mHostActivityDelegator.hostActivity as Context) - pluginActivity.setTheme(pluginActivityInfo.themeResource) + + val activityTheme = if (pluginActivityInfo.theme != 0) { + pluginActivityInfo.theme + } else { + pluginActivity.applicationInfo.theme + } + pluginActivity.setTheme(activityTheme) } override fun getLoaderVersion() = BuildConfig.VERSION_NAME diff --git a/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/delegates/ShadowNativeActivityDelegate.kt b/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/delegates/ShadowNativeActivityDelegate.kt index 1c75aaf1e..649de01f6 100644 --- a/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/delegates/ShadowNativeActivityDelegate.kt +++ b/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/delegates/ShadowNativeActivityDelegate.kt @@ -5,6 +5,7 @@ import android.content.pm.ActivityInfo import android.content.pm.PackageManager import android.view.InputQueue import android.view.SurfaceHolder +import com.tencent.shadow.core.runtime.PackageManagerInvokeRedirect import com.tencent.shadow.core.runtime.ShadowNativeActivity import com.tencent.shadow.core.runtime.container.HostNativeActivityDelegate @@ -45,9 +46,11 @@ class ShadowNativeActivityDelegate(mDI: DI) : ShadowActivityDelegate(mDI), //预期只有NativeActivity会调用这个方法 override fun getPackageManager(): PackageManager { + val pluginPackageManager = + PackageManagerInvokeRedirect.getPluginPackageManager(mPluginActivity.classLoader) return object : PackageManagerWrapper(mHostActivityDelegator.superGetPackageManager()) { override fun getActivityInfo(component: ComponentName, flags: Int): ActivityInfo { - return mPluginActivityInfo.activityInfo!! + return pluginPackageManager.getActivityInfo(component, flags) } } } diff --git a/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/infos/ManifestInfo.kt b/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/infos/ManifestInfo.kt deleted file mode 100644 index f2415d85b..000000000 --- a/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/infos/ManifestInfo.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.tencent.shadow.core.loader.infos - -import android.content.IntentFilter - -/** - * 插件AndroidManifest.xml信息存储类 - * - * @author xuedizi2009@163.com - */ -class ManifestInfo { - val receivers = mutableListOf() - - class Receiver(val name: String) { - var intents = mutableListOf() - - fun actions(): MutableList { - val actions = mutableListOf() - this.intents.forEach { intentInfo -> - intentInfo.actionsIterator().forEach { action -> - actions.add(action) - } - } - return actions - } - } - - class ReceiverIntentInfo : IntentFilter() -} \ No newline at end of file diff --git a/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/infos/PluginActivityInfo.kt b/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/infos/PluginActivityInfo.kt deleted file mode 100644 index cbbad51ab..000000000 --- a/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/infos/PluginActivityInfo.kt +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Tencent is pleased to support the open source community by making Tencent Shadow available. - * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. - * - * Licensed under the BSD 3-Clause License (the "License"); you may not use - * this file except in compliance with the License. You may obtain a copy of - * the License at - * - * https://opensource.org/licenses/BSD-3-Clause - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.tencent.shadow.core.loader.infos - -import android.content.pm.ActivityInfo -import android.os.Parcel -import android.os.Parcelable - -class PluginActivityInfo(className: String?, val themeResource: Int, val activityInfo: ActivityInfo?) : Parcelable, PluginComponentInfo(className) { - constructor(parcel: Parcel) : this( - parcel.readString(), - parcel.readInt(), - parcel.readParcelable(ActivityInfo::class.java.classLoader)) { - } - - override fun writeToParcel(parcel: Parcel, flags: Int) { - parcel.writeString(className) - parcel.writeInt(themeResource) - parcel.writeParcelable(activityInfo, flags) - } - - override fun describeContents(): Int { - return 0 - } - - companion object CREATOR : Parcelable.Creator { - override fun createFromParcel(parcel: Parcel): PluginActivityInfo { - return PluginActivityInfo(parcel) - } - - override fun newArray(size: Int): Array { - return arrayOfNulls(size) - } - } -} \ No newline at end of file diff --git a/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/infos/PluginComponentInfo.kt b/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/infos/PluginComponentInfo.kt deleted file mode 100644 index 6de1f7a7e..000000000 --- a/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/infos/PluginComponentInfo.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Tencent is pleased to support the open source community by making Tencent Shadow available. - * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. - * - * Licensed under the BSD 3-Clause License (the "License"); you may not use - * this file except in compliance with the License. You may obtain a copy of - * the License at - * - * https://opensource.org/licenses/BSD-3-Clause - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.tencent.shadow.core.loader.infos - -import android.os.Parcelable - -abstract class PluginComponentInfo(val className: String?) : Parcelable \ No newline at end of file diff --git a/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/infos/PluginInfo.kt b/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/infos/PluginInfo.kt deleted file mode 100644 index 40ea0f986..000000000 --- a/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/infos/PluginInfo.kt +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Tencent is pleased to support the open source community by making Tencent Shadow available. - * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. - * - * Licensed under the BSD 3-Clause License (the "License"); you may not use - * this file except in compliance with the License. You may obtain a copy of - * the License at - * - * https://opensource.org/licenses/BSD-3-Clause - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.tencent.shadow.core.loader.infos - -class PluginInfo( - val businessName: String?, - val partKey: String, - val packageName: String, - val applicationClassName: String? -) { - private val _mActivities: MutableSet = HashSet() - private val _mServices: MutableSet = HashSet() - private val _mProviders: MutableSet = HashSet() - private val _mReceivers: MutableSet = HashSet() - internal val mActivities: Set - get() = _mActivities - internal val mServices: Set - get() = _mServices - internal val mProviders: Set - get() = _mProviders - internal val mReceivers: Set - get() = _mReceivers - - internal var appComponentFactory: String? = null - - fun putActivityInfo(pluginActivityInfo: PluginActivityInfo) { - _mActivities.add(pluginActivityInfo) - } - - fun putServiceInfo(pluginServiceInfo: PluginServiceInfo) { - _mServices.add(pluginServiceInfo) - } - - fun putPluginProviderInfo(pluginProviderInfo: PluginProviderInfo) { - _mProviders.add(pluginProviderInfo) - } - - fun putReceiverInfo(pluginReceiverInfo: PluginReceiverInfo) { - _mReceivers.add(pluginReceiverInfo) - } -} diff --git a/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/infos/PluginParts.kt b/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/infos/PluginParts.kt index 56342ce83..e6c1a148c 100644 --- a/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/infos/PluginParts.kt +++ b/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/infos/PluginParts.kt @@ -29,6 +29,5 @@ class PluginParts( val application: ShadowApplication, val classLoader: PluginClassLoader, val resources: Resources, - val businessName: String?, val pluginPackageManager: PluginPackageManager ) \ No newline at end of file diff --git a/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/infos/PluginProviderInfo.kt b/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/infos/PluginProviderInfo.kt deleted file mode 100644 index 5f273ce12..000000000 --- a/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/infos/PluginProviderInfo.kt +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Tencent is pleased to support the open source community by making Tencent Shadow available. - * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. - * - * Licensed under the BSD 3-Clause License (the "License"); you may not use - * this file except in compliance with the License. You may obtain a copy of - * the License at - * - * https://opensource.org/licenses/BSD-3-Clause - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.tencent.shadow.core.loader.infos - -import android.content.pm.ProviderInfo -import android.os.Parcel -import android.os.Parcelable - -class PluginProviderInfo(className: String?, val authority: String?, val providerInfo: ProviderInfo?) : Parcelable, PluginComponentInfo(className) { - constructor(parcel: Parcel) : this( - parcel.readString(), - parcel.readString(), - parcel.readParcelable(ProviderInfo::class.java.classLoader)) { - } - - override fun writeToParcel(parcel: Parcel, flags: Int) { - parcel.writeString(className) - parcel.writeString(authority) - parcel.writeParcelable(providerInfo, flags) - } - - override fun describeContents(): Int { - return 0 - } - - companion object CREATOR : Parcelable.Creator { - override fun createFromParcel(parcel: Parcel): PluginProviderInfo { - return PluginProviderInfo(parcel) - } - - override fun newArray(size: Int): Array { - return arrayOfNulls(size) - } - } -} \ No newline at end of file diff --git a/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/infos/PluginReceiverInfo.kt b/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/infos/PluginReceiverInfo.kt deleted file mode 100644 index 33d347aa6..000000000 --- a/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/infos/PluginReceiverInfo.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.tencent.shadow.core.loader.infos - -import android.content.pm.ActivityInfo -import android.os.Parcel -import android.os.Parcelable -import android.os.Parcelable.Creator - -/** - * 插件广播数据 - * @author xuedizi2009@163.com - */ -class PluginReceiverInfo( - className: String?, - private val activityInfo: ActivityInfo?, - val actions: List? -) : Parcelable, PluginComponentInfo(className) { - constructor(parcel: Parcel) : this( - parcel.readString(), - parcel.readParcelable(ActivityInfo::class.java.classLoader), - parcel.createStringArrayList() - ) - - override fun writeToParcel(parcel: Parcel, flags: Int) { - parcel.writeString(className) - parcel.writeParcelable(activityInfo, flags) - parcel.writeStringList(actions) - } - - override fun describeContents(): Int { - return 0 - } - - companion object CREATOR : Creator { - override fun createFromParcel(parcel: Parcel): PluginReceiverInfo { - return PluginReceiverInfo(parcel) - } - - override fun newArray(size: Int): Array { - return arrayOfNulls(size) - } - } -} \ No newline at end of file diff --git a/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/infos/PluginServiceInfo.kt b/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/infos/PluginServiceInfo.kt deleted file mode 100644 index 1a947c8fe..000000000 --- a/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/infos/PluginServiceInfo.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Tencent is pleased to support the open source community by making Tencent Shadow available. - * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. - * - * Licensed under the BSD 3-Clause License (the "License"); you may not use - * this file except in compliance with the License. You may obtain a copy of - * the License at - * - * https://opensource.org/licenses/BSD-3-Clause - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.tencent.shadow.core.loader.infos - -import android.os.Parcel -import android.os.Parcelable - -/** - * Created by tracyluo on 2018/6/7. - */ -class PluginServiceInfo(className: String?) : Parcelable, PluginComponentInfo(className) { - constructor(parcel: Parcel) : this( - parcel.readString()) - - override fun writeToParcel(dest: Parcel, flags: Int) { - dest.writeString(className) - } - - override fun describeContents(): Int { - return 0 - } - - companion object CREATOR : Parcelable.Creator { - override fun createFromParcel(parcel: Parcel): PluginServiceInfo { - return PluginServiceInfo(parcel) - } - - override fun newArray(size: Int): Array { - return arrayOfNulls(size) - } - } -} \ No newline at end of file diff --git a/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/managers/ComponentManager.kt b/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/managers/ComponentManager.kt index 0c6ab02c5..18aa02e60 100644 --- a/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/managers/ComponentManager.kt +++ b/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/managers/ComponentManager.kt @@ -24,9 +24,9 @@ import android.content.ServiceConnection import android.os.Bundle import android.util.Pair import com.tencent.shadow.coding.java_build_config.BuildConfig +import com.tencent.shadow.core.load_parameters.LoadParameters import com.tencent.shadow.core.loader.infos.ContainerProviderInfo -import com.tencent.shadow.core.loader.infos.PluginComponentInfo -import com.tencent.shadow.core.loader.infos.PluginInfo +import com.tencent.shadow.core.runtime.PluginManifest import com.tencent.shadow.core.runtime.ShadowContext import com.tencent.shadow.core.runtime.ShadowContext.PluginComponentLauncher import com.tencent.shadow.core.runtime.container.DelegateProvider.LOADER_VERSION_KEY @@ -146,59 +146,67 @@ abstract class ComponentManager : PluginComponentLauncher { /** * key:插件ComponentName - * value:PluginInfo + * value:LoadParameters */ - private val pluginInfoMap: MutableMap = hashMapOf() + private val loadParametersMap: MutableMap = hashMapOf() /** * key:插件ComponentName - * value:PluginComponentInfo + * value:PluginManifest.ActivityInfo */ - private val pluginComponentInfoMap: MutableMap = hashMapOf() + private val pluginActivityInfoMap: MutableMap = hashMapOf() + /** + * 保存所有已加载插件对PluginManifest和apk文件路径对应关系 + * 用于在同一个Loader加载对多个插件之间相互查找组件 + */ + private val allLoadedPlugin: MutableList> = mutableListOf() - fun addPluginApkInfo(pluginInfo: PluginInfo) { - fun common(pluginComponentInfo: PluginComponentInfo,componentName:ComponentName) { - packageNameMap[pluginComponentInfo.className!!] = pluginInfo.packageName - val previousValue = pluginInfoMap.put(componentName, pluginInfo) + fun addPluginApkInfo(pluginManifest: PluginManifest, loadParameters: LoadParameters, archiveFilePath: String) { + fun common(componentInfo: PluginManifest.ComponentInfo, componentName: ComponentName) { + packageNameMap[componentInfo.className] = componentName.packageName + val previousValue = loadParametersMap.put(componentName, loadParameters) if (previousValue != null) { throw IllegalStateException("重复添加Component:$componentName") } - pluginComponentInfoMap[componentName] = pluginComponentInfo } - pluginInfo.mActivities.forEach { - val componentName = ComponentName(pluginInfo.packageName, it.className!!) - common(it,componentName) + val applicationPackageName = pluginManifest.applicationPackageName + pluginManifest.activities?.forEach { + val componentName = ComponentName(applicationPackageName, it.className) + common(it, componentName) componentMap[componentName] = onBindContainerActivity(componentName) + pluginActivityInfoMap[componentName] = it } - pluginInfo.mServices.forEach { - val componentName = ComponentName(pluginInfo.packageName, it.className!!) + pluginManifest.services?.forEach { + val componentName = ComponentName(applicationPackageName, it.className) common(it, componentName) } - pluginInfo.mProviders.forEach { - val componentName = ComponentName(pluginInfo.packageName, it.className!!) + pluginManifest.providers?.forEach { + val componentName = ComponentName(applicationPackageName, it.className) mPluginContentProviderManager!!.addContentProviderInfo( - pluginInfo.partKey, - it, - onBindContainerContentProvider(componentName) + loadParameters.partKey, + it, + onBindContainerContentProvider(componentName) ) } - pluginInfo.mReceivers.forEach { - val componentName = ComponentName(pluginInfo.packageName, it.className!!) + pluginManifest.receivers?.forEach { + val componentName = ComponentName(applicationPackageName, it.className) common(it, componentName) } + + allLoadedPlugin.add(pluginManifest to archiveFilePath) } fun getComponentBusinessName(componentName: ComponentName): String? { - return pluginInfoMap[componentName]?.businessName + return loadParametersMap[componentName]?.businessName } fun getComponentPartKey(componentName: ComponentName) : String? { - return pluginInfoMap[componentName]?.partKey + return loadParametersMap[componentName]?.partKey } private var mPluginServiceManager : PluginServiceManager? = null @@ -222,8 +230,8 @@ abstract class ComponentManager : PluginComponentLauncher { */ private fun Intent.toActivityContainerIntent(): Intent { val bundleForPluginLoader = Bundle() - val pluginComponentInfo = pluginComponentInfoMap[component]!! - bundleForPluginLoader.putParcelable(CM_ACTIVITY_INFO_KEY, pluginComponentInfo) + val pluginActivityInfo = pluginActivityInfoMap[component]!! + bundleForPluginLoader.putParcelable(CM_ACTIVITY_INFO_KEY, pluginActivityInfo) return toContainerIntent(bundleForPluginLoader) } @@ -233,17 +241,24 @@ abstract class ComponentManager : PluginComponentLauncher { * 调用前必须先调用isPluginComponent判断Intent确实一个插件内的组件 */ private fun Intent.toContainerIntent(bundleForPluginLoader: Bundle): Intent { - val component = this.component!! + val component = this.component + ?: throw IllegalArgumentException("Activity Intent必须指定ComponentName") val className = component.className - val packageName = packageNameMap[className]!! + + val packageName = packageNameMap[className] + ?: throw IllegalArgumentException("已加载的插件中找不到${className}对应的packageName") this.component = ComponentName(packageName, className) - val containerComponent = componentMap[component]!! - val businessName = pluginInfoMap[component]!!.businessName - val partKey = pluginInfoMap[component]!!.partKey + + val loadParameters = loadParametersMap[component] + ?: throw IllegalArgumentException("已加载的插件中找不到${component}对应的LoadParameters") + val businessName = loadParameters.businessName + val partKey = loadParameters.partKey val pluginExtras: Bundle? = extras replaceExtras(null as Bundle?) + val containerComponent = componentMap[component] + ?: throw IllegalArgumentException("已加载的插件中找不到${component}对应的ContainerActivity") val containerIntent = Intent(this) containerIntent.component = containerComponent @@ -258,4 +273,44 @@ abstract class ComponentManager : PluginComponentLauncher { containerIntent.putExtra(PROCESS_ID_KEY, DelegateProviderHolder.sCustomPid) return containerIntent } + + fun getArchiveFilePathForActivity(className: String) = + getArchiveFilePath(className, PluginManifest::getActivities) + + fun getArchiveFilePathForService(className: String) = + getArchiveFilePath(className, PluginManifest::getServices) + + fun getArchiveFilePathForProvider(action: String?): kotlin.Pair { + for ((pluginManifest, archiveFilePath) in allLoadedPlugin) { + val providers = pluginManifest.providers + if (providers != null) { + for (provider in providers) { + if (action?.equals(provider.authorities) == true) { + return provider.className to archiveFilePath + } + } + } + } + return null to null + } + + fun getAllArchiveFilePaths() = allLoadedPlugin.map { it.second }.toList() + + private fun getArchiveFilePath( + className: String, + getComponents: (PluginManifest) -> Array? + ): String? { + for ((pluginManifest, archiveFilePath) in allLoadedPlugin) { + val components = getComponents(pluginManifest) + if (components != null) { + for (component in components) { + if (component.className == className) { + return archiveFilePath + } + } + } + } + return null + } + } diff --git a/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/managers/PluginContentProviderManager.kt b/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/managers/PluginContentProviderManager.kt index 477c9c856..d61a26f78 100644 --- a/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/managers/PluginContentProviderManager.kt +++ b/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/managers/PluginContentProviderManager.kt @@ -20,11 +20,12 @@ package com.tencent.shadow.core.loader.managers import android.content.ContentProvider import android.content.Context +import android.content.pm.ProviderInfo import android.net.Uri import android.os.Bundle import com.tencent.shadow.core.loader.infos.ContainerProviderInfo import com.tencent.shadow.core.loader.infos.PluginParts -import com.tencent.shadow.core.loader.infos.PluginProviderInfo +import com.tencent.shadow.core.runtime.PluginManifest import com.tencent.shadow.core.runtime.UriConverter import java.util.* import kotlin.collections.HashSet @@ -44,7 +45,7 @@ class PluginContentProviderManager() : UriConverter.UriParseDelegate { private val providerAuthorityMap = HashMap() - private val pluginProviderInfoMap = HashMap?>() + private val pluginProviderInfoMap = HashMap?>() override fun parse(uriString: String): Uri { @@ -66,13 +67,13 @@ class PluginContentProviderManager() : UriConverter.UriParseDelegate { return pluginUri } - fun addContentProviderInfo(partKey: String, pluginProviderInfo: PluginProviderInfo, containerProviderInfo: ContainerProviderInfo) { - if (providerMap.containsKey(pluginProviderInfo.authority)) { + fun addContentProviderInfo(partKey: String, pluginProviderInfo: PluginManifest.ProviderInfo, containerProviderInfo: ContainerProviderInfo) { + if (providerMap.containsKey(pluginProviderInfo.authorities)) { throw RuntimeException("重复添加 ContentProvider") } - providerAuthorityMap[pluginProviderInfo.authority!!] = containerProviderInfo.authority - var pluginProviderInfos: HashSet? = null + providerAuthorityMap[pluginProviderInfo.authorities] = containerProviderInfo.authority + var pluginProviderInfos: HashSet? = null if (pluginProviderInfoMap.containsKey(partKey)) { pluginProviderInfos = pluginProviderInfoMap[partKey] } else { @@ -82,15 +83,23 @@ class PluginContentProviderManager() : UriConverter.UriParseDelegate { pluginProviderInfoMap.put(partKey, pluginProviderInfos) } - fun createContentProviderAndCallOnCreate(mContext: Context, partKey: String, pluginParts: PluginParts?) { + fun createContentProviderAndCallOnCreate(context: Context, partKey: String, pluginParts: PluginParts?) { pluginProviderInfoMap[partKey]?.forEach { try { val contentProvider = pluginParts!!.appComponentFactory .instantiateProvider(pluginParts.classLoader, it.className) - contentProvider?.attachInfo(mContext, it.providerInfo) - providerMap[it.authority!!] = contentProvider + + //convert PluginManifest.ProviderInfo to android.content.pm.ProviderInfo + val providerInfo = ProviderInfo() + providerInfo.packageName = context.packageName + providerInfo.name = it.className + providerInfo.authority = it.authorities + providerInfo.grantUriPermissions = true //插件没有权限管理机制 + + contentProvider?.attachInfo(context, providerInfo) + providerMap[it.authorities] = contentProvider } catch (e: Exception) { - throw RuntimeException("partKey==$partKey className==${it.className} providerInfo==${it.providerInfo}", e) + throw RuntimeException("partKey==$partKey className==${it.className} authorities==${it.authorities}", e) } } diff --git a/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/managers/PluginPackageManagerImpl.kt b/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/managers/PluginPackageManagerImpl.kt index 03e17b134..3f86c81d3 100644 --- a/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/managers/PluginPackageManagerImpl.kt +++ b/projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/managers/PluginPackageManagerImpl.kt @@ -18,124 +18,156 @@ package com.tencent.shadow.core.loader.managers +import android.annotation.SuppressLint import android.content.ComponentName import android.content.Intent import android.content.pm.* import com.tencent.shadow.core.runtime.PluginPackageManager -internal class PluginPackageManagerImpl(private val hostPackageManager: PackageManager, - private val packageInfo: PackageInfo, - private val allPluginPackageInfo: () -> (Array)) - : PluginPackageManager { +@SuppressLint("WrongConstant") +internal class PluginPackageManagerImpl(private val pluginApplicationInfoFromPluginManifest: ApplicationInfo, + private val pluginArchiveFilePath: String, + private val componentManager: ComponentManager, + private val hostPackageManager: PackageManager +) : PluginPackageManager { override fun getApplicationInfo(packageName: String, flags: Int): ApplicationInfo = - if (packageInfo.applicationInfo.packageName == packageName) { - packageInfo.applicationInfo + if (packageName.isPlugin()) { + getPluginApplicationInfo(flags) } else { hostPackageManager.getApplicationInfo(packageName, flags) } override fun getPackageInfo(packageName: String, flags: Int): PackageInfo? = - if (packageInfo.applicationInfo.packageName == packageName) { + if (packageName.isPlugin()) { + val packageInfo = hostPackageManager.getPackageArchiveInfo(pluginArchiveFilePath, flags) + if (packageInfo != null) { + packageInfo.applicationInfo = getPluginApplicationInfo(flags) + } packageInfo } else { hostPackageManager.getPackageInfo(packageName, flags) } - override fun getActivityInfo(component: ComponentName, flags: Int): ActivityInfo { - if (component.packageName == packageInfo.applicationInfo.packageName) { - val pluginActivityInfo = allPluginPackageInfo() - .mapNotNull { it.activities } - .flatMap { it.asIterable() }.find { - it.name == component.className - } - if (pluginActivityInfo != null) { - return pluginActivityInfo + override fun getActivityInfo(component: ComponentName, flags: Int): ActivityInfo? = + getComponentInfo( + component, + flags, + ComponentManager::getArchiveFilePathForActivity, + PackageManager.GET_ACTIVITIES, + { it?.activities }, + PackageManager::getActivityInfo + ) + + override fun getServiceInfo(component: ComponentName, flags: Int): ServiceInfo? = + getComponentInfo( + component, + flags, + ComponentManager::getArchiveFilePathForService, + PackageManager.GET_SERVICES, + { it?.services }, + PackageManager::getServiceInfo + ) + + override fun resolveActivity(intent: Intent, flags: Int): ResolveInfo? { + val component = intent.component + if (component != null) { + val activityInfo = getActivityInfo(component, flags) + if (activityInfo != null) { + val resolveInfo = ResolveInfo() + resolveInfo.activityInfo = activityInfo + return resolveInfo } } - return hostPackageManager.getActivityInfo(component, flags) + return hostPackageManager.resolveActivity(intent, flags) } - override fun getServiceInfo(component: ComponentName, flags: Int): ServiceInfo { - if (component.packageName == packageInfo.applicationInfo.packageName) { - val pluginServiceInfo = allPluginPackageInfo() - .mapNotNull { it.services } - .flatMap { it.asIterable() }.find { - it.name == component.className - } - if (pluginServiceInfo != null) { - return pluginServiceInfo + override fun resolveService(intent: Intent, flags: Int): ResolveInfo? { + val component = intent.component + if (component != null) { + val serviceInfo = getServiceInfo(component, flags) + if (serviceInfo != null) { + val resolveInfo = ResolveInfo() + resolveInfo.serviceInfo = serviceInfo + return resolveInfo + } } - } - return hostPackageManager.getServiceInfo(component, flags) + return hostPackageManager.resolveService(intent, flags) } - override fun resolveContentProvider(name: String, flags: Int): ProviderInfo? { - val pluginProviderInfo = allPluginPackageInfo() - .providers().find { - it.authority == name + private fun getComponentInfo( + component: ComponentName, + flags: Int, + getArchiveFilePath: ComponentManager.(String) -> String?, + componentGetFlag: Int, + getFromPackageInfo: (PackageInfo?) -> Array?, + getFromHost: PackageManager.(component: ComponentName, flags: Int) -> T? + ): T? { + + if (component.packageName.isPlugin()) { + val archiveFilePath = componentManager.getArchiveFilePath(component.className) + if (archiveFilePath != null) { + val packageInfo = hostPackageManager.getPackageArchiveInfo( + archiveFilePath, componentGetFlag or flags + ) + val componentInfo = getFromPackageInfo(packageInfo)?.find { + it.name == component.className } - if (pluginProviderInfo != null) { - return pluginProviderInfo + if (componentInfo != null) { + return componentInfo + } + } } + return hostPackageManager.getFromHost(component, flags) + } + override fun resolveContentProvider(name: String, flags: Int): ProviderInfo? { + val (className, archiveFilePath) = componentManager.getArchiveFilePathForProvider(name) + if (archiveFilePath != null) { + val packageInfo = hostPackageManager.getPackageArchiveInfo( + archiveFilePath, PackageManager.GET_PROVIDERS or flags + ) + val componentInfo = packageInfo?.providers?.find { + it.name == className + } + if (componentInfo != null) { + return componentInfo + } + } return hostPackageManager.resolveContentProvider(name, flags) } override fun queryContentProviders(processName: String?, uid: Int, flags: Int) = if (processName == null) { - val allNormalProviders = hostPackageManager.queryContentProviders(null, 0, flags) - val allPluginProviders = allPluginPackageInfo().providers() + val allNormalProviders = + hostPackageManager.queryContentProviders(null, 0, flags) + val allPluginProviders = allPluginProviders(flags) listOf(allNormalProviders, allPluginProviders).flatten() + } else if (processName == pluginApplicationInfoFromPluginManifest.processName && + uid == pluginApplicationInfoFromPluginManifest.uid) { + allPluginProviders(flags) } else { - allPluginPackageInfo().filter { - it.applicationInfo.processName == processName - && it.applicationInfo.uid == uid - }.providers() + hostPackageManager.queryContentProviders(processName, uid, flags) } - override fun resolveActivity(intent: Intent, flags: Int): ResolveInfo { - val hostResolveInfo = hostPackageManager.resolveActivity(intent, flags) - return if (hostResolveInfo?.activityInfo == null) { - ResolveInfo().apply { - activityInfo = allPluginPackageInfo() - .flatMap { it.activities.asIterable() } - .find { - it.name == intent.component?.className - } + private fun allPluginProviders(flags: Int): List = + componentManager.getAllArchiveFilePaths().flatMap { + val packageInfo = hostPackageManager.getPackageArchiveInfo(it, + PackageManager.GET_PROVIDERS or flags) + packageInfo?.providers?.asList().orEmpty() } - } else { - hostResolveInfo - } - } - override fun resolveService(intent: Intent, flags: Int): ResolveInfo { - val hostResolveInfo = hostPackageManager.resolveService(intent, flags) - return if (hostResolveInfo?.serviceInfo == null) { - ResolveInfo().apply { - serviceInfo = allPluginPackageInfo() - .flatMap { it.services.asIterable() } - .find { - it.name == intent.component?.className - } - } - } else { - hostResolveInfo - } - } + private fun String.isPlugin() = pluginApplicationInfoFromPluginManifest.packageName == this - private fun Array.providers(): List { - return this.asIterable().providers() - } + private fun getPluginApplicationInfo(flags: Int): ApplicationInfo { + val copy = ApplicationInfo(pluginApplicationInfoFromPluginManifest) - private fun Iterable.providers(): List { - return this.flatMap { - if (it.providers != null) { - it.providers.asIterable() - } else { - emptyList() - } + val needMetaData = flags and PackageManager.GET_META_DATA != 0 + if (needMetaData) { + val packageInfo = hostPackageManager.getPackageArchiveInfo(pluginArchiveFilePath, PackageManager.GET_META_DATA)!! + val metaData = packageInfo.applicationInfo.metaData + copy.metaData = metaData } - } - + return copy + } } \ No newline at end of file diff --git a/projects/sdk/core/manifest-parser/.gitignore b/projects/sdk/core/manifest-parser/.gitignore new file mode 100644 index 000000000..796b96d1c --- /dev/null +++ b/projects/sdk/core/manifest-parser/.gitignore @@ -0,0 +1 @@ +/build diff --git a/projects/sdk/core/manifest-parser/build.gradle b/projects/sdk/core/manifest-parser/build.gradle new file mode 100644 index 000000000..88d8ae8d0 --- /dev/null +++ b/projects/sdk/core/manifest-parser/build.gradle @@ -0,0 +1,22 @@ +apply plugin: 'kotlin' + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation 'com.squareup:javapoet:1.11.1' + implementation project(':runtime') + testImplementation 'junit:junit:4.12' + testImplementation 'commons-io:commons-io:2.9.0' + testImplementation 'com.tencent.shadow.coding:java-build-config' +} + +compileKotlin { + kotlinOptions { + jvmTarget = "1.6" + } +} + +compileTestKotlin { + kotlinOptions { + jvmTarget = "1.6" + } +} diff --git a/projects/sdk/core/manifest-parser/src/main/kotlin/com/tencent/shadow/core/manifest_parser/AndroidManifestKeys.kt b/projects/sdk/core/manifest-parser/src/main/kotlin/com/tencent/shadow/core/manifest_parser/AndroidManifestKeys.kt new file mode 100644 index 000000000..3a2e2ad96 --- /dev/null +++ b/projects/sdk/core/manifest-parser/src/main/kotlin/com/tencent/shadow/core/manifest_parser/AndroidManifestKeys.kt @@ -0,0 +1,25 @@ +package com.tencent.shadow.core.manifest_parser + +sealed class AndroidManifestKeys { + companion object { + const val appComponentFactory = "android:appComponentFactory" + const val `package` = "package" + const val name = "android:name" + const val theme = "android:theme" + const val configChanges = "android:configChanges" + const val windowSoftInputMode = "android:windowSoftInputMode" + const val authorities = "android:authorities" + const val `intent-filter` = "intent-filter" + const val action = "action" + const val manifest = "manifest" + const val application = "application" + const val activity = "activity" + const val service = "service" + const val provider = "provider" + const val receiver = "receiver" + } +} +typealias ComponentMapKey = String +typealias ComponentMapValue = Any +typealias ComponentMap = Map +typealias MutableComponentMap = MutableMap \ No newline at end of file diff --git a/projects/sdk/core/manifest-parser/src/main/kotlin/com/tencent/shadow/core/manifest_parser/AndroidManifestReader.kt b/projects/sdk/core/manifest-parser/src/main/kotlin/com/tencent/shadow/core/manifest_parser/AndroidManifestReader.kt new file mode 100644 index 000000000..86280b9a7 --- /dev/null +++ b/projects/sdk/core/manifest-parser/src/main/kotlin/com/tencent/shadow/core/manifest_parser/AndroidManifestReader.kt @@ -0,0 +1,177 @@ +package com.tencent.shadow.core.manifest_parser + +import org.w3c.dom.Document +import org.w3c.dom.Element +import org.w3c.dom.Node +import java.io.File +import javax.xml.parsers.DocumentBuilderFactory + +typealias ManifestMap = Map + +/** + * 读取xml格式的Manifest到内存Map中 + */ +class AndroidManifestReader { + /** + * 读取入口方法 + * + * @param xmlFile com.android.build.gradle.tasks.ManifestProcessorTask任务的输出文件, + * 一般位于apk工程的build/intermediates/merged_manifest目录中。 + */ + fun read(xmlFile: File): ManifestMap { + val manifest = readXml(xmlFile).documentElement + val application = readApplication(manifest) + val globalAttributes = readGlobalAttributes(manifest, application) + val components = readComponents(application) + return globalAttributes.plus(components) + } + + private fun readXml(xmlFile: File): Document { + try { + val documentBuilderFactory = DocumentBuilderFactory.newDefaultInstance() + val documentBuilder = documentBuilderFactory.newDocumentBuilder() + return documentBuilder.parse(xmlFile)!! + } catch (e: Exception) { + throw RuntimeException("xml应该是AGP生成的合法文件,所以不兼容任何xml读取错误", e) + } + } + + private fun readApplication(manifest: Element): Element? { + val elements = manifest.getElementsByTagName(AndroidManifestKeys.application) + return if (elements.length == 1) { + val node = elements.item(0) + assert(node.nodeType == Node.ELEMENT_NODE) + elements.item(0) as Element + } else { + null + } + } + + /** + * 读取Manifest中那些唯一的属性 + */ + private fun readGlobalAttributes(manifest: Element, application: Element?): Map { + val globalAttributes = mutableMapOf() + + fun manifestAttribute(name: String) { + globalAttributes[name] = manifest.getAttribute(name) + } + + fun applicationAttribute(name: String) { + if (application != null) { + val attribute = application.getAttribute(name) + if (attribute.isNotEmpty()) { + globalAttributes[name] = attribute + } + } + } + + manifestAttribute(AndroidManifestKeys.`package`) + listOf( + AndroidManifestKeys.name, + AndroidManifestKeys.theme, + AndroidManifestKeys.appComponentFactory, + ).forEach(::applicationAttribute) + return globalAttributes + } + + private fun readComponents(application: Element?) = + listOf( + AndroidManifestKeys.activity to ::parseActivity, + AndroidManifestKeys.service to ::parseService, + AndroidManifestKeys.receiver to ::parseReceiver, + AndroidManifestKeys.provider to ::parseProvider, + ).map { (componentKey, parseMethod) -> + val componentArray = parseComponents(application, componentKey, parseMethod) + componentKey to componentArray + } + + + private fun parseComponents( + application: Element?, + componentKey: String, + parseFunction: (Element) -> ComponentMap + ): Array { + if (application == null) { + return emptyArray() + } + val nodeList = application.getElementsByTagName(componentKey) + val length = nodeList.length + val collectionList = mutableListOf() + for (i in 0 until length) { + val node = nodeList.item(i) + assert(node.nodeType == Node.ELEMENT_NODE) + val map = parseFunction(node as Element) + collectionList.add(map) + } + return collectionList.toTypedArray() + } + + private fun parseActivity(element: Element): ComponentMap { + val activityMap = parseComponent(element).toMutableMap() + + listOf( + AndroidManifestKeys.theme, + AndroidManifestKeys.configChanges, + AndroidManifestKeys.windowSoftInputMode, + ).forEach { attributeKey -> + activityMap.putAttributeIfNotNull(element, attributeKey) + } + return activityMap + } + + private fun parseService(element: Element): ComponentMap { + return parseComponent(element) + } + + private fun parseReceiver(element: Element): ComponentMap { + val receiverMap = parseComponent(element).toMutableMap() + + val receiverActions = parseReceiverActions(element) + if (receiverActions.isNotEmpty()) { + receiverMap[AndroidManifestKeys.action] = receiverActions + } + + return receiverMap + } + + private fun parseReceiverActions(receiverElement: Element): List { + val intentFilters = receiverElement.getElementsByTagName(AndroidManifestKeys.`intent-filter`) + val collectionList = mutableListOf() + for (i in 0 until intentFilters.length) { + val intentFilter = intentFilters.item(i) + assert(intentFilter.nodeType == Node.ELEMENT_NODE) + val actions = (intentFilter as Element).getElementsByTagName(AndroidManifestKeys.action) + for (j in 0 until actions.length) { + val action = actions.item(j) + assert(action.nodeType == Node.ELEMENT_NODE) + val actionName = (action as Element).getAttribute(AndroidManifestKeys.name) + collectionList.add(actionName) + } + } + return collectionList + } + + private fun parseProvider(element: Element): ComponentMap { + val providerMap = parseComponent(element).toMutableMap() + + providerMap.putAttributeIfNotNull(element, AndroidManifestKeys.authorities) + + return providerMap + } + + private fun parseComponent(element: Element): ComponentMap { + val componentName = element.getAttribute(AndroidManifestKeys.name) + return mapOf( + AndroidManifestKeys.name to componentName + ) + } + + private fun MutableComponentMap.putAttributeIfNotNull( + componentElement: Element, + attributeKey: String) { + if (componentElement.hasAttribute(attributeKey)) { + this[attributeKey] = componentElement.getAttribute(attributeKey) + } + } +} diff --git a/projects/sdk/core/manifest-parser/src/main/kotlin/com/tencent/shadow/core/manifest_parser/ManifestParser.kt b/projects/sdk/core/manifest-parser/src/main/kotlin/com/tencent/shadow/core/manifest_parser/ManifestParser.kt new file mode 100644 index 000000000..584a1952f --- /dev/null +++ b/projects/sdk/core/manifest-parser/src/main/kotlin/com/tencent/shadow/core/manifest_parser/ManifestParser.kt @@ -0,0 +1,18 @@ +package com.tencent.shadow.core.manifest_parser + +import java.io.File + +/** + * manifest-parser的入口方法 + * + * @param xmlFile com.android.build.gradle.tasks.ManifestProcessorTask任务的输出文件, + * 一般位于apk工程的build/intermediates/merged_manifest目录中。 + * @param outputDir 生成文件的输出目录 + * @param packageName 生成类的包名 + * @param packageForR 生成对R.java引用时需要的R文件的包名 + */ +fun generatePluginManifest(xmlFile: File, outputDir: File, packageName: String, packageForR: String) { + val androidManifest = AndroidManifestReader().read(xmlFile) + val generator = PluginManifestGenerator(packageForR) + generator.generate(androidManifest, outputDir, packageName) +} \ No newline at end of file diff --git a/projects/sdk/core/manifest-parser/src/main/kotlin/com/tencent/shadow/core/manifest_parser/PluginManifestGenerator.kt b/projects/sdk/core/manifest-parser/src/main/kotlin/com/tencent/shadow/core/manifest_parser/PluginManifestGenerator.kt new file mode 100644 index 000000000..1c6a93b3a --- /dev/null +++ b/projects/sdk/core/manifest-parser/src/main/kotlin/com/tencent/shadow/core/manifest_parser/PluginManifestGenerator.kt @@ -0,0 +1,361 @@ +package com.tencent.shadow.core.manifest_parser + +import com.squareup.javapoet.* +import com.tencent.shadow.core.runtime.PluginManifest +import java.io.File +import java.util.* +import javax.lang.model.element.Modifier + +/** + * PluginManifest.java生成器 + * + * 将Loader所需的插件Manifest信息生成为Java文件, + * 添加runtime中PluginManifest接口的实现方法 + * + * @param packageForR 生成对R.java引用时需要的R文件的包名 + */ +class PluginManifestGenerator(private val packageForR: String) { + /** + * 生成器入口方法 + * + * 根据AndroidManifestReader输出的Map生成PluginManifest.java到outputDir目录中。 + * + * @param manifestMap AndroidManifestReader#read的输出Map + * @param outputDir 生成文件的输出目录 + * @param packageName 生成类的包名 + */ + fun generate(manifestMap: ManifestMap, outputDir: File, packageName: String) { + val pluginManifestBuilder = PluginManifestBuilder(manifestMap, packageForR) + val pluginManifest = pluginManifestBuilder.build() + JavaFile.builder(packageName, pluginManifest) + .build() + .writeTo(outputDir) + } +} + +private class PluginManifestBuilder( + val manifestMap: ManifestMap, + val packageForR: String +) { + val classBuilder: TypeSpec.Builder = + TypeSpec.classBuilder("PluginManifest") + .addSuperinterface(ClassName.get(PluginManifest::class.java)) + .addModifiers(Modifier.PUBLIC)!! + + fun build(): TypeSpec { + listOf( + *buildApplicationFields(), + buildActivityInfoArrayField(), + buildServiceInfoArrayField(), + buildReceiverInfoArrayField(), + buildProviderInfoArrayField(), + ).forEach { fieldSpec -> + val getterMethod = buildGetterMethod(fieldSpec) + classBuilder.addField(fieldSpec) + classBuilder.addMethod(getterMethod) + } + return classBuilder.build() + } + + private fun buildApplicationFields(): Array { + val stringFields = mapOf( + "applicationPackageName" to AndroidManifestKeys.`package`, + "applicationClassName" to AndroidManifestKeys.name, + "appComponentFactory" to AndroidManifestKeys.appComponentFactory, + ).map { (fieldName, key) -> + buildStringField(fieldName, key) + } + + val resIdFields = mapOf( + "applicationTheme" to AndroidManifestKeys.theme, + ).map { (fieldName, key) -> + buildResIdField(fieldName, key) + } + + return (stringFields + resIdFields).toTypedArray() + } + + private fun buildActivityInfoArrayField() = buildComponentArrayField( + AndroidManifestKeys.activity, + "ActivityInfo", + "activities", + ::toNewActivityInfo, + ) + + private fun buildServiceInfoArrayField() = buildComponentArrayField( + AndroidManifestKeys.service, + "ServiceInfo", + "services", + ::toNewServiceInfo, + ) + + private fun buildReceiverInfoArrayField() = buildComponentArrayField( + AndroidManifestKeys.receiver, + "ReceiverInfo", + "receivers", + ::toNewReceiverInfo, + ) + + private fun buildProviderInfoArrayField() = buildComponentArrayField( + AndroidManifestKeys.provider, + "ProviderInfo", + "providers", + ::toNewProviderInfo, + ) + + private fun buildComponentArrayField( + key: String, + subClassName: String, + fieldName: String, + transform: (ComponentMap) -> String + ): FieldSpec { + @Suppress("UNCHECKED_CAST") + val componentMapArray = manifestMap[key] as Array + val literal = componentMapArray.joinToString( + separator = ",\n", + prefix = "{\n", + postfix = "\n}", + transform = transform + ) + + val componentInfoArrayTypeName = ArrayTypeName.of(ClassName.get( + "com.tencent.shadow.core.runtime", + "PluginManifest", + subClassName)) + + val codeBlock = if (componentMapArray.isNotEmpty()) { + CodeBlock.of("new \$1T \$2L", componentInfoArrayTypeName, literal) + } else { + nullCodeBlock() + } + return privateStaticFinalFieldBuilder( + componentInfoArrayTypeName, + fieldName, + ).initializer( + codeBlock + ).build() + } + + private fun buildStringField(fieldName: String, key: String): FieldSpec { + val value = manifestMap[key] + val codeBlock = if (value != null) { + CodeBlock.of("\"$1L\"", value) + } else { + nullCodeBlock() + } + return privateStaticFinalStringFieldBuilder(fieldName) + .initializer(codeBlock).build() + } + + private fun buildGetterMethod(fieldSpec: FieldSpec): MethodSpec = + MethodSpec.methodBuilder("get${fieldSpec.name.capitalize()}") + .addModifiers( + Modifier.PUBLIC, + Modifier.FINAL, + ) + .returns(fieldSpec.type) + .addStatement(CodeBlock.of("return ${fieldSpec.name}")) + .build() + + private fun buildResIdField(fieldName: String, key: String): FieldSpec { + val manifestValue = manifestMap[key] + return if (manifestValue != null) { + buildResIdFieldWithValue(fieldName, manifestValue) + } else { + privateStaticFinalIntFieldBuilder(fieldName) + .initializer( + CodeBlock.of("$1L", "0") + ).build() + } + } + + private fun buildResIdFieldWithValue( + fieldName: String, + manifestValue: Any, + ): FieldSpec { + + val resIdLiteral = themeStringToResId(manifestValue, packageForR) + return privateStaticFinalIntFieldBuilder(fieldName) + .initializer( + CodeBlock.of("$1L", resIdLiteral) + ).build() + } + + + private fun toNewActivityInfo(componentMap: ComponentMap): String { + fun makeResIdLiteral(key: String, + valueToResId: (value: String) -> String): String { + val value = componentMap[key] as String? + val literal = if (value != null) { + valueToResId(value) + } else { + "0" + } + return literal + } + + val themeLiteral = makeResIdLiteral(AndroidManifestKeys.theme) { + themeStringToResId(it, packageForR) + } + val configChangesLiteral = makeResIdLiteral(AndroidManifestKeys.configChanges) { + configChangesToInt(it) + } + val softInputModeLiteral = makeResIdLiteral(AndroidManifestKeys.windowSoftInputMode) { + windowSoftInputModeToInt(it) + } + + return "new com.tencent.shadow.core.runtime.PluginManifest" + + ".ActivityInfo(" + + "\"${componentMap[AndroidManifestKeys.name]}\", " + + "$themeLiteral ," + + "$configChangesLiteral ," + + softInputModeLiteral + + ")" + } + + private fun toNewServiceInfo(componentMap: ComponentMap): String { + return "new com.tencent.shadow.core.runtime.PluginManifest" + + ".ServiceInfo(\"${componentMap[AndroidManifestKeys.name]}\")" + } + + private fun toNewReceiverInfo(componentMap: ComponentMap): String { + @Suppress("UNCHECKED_CAST") + val actions = componentMap[AndroidManifestKeys.action] as List? + val actionsLiteral = + actions?.joinToString( + prefix = "new String[]{\"", + separator = "\", \"", + postfix = "\"}" + ) ?: "null" + + return "new com.tencent.shadow.core.runtime.PluginManifest" + + ".ReceiverInfo(\"${componentMap[AndroidManifestKeys.name]}\", " + + actionsLiteral + + ")" + } + + private fun toNewProviderInfo(componentMap: ComponentMap): String { + val authoritiesValue = componentMap[AndroidManifestKeys.authorities] + val authoritiesLiteral = + if (authoritiesValue != null) { + "\"${authoritiesValue}\"" + } else { + "null" + } + + return "new com.tencent.shadow.core.runtime.PluginManifest" + + ".ProviderInfo(\"${componentMap[AndroidManifestKeys.name]}\", $authoritiesLiteral)" + } + + companion object { + fun privateStaticFinalFieldBuilder(type: TypeName, fieldName: String) = FieldSpec.builder( + type, + fieldName, + Modifier.PRIVATE, + Modifier.STATIC, + Modifier.FINAL, + )!! + + fun privateStaticFinalStringFieldBuilder(fieldName: String) = + privateStaticFinalFieldBuilder( + ClassName.get(String::class.java), + fieldName, + ) + + fun privateStaticFinalIntFieldBuilder(fieldName: String) = + privateStaticFinalFieldBuilder( + TypeName.INT, + fieldName, + ) + + fun nullCodeBlock() = CodeBlock.of("null")!! + + fun themeStringToResId(manifestValue: Any, packageForR: String): String { + val resName = manifestValue as String // for example: @style/TestPluginTheme + val split = resName.split('/') + val packagePart = split[0] // for example: @style + val namePart = split[1] // for example: TestPluginTheme + + val isSystemResource = packagePart.startsWith("@android:") + val packageNameOfRClass = if (isSystemResource) { + "android" + } else { + packageForR + } + + val innerClassName = if (isSystemResource) { // for example: style + packagePart.removePrefix("@android:") + } else { + packagePart.removePrefix("@") + } + + val resIdFieldName = namePart.replace('.', '_') + return "${packageNameOfRClass}.R.${innerClassName}.${resIdFieldName}" + } + + private fun flagsToInt( + stringValue: String, + className: String, + fieldMap: (String) -> String, + ): String = + stringValue.split('|') + .map(String::trim) + .map(fieldMap) + .map { "${className}.${it}" } + .reduce { acc, i -> "$acc|$i" } + + private fun configChangesToInt(configChangesValue: String): String = + flagsToInt( + configChangesValue, + "android.content.pm.ActivityInfo" + ) { + when (it) { + "mcc" -> "CONFIG_MCC" + "mnc" -> "CONFIG_MNC" + "locale" -> "CONFIG_LOCALE" + "touchscreen" -> "CONFIG_TOUCHSCREEN" + "keyboard" -> "CONFIG_KEYBOARD" + "keyboardHidden" -> "CONFIG_KEYBOARD_HIDDEN" + "navigation" -> "CONFIG_NAVIGATION" + "orientation" -> "CONFIG_ORIENTATION" + "screenLayout" -> "CONFIG_SCREEN_LAYOUT" + "uiMode" -> "CONFIG_UI_MODE" + "screenSize" -> "CONFIG_SCREEN_SIZE" + "smallestScreenSize" -> "CONFIG_SMALLEST_SCREEN_SIZE" + "density" -> "CONFIG_DENSITY" + "layoutDirection" -> "CONFIG_LAYOUT_DIRECTION" + "colorMode" -> "CONFIG_COLOR_MODE" + "assetsPaths" -> "CONFIG_ASSETS_PATHS" + "fontScale" -> "CONFIG_FONT_SCALE" + "windowConfiguration" -> "CONFIG_WINDOW_CONFIGURATION" + else -> throw IllegalArgumentException("不认识$it") + } + } + + private fun windowSoftInputModeToInt(windowSoftInputModeValue: String): String = + flagsToInt( + windowSoftInputModeValue, + "android.view.WindowManager.LayoutParams" + ) { + when (it) { + "stateUnspecified" -> "SOFT_INPUT_STATE_UNSPECIFIED" + "stateUnchanged" -> "SOFT_INPUT_STATE_UNCHANGED" + "stateHidden" -> "SOFT_INPUT_STATE_HIDDEN" + "stateAlwaysHidden" -> "SOFT_INPUT_STATE_ALWAYS_HIDDEN" + "stateVisible" -> "SOFT_INPUT_STATE_VISIBLE" + "stateAlwaysVisible" -> "SOFT_INPUT_STATE_ALWAYS_VISIBLE" + "adjustUnspecified" -> "SOFT_INPUT_ADJUST_UNSPECIFIED" + "adjustResize" -> "SOFT_INPUT_ADJUST_RESIZE" + "adjustPan" -> "SOFT_INPUT_ADJUST_PAN" + "adjustNothing" -> "SOFT_INPUT_ADJUST_NOTHING" + "isForwardNavigation" -> "SOFT_INPUT_IS_FORWARD_NAVIGATION" + else -> throw IllegalArgumentException("不认识$it") + } + } + + private fun String.capitalize() = + replaceFirstChar { + if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() + } + } +} diff --git a/projects/sdk/core/manifest-parser/src/test/java/android/R.java b/projects/sdk/core/manifest-parser/src/test/java/android/R.java new file mode 100644 index 000000000..71954adcf --- /dev/null +++ b/projects/sdk/core/manifest-parser/src/test/java/android/R.java @@ -0,0 +1,7 @@ +package android; + +public final class R { + public static final class style { + public static final int Theme_NoTitleBar = 0x01030006; + } +} diff --git a/projects/sdk/core/manifest-parser/src/test/java/android/content/pm/ActivityInfo.java b/projects/sdk/core/manifest-parser/src/test/java/android/content/pm/ActivityInfo.java new file mode 100644 index 000000000..feade0592 --- /dev/null +++ b/projects/sdk/core/manifest-parser/src/test/java/android/content/pm/ActivityInfo.java @@ -0,0 +1,42 @@ +package android.content.pm; + +/** + * mock for test + */ +public interface ActivityInfo { + int CONFIG_MCC = 0x0001; + + int CONFIG_MNC = 0x0002; + + int CONFIG_LOCALE = 0x0004; + + int CONFIG_TOUCHSCREEN = 0x0008; + + int CONFIG_KEYBOARD = 0x0010; + + int CONFIG_KEYBOARD_HIDDEN = 0x0020; + + int CONFIG_NAVIGATION = 0x0040; + + int CONFIG_ORIENTATION = 0x0080; + + int CONFIG_SCREEN_LAYOUT = 0x0100; + + int CONFIG_UI_MODE = 0x0200; + + int CONFIG_SCREEN_SIZE = 0x0400; + + int CONFIG_SMALLEST_SCREEN_SIZE = 0x0800; + + int CONFIG_DENSITY = 0x1000; + + int CONFIG_LAYOUT_DIRECTION = 0x2000; + + int CONFIG_COLOR_MODE = 0x4000; + + int CONFIG_ASSETS_PATHS = 0x80000000; + + int CONFIG_FONT_SCALE = 0x40000000; + + int CONFIG_WINDOW_CONFIGURATION = 0x20000000; +} diff --git a/projects/sdk/core/manifest-parser/src/test/java/android/view/WindowManager.java b/projects/sdk/core/manifest-parser/src/test/java/android/view/WindowManager.java new file mode 100644 index 000000000..3e5686e33 --- /dev/null +++ b/projects/sdk/core/manifest-parser/src/test/java/android/view/WindowManager.java @@ -0,0 +1,21 @@ +package android.view; + +/** + * mock for test + */ +public interface WindowManager { + interface LayoutParams { + int SOFT_INPUT_STATE_UNSPECIFIED = 0; + int SOFT_INPUT_STATE_UNCHANGED = 1; + int SOFT_INPUT_STATE_HIDDEN = 2; + int SOFT_INPUT_STATE_ALWAYS_HIDDEN = 3; + int SOFT_INPUT_STATE_VISIBLE = 4; + int SOFT_INPUT_STATE_ALWAYS_VISIBLE = 5; + int SOFT_INPUT_MASK_ADJUST = 0xf0; + int SOFT_INPUT_ADJUST_UNSPECIFIED = 0x00; + int SOFT_INPUT_ADJUST_RESIZE = 0x10; + int SOFT_INPUT_ADJUST_PAN = 0x20; + int SOFT_INPUT_ADJUST_NOTHING = 0x30; + int SOFT_INPUT_IS_FORWARD_NAVIGATION = 0x100; + } +} diff --git a/projects/sdk/core/manifest-parser/src/test/java/com/tencent/shadow/sample/plugin/app/lib/R.java b/projects/sdk/core/manifest-parser/src/test/java/com/tencent/shadow/sample/plugin/app/lib/R.java new file mode 100644 index 000000000..9780277ca --- /dev/null +++ b/projects/sdk/core/manifest-parser/src/test/java/com/tencent/shadow/sample/plugin/app/lib/R.java @@ -0,0 +1,7 @@ +package com.tencent.shadow.sample.plugin.app.lib; + +public final class R { + public static final class style { + public static final int TestPluginTheme = 0x7f030001; + } +} diff --git a/projects/sdk/core/manifest-parser/src/test/kotlin/com/tencent/shadow/core/manifest_parser/AndroidManifestReaderTest.kt b/projects/sdk/core/manifest-parser/src/test/kotlin/com/tencent/shadow/core/manifest_parser/AndroidManifestReaderTest.kt new file mode 100644 index 000000000..6c87e61f5 --- /dev/null +++ b/projects/sdk/core/manifest-parser/src/test/kotlin/com/tencent/shadow/core/manifest_parser/AndroidManifestReaderTest.kt @@ -0,0 +1,17 @@ +package com.tencent.shadow.core.manifest_parser + +import org.junit.Assert +import org.junit.Test +import java.io.File + +class AndroidManifestReaderTest { + @Test + fun testReadXml() { + val testFile = File(javaClass.classLoader.getResource("sample-app.xml")!!.toURI()) + val androidManifest = AndroidManifestReader().read(testFile) + Assert.assertEquals("com.tencent.shadow.sample.host", androidManifest[AndroidManifestKeys.`package`]) + Assert.assertEquals("com.tencent.shadow.sample.plugin.app.lib.UseCaseApplication", androidManifest[AndroidManifestKeys.name]) + Assert.assertEquals("com.tencent.shadow.test.plugin.androidx_cases.lib.TestComponentFactory", androidManifest[AndroidManifestKeys.appComponentFactory]) + Assert.assertEquals("@android:style/Theme.NoTitleBar", androidManifest[AndroidManifestKeys.theme]) + } +} \ No newline at end of file diff --git a/projects/sdk/core/manifest-parser/src/test/kotlin/com/tencent/shadow/core/manifest_parser/PluginManifestGeneratorTest.kt b/projects/sdk/core/manifest-parser/src/test/kotlin/com/tencent/shadow/core/manifest_parser/PluginManifestGeneratorTest.kt new file mode 100644 index 000000000..f1795e39c --- /dev/null +++ b/projects/sdk/core/manifest-parser/src/test/kotlin/com/tencent/shadow/core/manifest_parser/PluginManifestGeneratorTest.kt @@ -0,0 +1,40 @@ +package com.tencent.shadow.core.manifest_parser + +import org.junit.Assert +import org.junit.Test +import java.io.File + +class PluginManifestGeneratorTest { + + @Test + fun testCompileCaseAsLittleAsPossible() { + testCompile("case_as_little_as_possible.xml", "") + } + + @Test + fun testNoAppComponentFactory() { + testCompile("noAppComponentFactory.xml", "") + } + + @Test + fun testCompileSampleApp() { + testCompile("sample-app.xml", "com.tencent.shadow.sample.plugin.app.lib") + } + + private fun testCompile(case: String, packageForR: String) { + val testFile = File(javaClass.classLoader.getResource(case)!!.toURI()) + val androidManifest = AndroidManifestReader().read(testFile) + val generator = PluginManifestGenerator(packageForR) + + val tempBuildDir = File("build", "PluginManifestGeneratorTest") + val outputDir = File(tempBuildDir, case) + println("outputDir==$outputDir") + generator.generate(androidManifest, outputDir, "test") + + val cmd = "javac -cp ../runtime/build/classes/java/main:build/classes/java/test" + + " ${outputDir.absolutePath}/test/PluginManifest.java" + val process = Runtime.getRuntime().exec(cmd) + val ret = process.waitFor() + Assert.assertEquals(cmd, 0, ret) + } +} \ No newline at end of file diff --git a/projects/sdk/core/manifest-parser/src/test/resources/case_as_little_as_possible.xml b/projects/sdk/core/manifest-parser/src/test/resources/case_as_little_as_possible.xml new file mode 100644 index 000000000..b5308d2f4 --- /dev/null +++ b/projects/sdk/core/manifest-parser/src/test/resources/case_as_little_as_possible.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/projects/sdk/core/manifest-parser/src/test/resources/noAppComponentFactory.xml b/projects/sdk/core/manifest-parser/src/test/resources/noAppComponentFactory.xml new file mode 100644 index 000000000..f5b0162c2 --- /dev/null +++ b/projects/sdk/core/manifest-parser/src/test/resources/noAppComponentFactory.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/projects/sdk/core/manifest-parser/src/test/resources/sample-app.xml b/projects/sdk/core/manifest-parser/src/test/resources/sample-app.xml new file mode 100644 index 000000000..ac4789f54 --- /dev/null +++ b/projects/sdk/core/manifest-parser/src/test/resources/sample-app.xml @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + /> + + + + + + + + + + + + + + + + + + + + + + + diff --git a/projects/sdk/core/runtime/src/main/java/com/tencent/shadow/core/runtime/PackageManagerInvokeRedirect.java b/projects/sdk/core/runtime/src/main/java/com/tencent/shadow/core/runtime/PackageManagerInvokeRedirect.java index dd28855c3..02beae194 100644 --- a/projects/sdk/core/runtime/src/main/java/com/tencent/shadow/core/runtime/PackageManagerInvokeRedirect.java +++ b/projects/sdk/core/runtime/src/main/java/com/tencent/shadow/core/runtime/PackageManagerInvokeRedirect.java @@ -40,7 +40,7 @@ */ public class PackageManagerInvokeRedirect { - private static PluginPackageManager getPluginPackageManager(ClassLoader classLoaderOfInvokeCode) { + public static PluginPackageManager getPluginPackageManager(ClassLoader classLoaderOfInvokeCode) { return PluginPartInfoManager.getPluginInfo(classLoaderOfInvokeCode).packageManager; } diff --git a/projects/sdk/core/runtime/src/main/java/com/tencent/shadow/core/runtime/PluginManifest.java b/projects/sdk/core/runtime/src/main/java/com/tencent/shadow/core/runtime/PluginManifest.java new file mode 100644 index 000000000..a4bfb5972 --- /dev/null +++ b/projects/sdk/core/runtime/src/main/java/com/tencent/shadow/core/runtime/PluginManifest.java @@ -0,0 +1,142 @@ +package com.tencent.shadow.core.runtime; + +import android.os.Parcel; +import android.os.Parcelable; + +public interface PluginManifest { + /** + * same as android.content.pm.PackageItemInfo#packageName + */ + String getApplicationPackageName(); + + /** + * same as android.content.pm.ApplicationInfo#className + */ + String getApplicationClassName(); + + /** + * same as android.content.pm.ApplicationInfo#appComponentFactory + */ + String getAppComponentFactory(); + + /** + * same as android.content.pm.ApplicationInfo#theme + */ + int getApplicationTheme(); + + ActivityInfo[] getActivities(); + + ServiceInfo[] getServices(); + + ReceiverInfo[] getReceivers(); + + ProviderInfo[] getProviders(); + + abstract class ComponentInfo implements Parcelable { + public final String className; + + public ComponentInfo(String className) { + this.className = className; + } + + protected ComponentInfo(Parcel in) { + className = in.readString(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(className); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Creator CREATOR = new Creator() { + @Override + public ComponentInfo createFromParcel(Parcel in) { + throw new UnsupportedOperationException(); + } + + @Override + public ComponentInfo[] newArray(int size) { + return new ComponentInfo[size]; + } + }; + } + + final class ActivityInfo extends ComponentInfo implements Parcelable { + public final int theme; + public final int configChanges; + public final int softInputMode; + + public ActivityInfo(String className, + int theme, + int configChanges, + int softInputMode) { + super(className); + this.theme = theme; + this.configChanges = configChanges; + this.softInputMode = softInputMode; + } + + protected ActivityInfo(Parcel in) { + super(in); + theme = in.readInt(); + configChanges = in.readInt(); + softInputMode = in.readInt(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeInt(theme); + dest.writeInt(configChanges); + dest.writeInt(softInputMode); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Creator CREATOR = new Creator() { + @Override + public ActivityInfo createFromParcel(Parcel in) { + return new ActivityInfo(in); + } + + @Override + public ActivityInfo[] newArray(int size) { + return new ActivityInfo[size]; + } + }; + } + + final class ServiceInfo extends ComponentInfo { + + public ServiceInfo(String className) { + super(className); + } + } + + final class ReceiverInfo extends ComponentInfo { + public final String[] actions; + + public ReceiverInfo(String className, String[] actions) { + super(className); + this.actions = actions; + } + } + + final class ProviderInfo extends ComponentInfo { + public final String authorities; + + public ProviderInfo(String className, String authorities) { + super(className); + this.authorities = authorities; + } + } + +} diff --git a/projects/sdk/core/runtime/src/main/java/com/tencent/shadow/core/runtime/ShadowApplication.java b/projects/sdk/core/runtime/src/main/java/com/tencent/shadow/core/runtime/ShadowApplication.java index 7e7b7ce7b..a46710252 100644 --- a/projects/sdk/core/runtime/src/main/java/com/tencent/shadow/core/runtime/ShadowApplication.java +++ b/projects/sdk/core/runtime/src/main/java/com/tencent/shadow/core/runtime/ShadowApplication.java @@ -29,6 +29,8 @@ import android.content.res.Configuration; import android.os.Build; +import java.util.Arrays; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -148,8 +150,14 @@ public void setHostApplicationContextAsBase(Context hostAppContext) { = new ShadowActivityLifecycleCallbacks.Holder(mHostApplication); } - public void setBroadcasts(Map> broadcast){ - mBroadcasts = broadcast; + public void setBroadcasts(PluginManifest.ReceiverInfo[] receiverInfos) { + Map> classNameToActions = new HashMap<>(); + if (receiverInfos != null) { + for (PluginManifest.ReceiverInfo receiverInfo : receiverInfos) { + classNameToActions.put(receiverInfo.className, Arrays.asList(receiverInfo.actions)); + } + } + mBroadcasts = classNameToActions; } public void attachBaseContext(Context base) { diff --git a/projects/sdk/core/settings.gradle b/projects/sdk/core/settings.gradle index cfcdf1214..b36ae5451 100644 --- a/projects/sdk/core/settings.gradle +++ b/projects/sdk/core/settings.gradle @@ -7,6 +7,7 @@ include 'loader', 'common', 'manager', 'manager-db-test', + 'manifest-parser', 'load-parameters', 'transform-kit', 'utils' \ No newline at end of file diff --git a/projects/test/plugin/androidx-cases/test-plugin-androidx-cases/build.gradle b/projects/test/plugin/androidx-cases/test-plugin-androidx-cases/build.gradle index d2ff1bd4c..e09ae67a7 100644 --- a/projects/test/plugin/androidx-cases/test-plugin-androidx-cases/build.gradle +++ b/projects/test/plugin/androidx-cases/test-plugin-androidx-cases/build.gradle @@ -41,6 +41,10 @@ dependencies { implementation "androidx.activity:activity:$activity_version" implementation "androidx.appcompat:appcompat:$appcompat_version" + + //Shadow Transform后业务代码会有一部分实际引用runtime中的类 + //如果不以compileOnly方式依赖,会导致其他Transform或者Proguard找不到这些类 + pluginCompileOnly 'com.tencent.shadow.core:runtime' } buildscript { diff --git a/projects/test/plugin/general-cases/test-plugin-general-cases/build.gradle b/projects/test/plugin/general-cases/test-plugin-general-cases/build.gradle index b90858c0c..9c0de39bf 100644 --- a/projects/test/plugin/general-cases/test-plugin-general-cases/build.gradle +++ b/projects/test/plugin/general-cases/test-plugin-general-cases/build.gradle @@ -49,6 +49,10 @@ dependencies { androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' androidTestImplementation 'androidx.test.espresso:espresso-remote:3.3.0' androidTestImplementation "androidx.test:runner:1.3.0" + + //Shadow Transform后业务代码会有一部分实际引用runtime中的类 + //如果不以compileOnly方式依赖,会导致其他Transform或者Proguard找不到这些类 + pluginCompileOnly 'com.tencent.shadow.core:runtime' } buildscript { diff --git a/projects/test/plugin/particular-cases/plugin-service-for-host/build.gradle b/projects/test/plugin/particular-cases/plugin-service-for-host/build.gradle index e28968788..96ffb3eca 100644 --- a/projects/test/plugin/particular-cases/plugin-service-for-host/build.gradle +++ b/projects/test/plugin/particular-cases/plugin-service-for-host/build.gradle @@ -34,6 +34,12 @@ android { } } +dependencies { + //Shadow Transform后业务代码会有一部分实际引用runtime中的类 + //如果不以compileOnly方式依赖,会导致其他Transform或者Proguard找不到这些类 + pluginCompileOnly 'com.tencent.shadow.core:runtime' +} + buildscript { repositories { if (!System.getenv().containsKey("DISABLE_TENCENT_MAVEN_MIRROR")) {