Preface
I wrote a recent article about itEclipse project to Android StudioThe direct reason why the company's project needs to be transferred to AS is the topic I want to write today - the number of methods exceeds the limit. I believe that most Android projects will encounter this problem.
The traditional Eclipse solution has exceeded the limit, which is toAdd
=true
, and then clean the project and recompile. However, when the number of methods increases, this method will not solve the problem. At this time, you must use the solution given by Google.MultiDex Now.
MultiDex Solution
1. If yoursminSdkVersion >= 21
, then just add it to the module:
android { defaultConfig { ... multiDexEnabled true } ... }
2. If yoursminSdkVersion < 21
, then just add it to the module:
android { defaultConfig { ... multiDexEnabled true } ... } dependencies { compile ':multidex:1.0.1' }
Then, if you have not written your own Application class, then you just need to write your own Application class and inherit MultiDexApplication; if you have written your own Application class and/or do not want the Application class to inherit MultiDexApplication, then you need to override the Application's attachBaseContext method:
@Override protected void attachBaseContext(Context base) { (context); (this); }
Problems with Google multiDex
Although Google's subcontracting solution is very simple, the effect is not that good. Google itself also enumerates the disadvantages of the subcontracting solution:
- If it is executed in the main thread, load the second dex, because the loading of the slave dex is synchronous, it will block the thread. If the second dex is too large, it may lead to ANR
- Before API Level 14, due to the Dalvik LinearAlloc bug (issue 22586, which is the LinearAlloc issue mentioned above), there is a very likely problem.
- The application uses multiedex configuration, which will cause the use of relatively large memory.
- For more complex applications, there are many library projects. Multidex may cause dex file functions between different dependencies to be called with each other, and the method cannot be found
Start Optimization
Although the official solution is simple, it also has certain limitations. For example, when the application is loaded for the first time, it will consume a lot of time because the DEX file needs to be loaded, resulting in slow startup speed, affecting the user experience, and may even trigger ANR.
Regarding the issue of loading Dex, the Meituan technical team did this: streamline the main Dex package, and then load the second Dex package asynchronously after the application is started. This is a very good idea, but it is difficult to implement. You need to write scripts to distinguish which classes should be placed in the main Dex package. In general, many third-party SDKs are used in projects, which may cause the main Dex package to be streamlined to the state we want.
Of course, in addition to this, there are solutions that are more suitable for us. The solution ideas of the WeChat development team are very interesting. They reversed many APPs and finally referred to and improved Facebook's solutions. The general idea is to open a new process to execute()
method.
The idea of the WeChat development team is relatively simple to implement. I will directly put my code (by the way, I have also optimized the startup experience~):
The attachBaseContext method in Application:
@Override protected void attachBaseContext(Context context) { (context); if ((context)) { // Determine whether it is the main process and avoid repeated execution (this, false); // Save local data and mark whether the main page has been opened (this, false); // Save local data and mark whether the loading Dex process has been closed (context); // Open the process page that loads Dex, so that our APP becomes a background process } }
The process that loads Dex:
public class LoadDexActivity extends Activity { private static final int MULTIDEX_ERROR = 0; private static final int MULTIDEX_ACTIVITY_STARTED = 1; Handler handler = new Handler() { @Override public void handleMessage(Message msg) { switch () { case MULTIDEX_ERROR: case MULTIDEX_ACTIVITY_STARTED: // Exit the current process (getApplication()); finish(); (0); break; default: break; } } }; @Override protected void onCreate(Bundle savedInstanceState) { (savedInstanceState); setContentView(.activity_loaddex); if (.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { getWindow().addFlags(.FLAG_TRANSLUCENT_STATUS); } new Thread() { @Override public void run() { Message message = (); long startTime = (); long timeout = 10 * 1000; // Load timeout try { (getApplication()); (500); // Wait for the main interface to start while (!(getApplication()) && (() - startTime) < timeout) { (200); } = MULTIDEX_ACTIVITY_STARTED; (message); } catch (Exception e) { = MULTIDEX_ERROR; (message); } } }.start(); } @Override public void onBackPressed() { //cannot backpress } }
Configuration of LoadDexActivity in Manifest:
<activity android:name=".LoadDexActivity" android:alwaysRetainTaskState="false" android:excludeFromRecents="true" android:launchMode="singleTask" android:process=":loadDex" android:screenOrientation="portrait"> </activity>
Main interface onCreate method:
@Override public void onCreate(Bundle savedInstanceState) { (savedInstanceState); ... (getApplication()); // Tell the loadDex process that the main interface has been started ... }
MultiDexUtils tool class:
public class MultiDexUtils { public static final String KEY_ACTIVITY_STARTED = "activity-started-"; public static final String KEY_LOADDEX_CLOSED = "loaddex-closed-"; /** * Is the current process the main process */ public static boolean isMainProcess(Context context) { return "com.***.***(The process name is usually the package name)".equals(getCurProcessName(context)); } /** * Settings-The main interface is already open */ public static void setMainActivityStarted(Context context) { setMainActivityStarted(context, true); } /** * Settings - Is the main interface already open */ public static void setMainActivityStarted(Context context, boolean b) { SharedPreferences sp = ("multidex", Context.MODE_MULTI_PROCESS); ().putBoolean(KEY_ACTIVITY_STARTED + getPackageInfo(context).versionCode, b).commit(); } /** * Do you need to wait for the main interface? */ public static boolean isMainActivityStarted(Context context) { SharedPreferences sp = ("multidex", Context.MODE_MULTI_PROCESS); return (KEY_ACTIVITY_STARTED + getPackageInfo(context).versionCode, false); } /** * Determine whether the loading page is closed */ public static boolean isLoadDexActivityClosed(Context context) { SharedPreferences sp = ("multidex", Context.MODE_MULTI_PROCESS); return (KEY_LOADDEX_CLOSED + getPackageInfo(context).versionCode, false); } /** * The settings loading page has been closed */ public static void setLoadDexActivityClosed(Context context) { setLoadDexActivityClosed(context, true); } /** * set up-Is the loading page closed */ public static void setLoadDexActivityClosed(Context context, boolean b) { SharedPreferences sp = ("multidex", Context.MODE_MULTI_PROCESS); ().putBoolean(KEY_LOADDEX_CLOSED + getPackageInfo(context).versionCode, b).commit(); } /** * Open the waiting page, a new process */ public static void startLoadDexActivity(Context context) { Intent intent = new Intent(); ComponentName componentName = new ComponentName("com.***.***(Package name)", ()); (componentName); (Intent.FLAG_ACTIVITY_NEW_TASK); (intent); } /** * Get the process name */ public static String getCurProcessName(Context context) { try { int pid = (); ActivityManager mActivityManager = (ActivityManager) (Context.ACTIVITY_SERVICE); for ( appProcess : ()) { if ( == pid) { return ; } } } catch (Exception e) { // ignore } return null; } /** * Get package information */ private static PackageInfo getPackageInfo(Context context) { PackageManager pm = (); try { return ((), 0); } catch ( e) { // (TAG, ()); } return new PackageInfo(); } }
Another startup optimization solution
There is also a simple startup optimization solution that can only optimize the startup experience and cannot solve the ANR problem.
When clicking the desktop icon to start the application, give a background image and set the background back to empty after the startup is completed.
1. Add theme background to the entrance activity
android:theme="@style/SplashTheme"
Added configuration:
value:
<style name="SplashTheme" parent="@android:style/"> <item name="android:background">@drawable/logo_splash</item> </style>
value-v21: (Solve the problem of excessive animation effects in version 21 and above)
<style name="SplashTheme" parent="@android:style/"> <item name="android:windowBackground">@drawable/logo_splash</item> </style>
2. Set the background back to its original state
@Override public void setTheme(int resid) { (); }
The configuration is as follows:
<style name="CustomTransparent" parent="@android:style/"> <item name="android:background">@null</item> <item name="android:windowBackground">@color/curve_floater_frameColor</item> </style>
refer to
- Actually, you don't know how tricky MultiDex is
- Introduction to Meituan Android DEX automatic unpacking and dynamic loading
- Comparison of Android splitting and loading Dex solutions
- Applications with more than 64K configuration methods
Summarize
The above is the entire content of this article. I hope that the content of this article has a certain reference value for everyone's study or work. If you have any questions, you can leave a message to communicate. Thank you for your support.