这篇文章将为大家详细讲解有关Android中方法数超限问题与启动优化的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。
前言
传统的 Eclipse 解决方法数超限的办法,就是在 project.properties
中加上 dex.force.jumbo=true
,然后清理工程重新编译。但是,当方法数越来越多,这个方法也会解决不了问题,这个时候,就要用到 Google 官方给出的方案 MultiDex 了。
MultiDex 解决方案
一、 如果你的 minSdkVersion >= 21
,那么只要在模块的 build.gradle 中添加:
android { defaultConfig { ... multiDexEnabled true } ... }
二、 如果你的 minSdkVersion < 21
,那么只要在模块的 build.gradle 中添加:
android { defaultConfig { ... multiDexEnabled true } ... } dependencies { compile 'com.android.support:multidex:1.0.1' }
然后,如果你没有写自己的 Application 类,那么你只要写上自己的 Application 类,并继承 MultiDexApplication ;如果你写过自己的 Application 类,并且/或者不希望 Application 类继承 MultiDexApplication ,那么你需要重写 Application 的 attachBaseContext 方法:
@Override protected void attachBaseContext(Context base) { super.attachBaseContext(context); Multidex.install(this); }
谷歌multiDex存在的问题
虽然谷歌的分包方案很简单,但是效果并不是那么好,谷歌本身也枚举了分包方案的缺点:
如果在主线程中执行MultiDex.install,加载second dex,因为加载从dex是同步的,会阻塞线程,second dex太大的话,有可能导致ANR
API Level 14之前,由于Dalvik LinearAlloc bug(问题22586,就是上文提到的LinearAlloc问题),很可能会出问题的
应用程序使用了multiedex配置的,会造成使用比较大的内存
对于应用程序比较复杂的,存在较多的library的项目。multidex可能会造成不同依赖项目间的dex文件函数相互调用,找不到方法
启动优化
官方的解决方案虽然简单,但是也存在一定的局限。比如,首次加载应用时,由于需要加载 DEX 文件,会消耗较多的时间,导致启动速度慢,影响用户体验,甚至很可能引发 ANR 。
针对加载 Dex 问题,美团技术团队是这样做的:精简主 Dex 包,应用启动起来后再异步加载第二个 Dex 包。这是一个很不错的想法,但是实现起来有一定的难度。需要编写脚本,区分哪些类要放在主 Dex 包中,而且一般项目中都会用到很多第三方 SDK,这很可能导致主 Dex 包的精简程度不能达到我们想要的状态。
当然,除此之外,还有更适合我们的方案,微信开发团队的解决思路就很有意思,他们逆了不少 APP,最后参考并改进了 Facebook 的解决方案。大概的思路就是,新开一个进程来执行 Multidex.install()
方法。
微信开发团队的思路实现起来也比较简单,下面直接上我的代码(顺便把启动体验也优化了~):
Application 中的 attachBaseContext 方法:
@Override protected void attachBaseContext(Context context) { super.attachBaseContext(context); if (MultiDexUtils.isMainProcess(context)) { // 判断是否是主进程,避免重复执行 MultiDexUtils.setMainActivityStarted(this, false); // 保存本地数据,标记主页面是否已经开启 MultiDexUtils.setLoadDexActivityClosed(this, false); // 保存本地数据,标记加载Dex进程是否已经关闭 MultiDexUtils.startLoadDexActivity(context); // 打开加载 Dex 的进程页面,这样我们的APP就变成后台进程了 } }
加载 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 (msg.what) { case MULTIDEX_ERROR: case MULTIDEX_ACTIVITY_STARTED: // 退出当前进程 MultiDexUtils.setLoadDexActivityClosed(getApplication()); finish(); System.exit(0); break; default: break; } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_loaddex); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); } new Thread() { @Override public void run() { Message message = handler.obtainMessage(); long startTime = System.currentTimeMillis(); long timeout = 10 * 1000; // 加载超时时间 try { MultiDex.install(getApplication()); Thread.sleep(500); // 等待主界面启动 while (!MultiDexUtils.isMainActivityStarted(getApplication()) && (System.currentTimeMillis() - startTime) < timeout) { Thread.sleep(200); } message.what = MULTIDEX_ACTIVITY_STARTED; handler.sendMessage(message); } catch (Exception e) { message.what = MULTIDEX_ERROR; handler.sendMessage(message); } } }.start(); } @Override public void onBackPressed() { //cannot backpress } }
Manifest 中 LoadDexActivity 的配置:
<activity android:name=".LoadDexActivity" android:alwaysRetainTaskState="false" android:excludeFromRecents="true" android:launchMode="singleTask" android:process=":loadDex" android:screenOrientation="portrait"> </activity>
主界面 onCreate 方法:
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... MultiDexUtils.setMainActivityStarted(getApplication()); // 告诉loadDex进程,主界面已启动 ... }
MultiDexUtils 工具类:
public class MultiDexUtils { public static final String KEY_ACTIVITY_STARTED = "activity-started-"; public static final String KEY_LOADDEX_CLOSED = "loaddex-closed-"; /** * 当前进程是否是主进程 */ public static boolean isMainProcess(Context context) { return "com.***.***(进程名一般是包名)".equals(getCurProcessName(context)); } /** * 设置-主界面已经打开 */ public static void setMainActivityStarted(Context context) { setMainActivityStarted(context, true); } /** * 设置-主界面是否已经打开 */ public static void setMainActivityStarted(Context context, boolean b) { SharedPreferences sp = context.getSharedPreferences("multidex", Context.MODE_MULTI_PROCESS); sp.edit().putBoolean(KEY_ACTIVITY_STARTED + getPackageInfo(context).versionCode, b).commit(); } /** * 是否需要等待主界面 */ public static boolean isMainActivityStarted(Context context) { SharedPreferences sp = context.getSharedPreferences("multidex", Context.MODE_MULTI_PROCESS); return sp.getBoolean(KEY_ACTIVITY_STARTED + getPackageInfo(context).versionCode, false); } /** * 判断加载页面是否关闭 */ public static boolean isLoadDexActivityClosed(Context context) { SharedPreferences sp = context.getSharedPreferences("multidex", Context.MODE_MULTI_PROCESS); return sp.getBoolean(KEY_LOADDEX_CLOSED + getPackageInfo(context).versionCode, false); } /** * 设置加载页面已经关闭 */ public static void setLoadDexActivityClosed(Context context) { setLoadDexActivityClosed(context, true); } /** * 设置-加载页面是否已经关闭 */ public static void setLoadDexActivityClosed(Context context, boolean b) { SharedPreferences sp = context.getSharedPreferences("multidex", Context.MODE_MULTI_PROCESS); sp.edit().putBoolean(KEY_LOADDEX_CLOSED + getPackageInfo(context).versionCode, b).commit(); } /** * 开启等待页面,新的进程 */ public static void startLoadDexActivity(Context context) { Intent intent = new Intent(); ComponentName componentName = new ComponentName("com.***.***(包名)", LoadDexActivity.class.getName()); intent.setComponent(componentName); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); } /** * 获取进程名 */ public static String getCurProcessName(Context context) { try { int pid = android.os.Process.myPid(); ActivityManager mActivityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); for (ActivityManager.RunningAppProcessInfo appProcess : mActivityManager.getRunningAppProcesses()) { if (appProcess.pid == pid) { return appProcess.processName; } } } catch (Exception e) { // ignore } return null; } /** * 获取包信息 */ private static PackageInfo getPackageInfo(Context context) { PackageManager pm = context.getPackageManager(); try { return pm.getPackageInfo(context.getPackageName(), 0); } catch (PackageManager.NameNotFoundException e) { // Log.i(TAG, e.getLocalizedMessage()); } return new PackageInfo(); } }
另一种启动优化方案
还有一种简单的启动优化方案,只能优化启动体验,并不能解决 ANR 问题。
在点击桌面图标启动应用时,给个背景图片,启动完成后,将背景设回空。
1.在入口 Activity 中加入主题背景
android:theme="@style/SplashTheme"
style.xml 中加入配置:
value:
<style name="SplashTheme" parent="@android:style/Theme.NoTitleBar"> <item name="android:background">@drawable/logo_splash</item> </style>
value-v21:(解决21版本及以上的过度动画效果问题)
<style name="SplashTheme" parent="@android:style/Theme.NoTitleBar.Fullscreen"> <item name="android:windowBackground">@drawable/logo_splash</item> </style>
2.将背景设回原样
@Override public void setTheme(int resid) { super.setTheme(R.style.CustomTransparent); }
style.xml 配置如下:
<style name="CustomTransparent" parent="@android:style/Theme.Translucent"> <item name="android:background">@null</item> <item name="android:windowBackground">@color/curve_floater_frameColor</item> </style>
关于“Android中方法数超限问题与启动优化的示例分析”这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,使各位可以学到更多知识,如果觉得文章不错,请把它分享出去让更多的人看到。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。