If dynamic loading is performed in the work thread after startup, what should I do if the user needs to use the class in dynamic loading DEX for page operations without loading?First, we will analyze how to solve the first problem. When using the MultiDex solution, we know that BuildTool will automatically split the code into multiple DEX packages, in addition, the configuration file can be used to control which code is put into the first DEX package, which is the packaging process of Android:
To generate multiple DEX packages, We can customize a Task in Ant or gradle to intervene in the DEX generation process in this step of generating the DEX file, thus producing multiple DEX files, it is used to intervene in the Custom tasks that generate DEX in ant and gradle:
tasks.whenTaskAdded { task -> if (task.name.startsWith('proguard') && (task.name.endsWith('Debug') || task.name.endsWith('Release'))) { task.doLast { makeDexFileAfterProguardJar(); } task.doFirst { delete ${project.buildDir}/intermediates/classes-proguard; String flavor = task.name.substring('proguard'.length(), task.name.lastIndexOf(task.name.endsWith('Debug') ? Debug : Release)); generateMainIndexKeepList(flavor.toLowerCase()); } } else if (task.name.startsWith('zipalign') && (task.name.endsWith('Debug') || task.name.endsWith('Release'))) { task.doFirst { ensureMultiDexInApk(); } }}
The previous step solved the problem of how to package multiple DEX files, So how should we decide which classes should be placed in Main DEX based on what, in Secondary DEX (the Main DEX here refers to the Classes that are automatically loaded when the android system starts the apk before the Dalvik VM of Version 2.1. dex, while Secondary DEX refers to the DEX that needs to be installed by ourselves, for example, Classes2.dex, Classes3.dex, etc.). This needs to analyze the class dependency in the Main DEX, make sure to include all the class dependencies in Main DEX. Otherwise, ClassNotFoundException will occur at startup, here, our solution is to put all the Code involved in the Service, javaser, and Provider into Main DEX, and split the Code involved in the Activity to some extent, and split the homepage Activity, Laucher Activity, and Huan The class on which the webpage Activity and city list Activity are dependent is put into Main DEX, and the Activity of level 2 and level 3 pages and the Code of Business channels are put into Secondary DEX, to reduce the maintainability and high risk caused by manual analysis of class dependencies, we have compiled a script that can automatically analyze Class dependencies, this ensures that the Main DEX contains the class and all the classes they depend on are in it, so that the script will automatically analyze all the Code involved in starting the Main DEX before packaging, ensure that Main DEX runs normally.
With the solution to the second problem, we came to the tricky third problem. If we load Secondary DEX in the background, the user clicks the interface to jump to the class interface in Secondary DEX, so ClassNotFoundException will inevitably occur. How can this problem be solved, add in all Activity jump code to determine if Secondary DEX is loaded successfully? This method is feasible, but the workload is very large. Is there a better solution? By analyzing the Activity startup process, we found that the Activity was started by ActivityThread through Instrumentation. Can we do some work in Instrumentation? By analyzing the codes ActivityThread and Instrumentation, it is found that the Instrumentation methods related to Activity initiation include execStartActivity and newActivity, in this way, we can add code logic in these methods to determine whether the Class is loaded. If it is loaded, the Activity will be started directly, if the loading is not complete, start a waiting Activity and display it to the user. Then, wait for the background Secondary DEX loading to complete in this Activity. After the loading is complete, the Activity automatically jumps to the Activity that the user actually wants to jump; in this way, on the premise that the Code is fully decoupled and every business code can be granular, we can load Secondary DEX on demand. Below are some key codes added by Instrumentation:
public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode) { ActivityResult activityResult = null; String className; if (intent.getComponent() != null) { className = intent.getComponent().getClassName(); } else { ResolveInfo resolveActivity = who.getPackageManager().resolveActivity(intent, 0); if (resolveActivity != null && resolveActivity.activityInfo != null) { className = resolveActivity.activityInfo.name; } else { className = null; } } if (!TextUtils.isEmpty(className)) { boolean shouldInterrupted = !MeituanApplication.isDexAvailable(); if (MeituanApplication.sIsDexAvailable.get() || mByPassActivityClassNameList.contains(className)) { shouldInterrupted = false; } if (shouldInterrupted) { Intent interruptedIntent = new Intent(mContext, WaitingActivity.class); activityResult = execStartActivity(who, contextThread, token, target, interruptedIntent, requestCode); } else { activityResult = execStartActivity(who, contextThread, token, target, intent, requestCode); } } else { activityResult = execStartActivity(who, contextThread, token, target, intent, requestCode); } return activityResult; } public Activity newActivity(Class
clazz, Context context, IBinder token, Application application, Intent intent, ActivityInfo info, CharSequence title, Activity parent, String id, Object lastNonConfigurationInstance) throws InstantiationException, IllegalAccessException { String className = ; Activity newActivity = null; if (intent.getComponent() != null) { className = intent.getComponent().getClassName(); } boolean shouldInterrupted = !MeituanApplication.isDexAvailable(); if (MeituanApplication.sIsDexAvailable.get() || mByPassActivityClassNameList.contains(className)) { shouldInterrupted = false; } if (shouldInterrupted) { intent = new Intent(mContext, WaitingActivity.class); newActivity = mBase.newActivity(clazz, context, token, application, intent, info, title, parent, id, lastNonConfigurationInstance); } else { newActivity = mBase.newActivity(clazz, context, token, application, intent, info, title, parent, id, lastNonConfigurationInstance); } return newActivity; }
In practical application, we also encountered another tricky problem, that is, too many fields, which were introduced by the Code organization structure we currently use, to facilitate the development of multi-business lines and multi-team concurrency and collaboration, we adopt the aar Method for development and introduce a general business aar at the bottom of the aar dependency chain, the General Service aar contains a lot of resources. When the Library resources are processed in ADT14 and later versions, the R resources of the Library are no longer static final, for details, please refer to the official instructions of google. In this way, the R in the Library cannot be inline during the final packaging, which leads to the excessive number of R fields, resulting in the need to split multiple Secondary DEX, to solve this problem, we use scripts to replace the references of R field (such as ID, Layout, Drawable) in Libray with constants during the packaging process, and then delete R. the corresponding Field in the class.
SummaryThe above is the DEX automatic unpacking solution that we evolved in the MultiDex process. In this way, we can use script control to automate the splitting of DEX, then, the Secondary DEX can be freely loaded during the runtime, which can ensure the cold start speed and reduce the memory usage during the runtime.