1 facebook
facebook /notes/facebook-engineering/under-the-hood-dalvik-patch-for-facebook-for-android/10151345597798920
facebook is one of the most feature-rich apps available for android. with features like push notifications, news feed, and an embedded version of facebook messenger (a complete app in its own right) all working together in real-time, the complexity and volume of code creates technical challenges that few, if any, other android developers face–especially on older versions of the platform. (our latest apps support android versions as old as froyo–android version 2.2–which is almost three years old.)
one of these challenges is related to the way android’s runtime engine, the dalvik virtual machine, handles java methods. late last year we completed a major rebuild of our android app ( facebook /notes/facebook-engineering/under-the-hood-rebuilding-facebook-for-android/10151189598933920), which involved moving a lot of our code from javascript to java, as well as using newer abstractions that encouraged large numbers of small methods (generally considered a good programming practice). unfortunately, this caused the number of java methods in our app to drastically increase.
as we were testing, the problem first showed up as described in this bug ( code.google /p/android/issues/detail?id=22586) , which caused our app installation to fail on older android phones. during standard installation, a program called “dexopt” runs to prepare your app for the specific phone it’s being installed on. dexopt uses a fixed-size buffer (called the “linearalloc” buffer) to store information about all of the methods in your app. recent versions of android use an 8 or 16 mb buffer, but froyo and gingerbread (versions 2.2 and 2.3) only have 5 mb. because older versions of android have a relatively small buffer, our large number of methods was exceeding the buffer size and causing dexopt to crash.
after a bit of panic, we realized that we could work around this problem by breaking our app into multiple dex files, using the technique described here ( android-developers.blogspot /2011/07/custom-class-loading-in-dalvik.html), which focuses on using secondary dex files for extension modules, not core parts of the app.
however, there was no way we could break our app up this way–too many of our classes are accessed directly by the android framework. instead, we needed to inject our secondary dex files directly into the system class loader. this isn’t normally possible, but we examined the android source code and used java reflection to directly modify some of its internal structures. we were certainly glad and grateful that android is open source—otherwise, this change wouldn’t have been possible.
but as we came closer to launching our redesigned app, we ran into another problem. the linearalloc buffer doesn’t just exist in dexopt–it exists within every running android program. while dexopt uses linearalloc to to store information about all of the methods in your dex file, the running app only needs it for methods in classes that you are actually using. unfortunately, we were now using too many methods for android versions up to gingerbread, and our app was crashing shortly after startup.
there was no way to work around this with dex files since all of our classes were being loaded into one process, and we weren’t able to find any information about anyone who had faced this problem before (since it is only possible once you are already using multiple dex files, which is a difficult technique in itself). we were on our own.
we tried various techniques to reclaim space, including aggressive use of proguard and source code transformations to reduce our method count. we even built a profiler for linearalloc usage to figure out what the biggest consumers were. nothing we tried had a significant impact, and we still needed to write many more methods to support all of the rich content types in our new and improved news feed and timeline.
as it stood, the release of the much-anticipated facebook for android 2.0 was at risk. it seemed like we would have to choose between cutting significant features from the app or only shipping our new version to the newest android phones (ics and up). neither seemed acceptable. we needed a better solution.
once again, we looked to the android source code. looking at the definition of the linearalloc buffer ( github /android/platform_dalvik/blob/android-2.3.7_r1/vm/linearalloc.h#l33), we realized that if we could only increase that buffer from 5 mb to 8 mb, we would be safe!
that’s when we had the idea of using a jni extension to replace the existing buffer with a larger one. at first, this idea seemed completely insane. modifying the internals of the java class loader is one thing, but modifying the internals of the dalvik vm while it was running our code is incredibly dangerous. but as we pored over the code, analyzing all the uses of linearalloc, we began to realize that it should be safe as long as we did it at the start of our program. all we had to do was find the linearallochdr object, lock it, and replace the buffer.
finding it turned out to be the hard part. here’s where it’s stored ( github /android/platform_dalvik/blob/android-2.3.7_r1/vm/globals.h#l519), buried within the dvmglobals object, over 700 bytes from the start. searching the entire object would be risky at best, but fortunately, we had an anchor point: the vmlist object just a few bytes before. this contained a value that we could compare to the javavm pointer available through jni.
the plan was finally coming together: find the proper value for vmlist, scan the dvmglobals object to find a match, jump a few more bytes to the linearalloc header, and replace the buffer. so we built the jni extension, embedded it in our app, started it up, and…we saw the app running on a gingerbread phone for the first time in weeks.the plan had worked.
but for some reason it failed on the samsung galaxy s ii… the most popular gingerbread phone… of all time…
it seems that samsung made a small change to android that was confusing our code. other manufacturers might have done the same, so we realized we needed to make our code more robust.
manual inspection of the gsii revealed that the linearalloc buffer was only 4 bytes from where we expected it, so we adjusted our code to look a few bytes to each side if it failed to find the linearalloc buffer in the expected location. this required us to parse our process’s memory map to ensure we didn’t make any invalid memory references (which would crash the app immediately) and also build some strong heuristics to make sure we would recognize the linearalloc buffer when we found it. as a last resort, we found a (mostly) safe way to scan the entire process heap to search for the buffer.
now we had a version of the code that worked on a few popular phones–but we needed more than just a few. so we bundled our code up into a test app that would run the same procedure we were using for the facebook app, then just display a large green or red box, indicating success or failure.
we used manual testing, deviceanywhere, and a test lab that google let us borrow to run our test app on 70 different phone models, and fortunately, it worked on every single one!
we released this code with facebook for android 2.0 in december. it’s now running on hundreds of different phone models, and we have yet to find one where it doesn’t work. the great speed improvements in that release would not have been possible without this crazy hack. and needless to say, without android’s open platform, we wouldn’t have had the opportunity to ship our best version of the app. there’s a lot of opportunity for building on android, and we’re excited to keep bringing the facebook experience to more people and devices.
2 android dex分包技术
当一个app的功能越来越复杂,代码量越来越多,也许有一天便会突然遇到 下列现象 :
生成的apk在2.3以前的机器无法安装,提示install_failed_dexopt方法数量过多,编译时出错,提示:
conversion to dalvik format failed:unable to execute dex: method id not in [0, 0xffff]: 65536
出现这种问题的原因是 :
android2.3及以前版本用来执行dexopt( 用于优化dex文件 )的内存只分配了5m一个dex文件最多只支持65536个方法。
针对上述问题,也出现了诸多威尼斯人2299的解决方案,使用的最多的是插件化,即将一些独立的功能做成一个单独的apk,当打开的时候使用dexclassloader动态加载,然后使用反射机制来调用插件中的类和方法。这固然是一种解决问题的方案:但这种方案存在着以下两个问题:
插件化只适合一些比较独立的模块;必须通过反射机制去调用插件的类和方法,因此,必须搭配一套插件框架来配合使用;
由于上述问题的存在,通过不断研究,便有了dex分包的威尼斯人2299的解决方案。简单来说,其原理是将编译好的class文件拆分打包成两个dex,绕过dex方法数量的限制以及安装时的检查,在运行时再动态加载第二个dex文件中。facebook曾经遇到相似的问题,具体可参考:
facebook /notes/facebook-engineering/under-the-hood-dalvik-patch-for-facebook-for-android/10151345597798920
文中有这么一段话:
however, there was no way we could break our app up this way–too many of our classes are accessed directly by the android framework. instead, we needed to inject our secondary dex files directly into the system class loader。
文中说得比较简单,我们来完善一下该方案:除了第一个dex文件(即正常apk包唯一包含的dex文件),其它dex文件都以资源的方式放在安装包中,并在application的oncreate回调中被注入到系统的classloader。因此,对于那些在注入之前已经引用到的类(以及它们所在的jar),必须放入第一个dex文件中。
下面通过一个简单的demo来讲述dex分包方案,该方案分为两步执行:
整个demo的目录结构是这样,我打算将secondactivity,mycontainer以及dropdownview放入第二个dex包中,其它保留在第一个dex包。
整个demo的目录结构是这样,我打算将secondactivity,mycontainer以及dropdownview放入第二个dex包中,其它保留在第一个dex包。
一、编译时分包
整个编译流程如下:
除了框出来的两target,其它都是编译的标准流程。而这两个target正是我们的分包操作。 首先来看看spliteclasses target。
由于我们这里仅仅是一个demo,因此放到第二个包中的文件很少,就是上面提到的三个文件。分好包之后就要开始生成dex文件,首先打包第一个dex文件:
由这里将${classes}(该文件夹下都是要打包到第一个dex的文件)打包生成第一个dex。接着生成第二个dex,并将其打包到资资源文件中:
可以看到,此时是将${secclasses}中的文件打包生成dex,并将其加入ap文件(打包的资源文件)中。到此,分包完毕,接下来,便来分析一下如何动态将第二个dex包注入系统的classloader。
二、将dex分包注入classloader
这里谈到注入,就要谈到android的classloader体系。
由上图可以看出,在叶子节点上,我们能使用到的是dexclassloader和pathclassloader,通过查阅开发文档,我们发现他们有如下使用场景:
关于pathclassloader,文档中写到: android uses this class for its system class loader and for its application class loader(s),
由此可知,android应用就是用它来加载; 2. dexclass可以加载apk,jar,及dex文件,但pathclassloader只能加载已安装到系统中(即/data/app目录下)的apk文件。
知道了两者的使用场景,下面来分析下具体的加载原理,由上图可以看到,两个叶子节点的类都继承basedexclassloader中,而具体的类加载逻辑也在此类中:
basedexclassloader:
@override
protected class findclass(string name) throws classnotfoundexception {
list
class c = pathlist.findclass(name, suppressedexceptions);
if (c == null) {
classnotfoundexception cnfe = new classnotfoundexception("didn't find class \"" name "\" on path: " pathlist);
for (throwable t : suppressedexceptions) {
cnfe.addsuppressed(t);
}
throw cnfe;
}
return c;
}
由上述函数可知,当我们需要加载一个class时,实际是从pathlist中去需要的,查阅源码,发现pathlist是dexpathlist类的一个实例。ok,接着去分析dexpathlist类中的findclass函数,
dexpathlist:
public class findclass(string name, list
for (element element : dexelements) {
dexfile dex = element.dexfile;
if (dex != null) {
class clazz = dex.loadclassbinaryname(name, definingcontext, suppressed);
if (clazz != null) {
return clazz;
}
}
}
if (dexelementssuppressedexceptions != null) {
suppressed.addall(arrays.aslist(dexelementssuppressedexceptions));
}
return null;
}
上述函数的大致逻辑为:遍历一个装在dex文件(每个dex文件实际上是一个dexfile对象)的数组(element数组,element是一个内部类),然后依次去加载所需要的class文件,直到找到为止。
看到这里,注入的威尼斯人2299的解决方案也就浮出水面, 假如我们将第二个dex文件放入element数组中,那么在加载第二个dex包中的类时,应该可以直接找到。
带着这个假设,来完善demo。
在我们自定义的baseapplication的oncreate中,我们执行注入操作:
public string inject(string libpath) {
boolean hasbasedexclassloader = true;
try {
class.forname("dalvik.system.basedexclassloader");
} catch (classnotfoundexception e) {
hasbasedexclassloader = false;
}
if (hasbasedexclassloader) {
pathclassloader pathclassloader = (pathclassloader)sapplication.getclassloader();
dexclassloader dexclassloader = new dexclassloader(libpath, sapplication.getdir("dex", 0).getabsolutepath(), libpath, sapplication.getclassloader());
try {
object dexelements = combinearray(getdexelements(getpathlist(pathclassloader)), getdexelements(getpathlist(dexclassloader)));
object pathlist = getpathlist(pathclassloader);
setfield(pathlist, pathlist.getclass(), "dexelements", dexelements);
return "success";
} catch (throwable e) {
e.printstacktrace();
return android.util.log.getstacktracestring(e);
}
}
return "success";
}
这是注入的关键函数,分析一下这个函数:
参数libpath是第二个dex包的文件信息(包含完整路径,我们当初将其打包到了assets目录下),然后将其使用dexclassloader来加载(这里为什么必须使用dexclassloader加载,回顾以上的使用场景),然后通过反射获取pathclassloader中的dexpathlist中的element数组(已加载了第一个dex包,由系统加载),以及dexclassloader中的dexpathlist中的element数组(刚将第二个dex包加载进去),将两个element数组合并之后,再将其赋值给pathclassloader的element数组,到此,注入完毕。
现在试着启动app,并在testurlactivity(在第一个dex包中)中去启动secondactivity(在第二个dex包中),启动成功。这种方案是可行。
但是使用dex分包方案仍然有几个注意点:
由于第二个dex包是在application的oncreate中动态注入的,如果dex包过大,会使app的启动速度变慢,因此,在dex分包过程中一定要注意,第二个dex包不宜过大。由于上述第一点的限制,假如我们的app越来越臃肿和庞大,往往会采取dex分包方案和插件化方案配合使用,将一些非核心独立功能做成插件加载,核心功能再分包加载。
3 android 使用android-support-multidex解决dex超出方法数的限制问题,让你的应用不再爆棚
如有转载,请声明出处: 时之沙: blog.csdn /t12x3456 (来自时之沙的csdn博客) 随着应用不断迭代,业务线的扩展,应用越来越大(比如集成了各种第三方sdk或者公共支持的jar包,项目耦合性高,重复作用的类越来越多),相信很多人都遇到过如下的错误:
unexpected top-level exception:
java.lang.illegalargumentexception: method id not in [0, 0xffff]: 65536
at com.android.dx.merge.dexmerger$6.updateindex(dexmerger.java:501)
at com.android.dx.merge.dexmerger$idmerger.mergesorted(dexmerger.java:282)
at com.android.dx.merge.dexmerger.mergemethodids(dexmerger.java:490)
at com.android.dx.merge.dexmerger.mergedexes(dexmerger.java:167)
at com.android.dx.merge.dexmerger.merge(dexmerger.java:188)
at com.android.dx mand.dexer.main.mergelibrarydexbuffers(main.java:439)
at com.android.dx mand.dexer.main.runmonodex(main.java:287)
at com.android.dx mand.dexer.main.run(main.java:230)
at com.android.dx mand.dexer.main.main(main.java:199)
at com.android.dx mand.main.main(main.java:103)
没错,你的应用中的dex 文件方法数超过了最大值65536的上限,简单来说,应用爆棚了. 那么让我们看一下为什么会引起这种错误: 在android系统中,一个app的所有代码都在一个dex文件里面。dex是一个类似jar的存储了多有java编译字节码的归档文件。因为android系统使用dalvik虚拟机,所以需要把使用java compiler编译之后的class文件转换成dalvik能够执行的class文件。这里需要强调的是,dex和jar一样是一个归档文件,里面仍然是java代码对应的字节码文件。当android系统启动一个应用的时候,有一步是对dex进行优化,这个过程有一个专门的工具来处理,叫dexopt。dexopt的执行过程是在第一次加载dex文件的时候执行的。这个过程会生成一个odex文件,即optimised dex。执行odex的效率会比直接执行dex文件的效率要高很多。但是在早期的android系统中,dexopt有一个问题,也就是这篇文章想要说明并解决的问题。dexopt会把每一个类的方法id检索起来,存在一个链表结构里面。但是这个链表的长度是用一个short类型来保存的,导致了方法id的数目不能够超过65536个。当一个项目足够大的时候,显然这个方法数的上限是不够的。尽管在新版本的android系统中,dexopt修复了这个问题,但是我们仍然需要对低版本的android系统做兼容. 目前比较常用的方法:(1) 应用插件化,比如使用我正在参与开发的插件化框架 : github /singwhatiwanna/dynamic-load-apk ,如果有建议或者相关的问题,欢迎到github上积极参与. (2) 分割dex,多工程: 把所需要的.class文件或者是jar文件和一些源码一起编译生成一个jar文件。然后使用android sdk提供的dx工具把jar文件转成dex文件。我们可以提前对它进行odex操作,让它在被dexclassloader加载的时候,跳过dexopt的部分工作,从而加快加载的过程.(可参考facebook: facebook /notes/facebook-engineering/under-the-hood-dalvik-patch-for-facebook-for-android/10151345597798920,这里边还可以看到在2.3上动态改变linearalloc缓冲的解决思路) 这两种方法并不冲突,插件化除了解决应用爆棚,还有很多其他的优点,可以看我之前的文章,不再复述. 当然,google看来也意识到了目前应用方法数爆棚的问题, 目前在已经在api 21中提供了通用的威尼斯人2299的解决方案,那就是android-support-multidex.jar. 这个jar包最低可以支持到api 4的版本(android l及以上版本会默认支持mutidex). 让我们看一下如何应用android-support-multidex.jar(以下都以在anroid studio中的使用为例,使用eclipse开发需要安装gradle插件,其他基本上相同):
首先可以用–multi-dex配置(build.gradle)进行解决,生成的apk中将包含多个dex文件,比如classes.dex, classes2.dex. 如下,需要对build.gradle进行修改:
afterevaluate {
tasks.matching {
it.name.startswith('dex')
}.each { dx ->
if (dx.additionalparameters == null) {
dx.additionalparameters = []
}
dx.additionalparameters = '--multi-dex' // enable multidex
// optional
// dx.additionalparameters = "--main-dex-list=$projectdir/
}
}
但是默认的dalvik 类加载器只会寻找classes.dex,所以需要将它们进行合并才能使得被识别
当然,现在有了android.support.multidex.jar的支持,一切都会变得非常简单,首先我们看一下相关源码的目录,具体的原理分析我会在之后的文章中进行讲解:
android/support/multidex/buildconfig.class
android/support/multidex/multidex$v14.class
android/support/multidex/multidex$v19.class
android/support/multidex/multidex$v4.class
android/support/multidex/multidex.class
android/support/multidex/multidexapplication.class
android/support/multidex/multidexextractor$1.class
android/support/multidex/multidexextractor.class
android/support/multidex/ziputil$centraldirectory.class
android/support/multidex/ziputil.class
具体集成: 将如下配置加入工程 classpath中
repositories {
jcenter()
}
dependencies {
compile 'com.google.android:multidex:0.1'
}
multidex实现原理: apk在运行的时候,有一个dexpathlist,而multidex的源码中,会根据你的系统版本号对dexpathlist做修改,将所有的dex都添加到dexpathlist中.
接下来集成有两个步骤: 一. 从sdk\extras\android\support\multidex\library\libs 目录将android-support-multidex.jar导入工程中 二. 如果你的工程中已经含有application类,那么让它继承android.support.multidex.multidexapplication类, 如果你的application已经继承了其他类并且不想做改动,那么还有另外一种使用方式,覆写attachbasecontext()方法:
public class myapplication extends fooapplication {
@override
protected void attachbasecontext(context base) {
super.attachbasecontext(base);
multidex.install(this);
}
}
最后给出build.gradle中的完整配置:
android {
compilesdkversion 21
buildtoolsversion "21.1.0"
defaultconfig {
...
minsdkversion 14
targetsdkversion 21
...
// enabling multidex support.
multidexenabled true
}
...
}
dependencies {
compile 'com.android.support:multidex:1.0.0'
}
使用mutidex的主意事项
一. 如果你继承了mutidexapplication或者覆写了application中的attachbasecontext()方法. application类中逻辑的注意事项: application 中的静态全局变量会比mutidex的 instal()方法优先加载,所以建议避免在application类中使用静态变量引用main classes.dex文件以外dex文件中的类,可以根据如下所示的方式进行修改:
@override
public void oncreate() {
super.oncreate();
final context mcontext = this;
new runnable() {
@override
public void run() {
// put your logic here!
// use the mcontext instead of this here
}
}.run();
}
二. 虽然google解决了应用总方法数限制的问题,但并不意味着开发者可以任意扩大项目规模。multidex仍有一些限制: dex文件安装到设备的过程非常复杂,如果第二个dex文件太大,可能导致应用无响应。此时应该使用proguard减小dex文件的大小。 由于dalvik linearalloc的bug,应用可能无法在android 4.0之前的版本启动,如果你的应用要支持这些版本就要多执行测试。 同样因为dalvik linearalloc的限制,如果请求大量内存可能导致崩溃。dalvik linearalloc是一个固定大小的缓冲区。在应用的安装过程中,系统会运行一个名为dexopt的程序为该应用在当前机型中运行做准备。dexopt使用linearalloc来存储应用的方法信息。android 2.2和2.3的缓冲区只有5mb,android 4.x提高到了8mb或16mb。当方法数量过多导致超出缓冲区大小时,会造成dexopt崩溃。 multidex构建工具还不支持指定哪些类必须包含在首个dex文件中,因此可能会导致某些类库(例如某个类库需要从原生代码访问java代码)无法使用。 避免应用过大、方法过多仍然是android开发者要注意的问题。mihai parparita的开源项目dex-method-counts可以用于统计apk中每个包的方法数量。
通常开发者自己的代码很难达到这样的方法数量限制,但随着第三方类库的加入,方法数就会迅速膨胀。因此选择合适的类库对android开发者来说尤为重要。
开发者应该避免使用google guava这样的类库,它包含了13000多个方法。尽量使用专为移动应用设计的lite/android版本类库,或者使用小类库替换大类库,例如用google-gson替换jackson json。而对于google protocol buffers这样的数据交换格式,其标准实现会自动生成大量的方法。采用square wire的实现则可以很好地解决此问题。
常见问题
dexexception: library dex files are not supported in multi-dex mode,你可能会见到如下的错误:
error:execution failed for task ':app:dexdebug'.
> com.android.ide mon.internal.loggederrorexception: failed to run command:
$android_sdk/build-tools/android-4.4w/dx --dex --num-threads=4 --multi-dex
...
error code:
2
output:
unexpected top-level exception:
com.android.dex.dexexception: library dex files are not supported in multi-dex mode
at com.android.dx mand.dexer.main.runmultidex(main.java:322)
at com.android.dx mand.dexer.main.run(main.java:228)
at com.android.dx mand.dexer.main.main(main.java:199)
at com.android.dx mand.main.main(main.java:103)
对于dex 的–multi-dex 选项设置与预编译的library工程有冲突,因此如果你的应用中包含引用的lirary工程,需要将预编译设置为false:
android {
// ...
dexoptions {
predexlibraries = false
}
}
outofmemoryerror: java heap space
当运行时如果看到如下错误:
unexpected top-level error:
java.lang.outofmemoryerror: java heap space
在dexoptions中有一个字段用来增加java堆内存大小:
android {
// ...
dexoptions {
javamaxheapsize "2g"
}
}
使用eclipse的开发人员可以参考google官方文档 developer.android /tools/building/multidex.html#mdex-gradle 需要安装支持gradle构建的插件,下载地址: dist.springsource /release/tools/gradle (目前可能需要翻墙)
参考相关资料: 1. mutidex 官方文档: developer.android /reference/android/support/multidex/multidex.html 2. blog.osom.info/2014/10/multi-dex-to-rescue-from-infamous-65536.html
另附android -support-mutidex.jar下载地址: download.csdn /detail/t12x3456/8143383
补充注意事项:
com.android.dex.dexexception: multiple dex files define l{package}/buildconfig;
如果遇到这个错误请进行如下检查: 1. 主工程与依赖library工程包名是否重复 2. 检查主工程与依赖library工程是否含有重复的support.jar或者其他jar包
威尼斯人2299的解决方案: 1. 修改library工程包名 2. 删除重复jar包 3.手工添加lib包,增加如下配置
dependencies {
compile filetree(dir: 'libs', include: ['*.jar'])
compile project(':lib-project-module')
}
?
目录
安装包监控
android studio 的 apk analyser
matrix中 的 apkchecker(传送)
安装包优化实践
lint查找无用文件
启用压缩、混淆和优化功能
文件优化
图片优化
降低图片bit
使用 vectordrawable 图片
使用 webp 格式图片
三方库优化
关于 app bundle
文章推荐
???????实际开发应用时,包体积优化是必不可少的。毕竟手机内存有限,如果包体积过大很多用户会直接放弃(以前手机内存很小的时候,这个真的很重要),现在由于手机内存大了(512g已经挡不住了),现在的用户更关注流畅度和美观作为参考,但是该有的优化还是要优化的,毕竟要尽善尽美嘛。?
安装包监控
android studio 的 apk analyser
????????这是 android studio 提供的一个 apk 检测工具,通过它可以查看一个 apk 文件内部各项内容所占的大小,并且按照大小排序显示。因此我们很容易观察到 apk 中哪一部分内容占用了最大空间。apk analyzer 的使用非常简单,只要将需要分析的 apk 文件拖入 android studio 中即可(直接点击项目中的apk也可以),显示内容类似下图所示:
????????从上图中可以看出classes.dex都代码文件,不是很好东,看图片也占用了比较大的资源空间,因此可以针对性地对其做压缩优化等操作。(我这个项目是个demo所以显得代码占比较多。实际项目中肯定是图片资源占比相对较大)
????????从上图看出,实际上 apk analyzer 的作用不光是查看 apk 大小,从它的名字也能看出它是用来分析 apk 的,因此可以使用它来分析一些优秀 apk 的目录结构、代码规范,甚至是使用了哪些动态库技术等。
matrix中 的 apkchecker(传送)
????????matrix是微信终端自研和正在使用的一套apm(application performance management)系统。 matrix-apkchecker 作为matrix系统的一部分,是针对android安装包的分析检测工具,根据一系列设定好的规则检测apk是否存在特定的问题,并输出较为详细的检测结果报告,用于分析排查问题以及版本追踪。
安装包优化实践
lint查找无用文件
????????使用 lint 查看未引用资源。lint 是一个静态扫描工具,它可以识别出项目中没有被任何代码所引用到的资源文件。具体使用也很简单,只要在 android studio 中点击 analyze -> inspect code,如下所示:
选中整个项目,如下所示
如果项目中有未被使用资源,则 lint 会在窗口 inspection result 中显示,类似结果如下:
低效布局权重:提供优化方案。
????????上面就是未使用的资源:会使应用程序变大,并降低构建速度。
????????还有很多就不多介绍了,感兴趣的可以去玩玩。
启用压缩、混淆和优化功能
????????当你使用 android studio 3.4 或 android gradle 插件 3.4.0 及更高版本时,r8 是默认编译器,用于将项目的 java 字节码转换为在 android 平台上运行的 dex 格式。不过,当您使用 android studio 创建新项目时,缩减、混淆处理和代码优化功能默认处于停用状态。
????????debug{
????????????//?启用代码收缩、模糊处理和优化
????????????minifyenabled?true
????????????//?资源缩减
????????????shrinkresources?true
????????????//包括与打包在一起的默认proguard规则文件
????????????//r8配置文件。
????????????proguardfiles?getdefaultproguardfile(
????????????????????'proguard-android-optimize.txt'),
????????????????????'proguard-rules.pro'
????????}
????????未启用
????????启用后
文件优化
图片优化
降低图片bit
????????不需要太精致的图片可以将图中32 bit降至16 bit或者8 bit。
使用 vectordrawable 图片
????????ui小姐姐能提供最好不能提供,咱们自己自己造。
????????android studio 中点击?file > new > vector asset
????????clip art 项为固有的矢量图,我们直接用即可,而local file(svg,psd)选项,则是我们需要转换的了,剩下的提示进行就可以啦。
使用 webp 格式图片
????????使用webp格式的图片可以在保持清晰度的情况下减小图片的磁盘大小,是一种比较优秀的,google推荐的图片格式。
????????选中图片>右键>选择
????????图片由.png转为.webp
三方库优化
????????在 app 中会引入各种三方的库,但是在引入之前最好权衡一下是否需要将其代码全部引入,造成不必要的代码或者资源也被打包到 apk 中。
????????例如facebook全家桶,你不可能全部用到仅导入部分即可,如登入和分享
dependencies?{
????//?facebook?core?only?(analytics)
????implementation?'com.facebook.android:facebook-core:11.1.0'
????//?facebook?login?only
????implementation?'com.facebook.android:facebook-login:11.1.0'
????//?facebook?share?only
????implementation?'com.facebook.android:facebook-share:11.1.0'
????//?facebook?messenger?only
????implementation?'com.facebook.android:facebook-messenger:11.1.0'
????//?facebook?app?links?only
????implementation?'com.facebook.android:facebook-applinks:11.1.0'
????//?facebook?android?sdk?(everything)
????implementation?'com.facebook.android:facebook-android-sdk:11.1.0'
}
仅需导入
dependencies?{
????implementation?'com.facebook.android:facebook-login:11.1.0'
????implementation?'com.facebook.android:facebook-share:11.1.0'
}
????????例如xrecyclerview一个 recyclerview 实现了 pullrefresh 、loadingmore 和 header featrues。你可能仅用到 loadingmore,那你就可以将关于loadingmore部分截取出来。而不用导入整个包。
关于 app bundle
????????这个功能就跟ios一样了,他们就是将所有资源全部打到项目中,然后app store,根据安装设备的属性,来选取相应资源打包进行下载。
????????谷歌的 dynamic delivery 功能就天然地解决了这个问题,通过 google play store 安装 apk 时,也会根据安装设备的属性,只选取相应的资源打包到 apk 文件中。
????????如下图,你上传的700mb大小的aab,但是你下载的话会有两套资源打在apk中,但是用户下载仅一套资源700mb。
????????但是 app bundle 目前只适合在 google play store 上发布的项目,国内目前还是通过各家的插件化方案来实现动态部署,一定程度上也可以算作减少安装包大小的方案。
????????还有一个骚操作,就是前期资源打包,后续资源用户边玩边下载,缺点可能造成卡顿和浪费流量,仅供参考。
文章推荐
image asset studio(图标生成工具)
aab打包
给安卓全球app排名,以便了解用户最喜欢的app是一件困难的事情,这是因为,每一刻都有新app产生,每一刻也都有app死去。一个app可能已经存在了十年之久,论资排辈,资历老导致其下载基数比较大,也可能一个新app很受欢迎,但是出生晚,导致其下载量比较小。
总之完全给app排名比较困难,但是依据点评数量人数排名,还是可以一窥全球android app最受欢迎排行榜。
全球有多少app
截止2019年底,全球安装量超过1万以上的共计 ?290706个。其中,安装量在5亿-10亿间的有47款app,安装量在10亿-50亿之间的有37款app,而安装量在50亿-100亿之间的有12款app。
全球app下载排行榜-用户评论数排序
如上所述,我们认为如果一个app点评数越多,就表示这个app使用人数多,自然下载量也就是越多。因此,使用点评数更能真实反映app受欢迎的程序。
一个用户如果多次下载同一app,会导致下载量重复计算,所以使用评论数量更合理。
从下图可以看到在全球30多万app里,前20款最受欢迎app排行名单。其中脸书facebook旗下的:whatsapp(聊天软件)、facebook(社交软件)、instagram(图片软件)、messenger(聊天软件)四款软件独占鳌头。换句话说,排名1-4名全被facebook独占。谷歌的youtbue排名第5。
在前10名中,中国只有一款 clean master (猎豹的清理大师)挤进排名,下载量超过10亿。 ?在前20名里,排名第17的uc浏览器下载量超过5亿,甚至超过了谷歌的chrome,这说明uc浏览器获得了全球用户的认可。
全球app下载排行榜-下载数排序
如果以下载量排序,则前5名为:whatsapp、facebook、youtube、google play ?service和chrome。毕竟,android是谷歌研发的,所以,其能获得先发优势。
全球app下载排行榜-最近60天增长率排序
如果以最近60天增长率排行看,国产的tiktok(抖音)和kwai快手上榜,成为全球用户使用较多的短视频软件。
结论
这份统计主要针对android操作系统,并不包含苹果app,数据来源于google ?store,因为众所周知的原因,国内app下载数量可能无法反应在这份名单里。
这个榜单反应的是中国app和美国app一次在全球的正面的较量。
换句话说,不管是印度用户还是日本用户他们在选择app时,是选择wps还是office,是选择谷歌地图还是百度地图,是他们的自由,此时比的就是中美app的实力。
??
发表评论