The reason
In fact, DynamicProviderSwitch is sufficient without optimization. The main purpose is to simplify the compilation process, improve the compilation speed, and as much as possible to the official process. The old scheme used a hidden API on android versions with API >=28. This may change in the new version. This optimization uses API28’s new AppComponentFactory instead of the hidden API to improve stability. When API >=28, all public apis are used.
The Provider is invoked when the application is started. If the Provider in Dynamic is not found when the application is started for the first time, an error message is reported and the application cannot be started. Qigsaw generates a proxy class xxxContentProviderProxy (or decoration class) for each DynamicProvider. When the original DynamicProvider cannot be found, the proxy class is called.
QigsawBundle does not generate any decorator classes. Instead, DynamicProviderSwitch automatically sets DynamicProvider to off (Android :enabled=”false”). When the application is started, the DynamicProvider class in the manifest is read. If the DynamicProvider class exists, start the Provider. If the DynamicProvider class does not exist, ignore it. Split Install and retry failed DynamicProvider.
DynamicProviderSwitch: 2.0.0 was at compile time to revise the compiled output of the manifest file to disable DynamicProvider. DynamicProvider is disabled by code at runtime after 2.0.0.
Dry goods
I found a tool called HookUtil a long time ago that I thought could be used to optimize DynamicProviderSwitch. I recently tried and completed it when I had time. The main principles are as follows.
api<28
When API <28, disable DynamicProvider and remove it from the startup list by HookUtil before starting the Provider.
The main code is as follows
/**
* api>=28 -> HookProviderFactory & ContentProviderProxy,
* else disable Provider.
* must call in Application#attachBaseContext
* */
fun compatDynamicProvider(base: Context) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
try {
attachContext()
disableDynamicProviders(base)
initProvider(base)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
Copy the code
api>=28
When API >=28, replace the nonexistent class with ContentProviderProxy (code from Qigsaw) via HookProviderFactory, and then proxy all behavior of the Provider via ContentProviderProxy. Each time an event is triggered, try to load the real class. If the load is successful, the request is later completed with the real class. This logic is basically the same as Qigsaw, except that Qigsaw generates the proxy class at compile time, and QigsawBundle replaces the proxy class at run time with DynamicProviderSwitch.
@RequiresApi(Build.VERSION_CODES.P)
open class HookProviderFactory : AppComponentFactory() {
private val TAG = "ProviderSwitch"
override fun instantiateProviderCompat(cl: ClassLoader, className: String): ContentProvider {
if (BuildConfig.DEBUG) Log.i(TAG, "instantiateProviderCompat $className")
val dynamicProvider = try {
Class.forName(className).name.isEmpty()
} catch (e: ClassNotFoundException) {
true
}
return if (dynamicProvider) {
if (BuildConfig.DEBUG) Log.i(TAG, "ContentProviderProxy $className")
super.instantiateProviderCompat(cl, ContentProviderProxy::class.java.name)
} else {
super.instantiateProviderCompat(cl, className)
}
}
}
Copy the code