Android开发之Universal-Image-Loader解析DisplayImageOptions的详细配置

 更新时间:2016年9月20日 19:58  点击:1793
下面来给各位介绍一个Android开发之Universal-Image-Loader解析DisplayImageOptions的详细配置,希望对各位有用.


在使用这个框架的时候,我们必须要配置一个DisplayImageOptions对象来作为ImageLoader.getInstance().displayImage()中的参数,所以很有必要讲解这个对象的配制方法。讲解完了后其实这个框架我们就会了解的比较详尽了。

1.默认的配置

DisplayImageOptions options = new DisplayImageOptions.Builder()
    .showImageOnLoading(R.drawable.ic_stub) // resource or drawable
    .showImageForEmptyUri(R.drawable.ic_empty) // resource or drawable
    .showImageOnFail(R.drawable.ic_error) // resource or drawable
    .resetViewBeforeLoading(false)  // default
    .delayBeforeLoading(1000)
    .cacheInMemory(false) // default
    .cacheOnDisk(false) // default
    .preProcessor(...)
    .postProcessor(...)
    .extraForDownloader(...)
    .considerExifParams(false) // default
    .imageScaleType(ImageScaleType.IN_SAMPLE_POWER_OF_2) // default
    .bitmapConfig(Bitmap.Config.ARGB_8888) // default
    .decodingOptions(...)
    .displayer(new SimpleBitmapDisplayer()) // default
    .handler(new Handler()) // default
    .build();

example:

/**
     * 显示图片的所有配置
     * @return
     */
    private DisplayImageOptions getWholeOptions() {
        DisplayImageOptions options = new DisplayImageOptions.Builder() 
        .showImageOnLoading(R.drawable.loading) //设置图片在下载期间显示的图片 
        .showImageForEmptyUri(R.drawable.ic_launcher)//设置图片Uri为空或是错误的时候显示的图片 
        .showImageOnFail(R.drawable.error)  //设置图片加载/解码过程中错误时候显示的图片
        .cacheInMemory(true)//设置下载的图片是否缓存在内存中 
        .cacheOnDisk(true)//设置下载的图片是否缓存在SD卡中 
        .considerExifParams(true)  //是否考虑JPEG图像EXIF参数(旋转,翻转)
        .imageScaleType(ImageScaleType.IN_SAMPLE_INT)//设置图片以如何的编码方式显示 
        .bitmapConfig(Bitmap.Config.RGB_565)//设置图片的解码类型
        //.decodingOptions(BitmapFactory.Options decodingOptions)//设置图片的解码配置 
        .delayBeforeLoading(0)//int delayInMillis为你设置的下载前的延迟时间
        //设置图片加入缓存前,对bitmap进行设置 
        //.preProcessor(BitmapProcessor preProcessor) 
        .resetViewBeforeLoading(true)//设置图片在下载前是否重置,复位 
        .displayer(new RoundedBitmapDisplayer(20))//不推荐用!!!!是否设置为圆角,弧度为多少 
        .displayer(new FadeInBitmapDisplayer(100))//是否图片加载好后渐入的动画时间,可能会出现闪动
        .build();//构建完成
       
        return options;
    }

2.一般常用的配置

    /**
     * 设置常用的设置项
     * @return
     */
    private DisplayImageOptions getSimpleOptions() {
        DisplayImageOptions options = new DisplayImageOptions.Builder() 
        .showImageOnLoading(R.drawable.loading) //设置图片在下载期间显示的图片 
        .showImageForEmptyUri(R.drawable.ic_launcher)//设置图片Uri为空或是错误的时候显示的图片 
        .showImageOnFail(R.drawable.error)  //设置图片加载/解码过程中错误时候显示的图片
        .cacheInMemory(true)//设置下载的图片是否缓存在内存中 
        .cacheOnDisk(true)//设置下载的图片是否缓存在SD卡中 
        .imageScaleType(ImageScaleType.IN_SAMPLE_INT)//设置图片以如何的编码方式显示 
        .bitmapConfig(Bitmap.Config.RGB_565)//设置图片的解码类型
        .build();//构建完成
        return options;
    }
 

注意:这里面的参数当然也是可以随意修改的,根据项目需要再定!

几点说明:

 
1).imageScaleType(ImageScaleType imageScaleType)  //设置图片的缩放方式,如:

.imageScaleType(ImageScaleType.IN_SAMPLE_INT)
 

其中,mageScaleType的选择值:
EXACTLY :图像将完全按比例缩小的目标大小
EXACTLY_STRETCHED:图片会缩放到目标大小完全
IN_SAMPLE_INT:图像将被二次采样的整数倍
IN_SAMPLE_POWER_OF_2:图片将降低2倍,直到下一减少步骤,使图像更小的目标大小
NONE:图片不会调整

 2).displayer(BitmapDisplayer displayer)  // 设置图片的显示方式,如:

.displayer(new FadeInBitmapDisplayer(100))
 

其中,displayer的选择值:

 

RoundedBitmapDisplayer(int roundPixels)设置圆角图片,不推荐!!!
FakeBitmapDisplayer()这个类什么都没做
FadeInBitmapDisplayer(int durationMillis)设置图片渐显的时间
SimpleBitmapDisplayer()正常显示一张图片

 

3.避免OOM

 

.bitmapConfig(Bitmap.Config.RGB_565) //默认是ARGB_8888,使用RGB_565会比使用ARGB_8888少消耗2倍的内

 

使用.imageScaleType(ImageScaleType.IN_SAMPLE_INT) 或imageScaleType(ImageScaleType.EXACTLY)

 

避免使用.displayer(new RoundedBitmapDisplayer(20)) //他会创建新的ARGB_8888格式的Bitmap对象;

ART 将会取代Dalvik虚拟机,因为 在Dalvik下,应用每次运行的时候,字节码都需要通过即时编译器转换为机器码,这会拖慢应用的运行效率,而在ART 环境中,应用在第一次安装的时候,字节码就会预先编译成机器码,使其成为真正的本地应用。

在最新的Google I/O大会上,Google 发布了关于Android上最新的运行时库的情况。这就是Android RunTime (ART). ART 将会取代Dalvik虚拟机,成为Android平台上Java代码的执行工具。虽然自从Android KitKat,就有了一些关于ART的消息,但是基本都是一些新闻性质的,缺乏具体技术细节方面的介绍。本文尝试综合目前已有的各种消息,以及最新放出的 Android L 预览版本的ROM的情况,对ART运行时库做个详细的分析。

 

ART 特性简介

 

和IOS,Windows,Tizen之类的移动平台直接将软件编译成能够直接运行在特定硬件平台上的本地代码不同。Android平台上的软件会被编译器首先编译成通用的“byte-code”,然后再在具体的移动设备上被转换成本地指令执行。

从Android诞生至今的十几年时间里,Dalvik从开始时非常简单的Java Byte-Code执行虚拟机,逐渐增加各种新的特性,满足应用程序对性能的需求,以及与硬件设备协同演进。这其中包括在Android 2.2版本中引入的即时编译器(JIT-Compiler), 以及随后的多线程支持,以及其他一些优化。

 

Android平台的演进

 

不过,在近两年里,Android整个生态系统的进步对Android虚拟机的需求,目前的Dalvik虚拟机的开发已经无法满足了。Dalvik 最初设计时,处理器的性能很弱,移动设备的内存空间非常有限,而且都是32位的系统。于是Google开始构建一个新的虚拟机来更好的面对未来的发展趋势。这种虚拟机的性能能够在目前的多核处理器,甚至未来的8核处理上轻松扩展,能够满足对大容量存储的支持,以及大容量内存的支持。 于是乎,ART出现了。

1 架构介绍

 

APK文件的工作流

 

首先,ART的首要设计需求就是完全兼容能在Dalvik上运行的byte-code,即dex(Dalvik executable)。这样的话,对于程序员来说,就不需要对重新编译已有的程序,直接拿APK就可以在Dalvik和ART虚拟机上运行。ART带来的最大的变化,就是使用预编译技术(Ahead-of-Time compile)取代Dalvik中的即时编译技术(Just-In-Time compile)。之前,在应用程序每次执行的时候,虚拟机需要将bytecode编译成本地码执行,而在ART中这种编译操作只需执行一次,随后对该应用程序的执行都可以通过直接执行保存下来的本地码完成。当然,这种预编译技术,需要占用额外的存储空间来存储本地码。正是因为现在移动设备的存储空间越来越大,这种技术才得以应用。

这种预编译技术使得很多原来无法执行的编译优化技术在新的Android平台上成为可能。因为代码只被编译和优化一次,因此值得花费更多的时间在这次编译上,以便进行更多的优化。Google表示,现在可以在应用程序的整体代码技术上进行更高层次的优化,因为编译器现在能够看到应用程序的整体代码,而之前的即时编译,编译器只能看到并优化应用程序中某个函数或者非常小的一部分代码。采用ART后,代码中异常检查带来的开销绝大部分可以避免,对方法和接口的调用也加快了很多。完成这部分功能的是新添加的“dex2oat”组件,用来替代Dalvik中对应的“dexopt”组件。Dalvik中的 Odex文件(优化后的dex)文件,在ART中也用ELF文件代替了。

因为ART目前编译生成ELF可执行文件,内核就可以直接对载入内存中的代码进行分页管理,这也会带来更加高效的内存管理,以及更少的内存占用。说到这里,我非常好奇内核中的KSM(Kernel same-page merging)在ART中会有什么样的影响,应该能带来不错的效果吧。我们拭目以待。

ART对续航时间的影响也是非常显著的。因为不再需要解释执行,JIT也不用在程序运行时工作,这样会直接节省CPU需要执行的指令数,因而耗电降低。

因为预编译时引入了更多分析和优化,编译的时间会变长,这是ART可能会带来的一个副作用。因此相比Dalvik虚拟机,当设备首次启动及应用程序第一次安装时,需要花费的时间更久。Google声称,这种时间上的增加并不那么恐怖。他们希望并预期日后ART上完成上述动作的时间会和目前的 Dalvik差不多,甚至更短些。

 

ART与Dalvik性能比较

 

上面的图显示,ART带来的性能提升是非常明显的。对于同样的代码,性能提升约2倍左右。Google称,将Android L最终发布的时候,可以预计的性能提升将会像Chessbench一样,有3x的加速。

2 垃圾收集:理论和实践

Android虚拟机依赖自动化的内存管理机制,即自动垃圾收集。这一Java语言编程模式的基石也是Android系统自诞生之日起,非常重要的一部分。这里向不太了解垃圾收集概念的朋友解释一下,所谓自动垃圾收集,就是说程序员在编程过程中,不需要自己负责物理内存的存储的分配和释放。只需要使用固定的模式创建你需要的变量或者对象,然后直接利用该变量或对象即可。程序的运行环境会自动在内存中分配相应的内存空间存储该变量或者对象, 并在该变量或者对象失效后,自动释放所分配的内存。这是和其他需要人工进行存储管理的较低层次语言最大的区别。自动垃圾收集的好处是,程序员不必再在编程时担心内存管理的问题,当然,这也是有代价的,那就是程序员无法控制内存何时分配和释放,因而无法在需要时进行优化(Java语言有一些编程接口可以供程序员手工优化程序,但控制方式和粒度有限).

Android曾经被Dalvik的垃圾收集机制折腾了很久。Android平台的内存普遍较小,每次应用程序需要分配内存,当堆空间(分配给应用程序的一块内存空间)不能提供如此大小的空间时,Dalvik的垃圾收集器就会启动。垃圾收集器会遍历整个堆空间,查看每一个应用程序分配的对象,并对所有可到达的对象(即还会被使用的对象)标记,并将那些没有标记的对象空间释放掉。

在Dalvik虚拟机中,垃圾收集器执行的过程将导致两次应用程序的停顿:

一是在遍历堆地址空间阶段,

另一个是标记阶段。

所谓停顿,即应用程序所有正在执行的进程将暂停。如果停顿时间过长,将会导致应用程序在渲染时出现丢帧现象,进而导致应用程序的卡顿现象,大大降低用户体验。

Google声称,在Nexus 5手机上,这种停顿的平均长度在54ms。这个停顿时间将导致平均每次垃圾收集会导致在应用程序渲染显式时丢掉4帧的。

我自己的经验和测试表明,根据应用程序的不同,停顿的时间可能会增大很多。比如,在官方的FIFA应用程序这一典型程序中,垃圾收集的停顿会非常厉害。

07-01 15:56:14.275: D/dalvikvm(30615): GCFORALLOC freed 4442K, 25% free 20183K/26856K, paused 24ms, total 24ms

07-01 15:56:16.785: I/dalvikvm-heap(30615): Grow heap (frag case) to 38.179MB for 8294416-byte allocation

07-01 15:56:17.225: I/dalvikvm-heap(30615): Grow heap (frag case) to 48.279MB for 7361296-byte allocation

07-01 15:56:17.625: I/Choreographer(30615): Skipped 35 frames! The application may be doing too much work on its main thread.

07-01 15:56:19.035: D/dalvikvm(30615): GCCONCURRENT freed 35838K, 43% free 51351K/89052K, paused 3ms+5ms, total 106ms

07-01 15:56:19.035: D/dalvikvm(30615): WAITFORCONCURRENTGC blocked 96ms

07-01 15:56:19.815: D/dalvikvm(30615): GCCONCURRENT freed 7078K, 42% free 52464K/89052K, paused 14ms+4ms, total 96ms

07-01 15:56:19.815: D/dalvikvm(30615): WAITFORCONCURRENTGC blocked 74ms

07-01 15:56:20.035: I/Choreographer(30615): Skipped 141 frames! The application may be doing too much work on its main thread.

07-01 15:56:20.275: D/dalvikvm(30615): GCFORALLOC freed 4774K, 45% free 49801K/89052K, paused 168ms, total 168ms

07-01 15:56:20.295: I/dalvikvm-heap(30615): Grow heap (frag case) to 56.900MB for 4665616-byte allocation

07-01 15:56:21.315: D/dalvikvm(30615): GCFORALLOC freed 1359K, 42% free 55045K/93612K, paused 95ms, total 95ms

07-01 15:56:21.965: D/dalvikvm(30615): GCCONCURRENT freed 6376K, 40% free 56861K/93612K, paused 16ms+8ms, total 126ms

07-01 15:56:21.965: D/dalvikvm(30615): WAITFORCONCURRENTGC blocked 111ms

07-01 15:56:21.965: D/dalvikvm(30615): WAITFORCONCURRENTGC blocked 97ms

07-01 15:56:22.085: I/Choreographer(30615): Skipped 38 frames! The application may be doing too much work on its main thread.

07-01 15:56:22.195: D/dalvikvm(30615): GCFORALLOC freed 1539K, 40% free 56833K/93612K, paused 87ms, total 87ms

07-01 15:56:22.195: I/dalvikvm-heap(30615): Grow heap (frag case) to 60.588MB for 1331732-byte allocation

07-01 15:56:22.475: D/dalvikvm(30615): GCFORALLOC freed 308K, 39% free 59497K/96216K, paused 84ms, total 84ms

07-01 15:56:22.815: D/dalvikvm(30615): GCFORALLOC freed 287K, 38% free 60878K/97516K, paused 95ms, total 95ms

上面的log是从FIFA应用程序运行后的几秒钟时间里截取的。垃圾收集器在短短的8秒内被执行了9次,导致应用程序总共卡顿了603ms,丢帧达214次。绝大多数的卡顿都来自内存分配请求,在log中以”GC_FOR_ALLOC“标签描述。

ART将整个垃圾收集系统做了重新设计和实现。为了能做些对比,下面给出使用ART运行相同的应用程序,在相同的场景下提取的log:

07-01 16:00:44.531: I/art(198): Explicit concurrent mark sweep GC freed 700(30KB) AllocSpace objects, 0(0B) LOS objects, 792% free, 18MB/21MB, paused 186us total 12.763ms

07-01 16:00:44.545: I/art(198): Explicit concurrent mark sweep GC freed 7(240B) AllocSpace objects, 0(0B) LOS objects, 792% free, 18MB/21MB, paused 198us total 9.465ms

07-01 16:00:44.554: I/art(198): Explicit concurrent mark sweep GC freed 5(160B) AllocSpace objects, 0(0B) LOS objects, 792% free, 18MB/21MB, paused 224us total 9.045ms

07-01 16:00:44.690: I/art(801): Explicit concurrent mark sweep GC freed 65595(3MB) AllocSpace objects, 9(4MB) LOS objects, 810% free, 38MB/58MB, paused 1.195ms total 87.219ms

07-01 16:00:46.517: I/art(29197): Background partial concurrent mark sweep GC freed 74626(3MB) AllocSpace objects, 39(4MB) LOS objects, 1496% free, 25MB/32MB, paused 4.422ms total 1.371747s

07-01 16:00:48.534: I/Choreographer(29197): Skipped 30 frames! The application may be doing too much work on its main thread.

07-01 16:00:48.566: I/art(29197): Background sticky concurrent mark sweep GC freed 70319(3MB) AllocSpace objects, 59(5MB) LOS objects, 825% free, 49MB/56MB, paused 6.139ms total 52.868ms

07-01 16:00:49.282: I/Choreographer(29197): Skipped 33 frames! The application may be doing too much work on its main thread.

07-01 16:00:49.652: I/art(1287): Heap transition to ProcessStateJankImperceptible took 45.636146ms saved at least 723KB

07-01 16:00:49.660: I/art(1256): Heap transition to ProcessStateJankImperceptible took 52.650677ms saved at least 966KB

ART和Dalvik的差别非常大,新的运行时内存管理仅仅停顿了12.364ms,运行了4次前台垃圾收集,以及2次后台垃圾收集。在应用程序执行的过程中,应用程序的堆空间大小并没有增加,而Dalvik虚拟机中堆空间共增加了4次。丢帧的个数方面,ART虚拟机也降到了63帧。

上面这段示例,只不过是一个开发并不完善的应用程序中最坏的一个场景。因为即使在ART虚拟机中,这个应用程序还是丢掉了不少帧渲染图像。不过上面的log对比依然很有参考价值,毕竟牛逼的程序员没几个,大多数的Android程序都没办法开发的很完美。Android需要能hold住这种情况。

ART将一些通常需要垃圾收集器做的工作,拆分给应用程序本身完成。这样,Dalvik中因为遍历堆空间引入的第一次停顿,就被完全消除了。而第二次停顿也因为一项预清理技术 (packard pre-cleaning)的应用而大大缩短。使用该技术后,只需要在清理完成后,简单的检查和验证时稍微停顿一下即可。Google声称,他们已经设法将这类停顿的时间缩短到3ms左右,相比Dalvik虚拟机的垃圾收集器来说,基本上是一个多数量级的降低,很不错了。

 

垃圾收集性能对比

 

ART还引入了一个特殊的超大对象存储空间(large object space,LOS),这个空间与堆空间是分开的,不过仍然驻留在应用程序内存空间中。这一特殊的设计是为了让ART可以更好的管理较大的对象,比如位图对象(bitmaps)。在对堆空间分段时,这种较大的对象会带来一些问题。比如,在分配一个此类对象时,相比其他普通对象,会导致垃圾收集器启动的次数增加很多。有了这个超大对象存储空间的支持,垃圾收集器因堆空间分段而引发调用次数将会大大降低,这样垃圾收集器就能做更加合理的内存分配,从而降低运行时开销。

一个很好的例子,就是运行Hangouts(环聊)应用程序时,在Dalvik虚拟机中,我们能看到数次因为分配内存,运行GC而导致的停顿。

07-01 06:37:13.481: D/dalvikvm(7403): GCEXPLICIT freed 2315K, 46% free 18483K/34016K, paused 3ms+4ms, total 40ms

07-01 06:37:13.901: D/dalvikvm(9871): GCCONCURRENT freed 3779K, 22% free 21193K/26856K, paused 3ms+3ms, total 36ms

07-01 06:37:14.041: D/dalvikvm(9871): GCFORALLOC freed 368K, 21% free 21451K/26856K, paused 25ms, total 25ms

07-01 06:37:14.041: I/dalvikvm-heap(9871): Grow heap (frag case) to 24.907MB for 147472-byte allocation

07-01 06:37:14.071: D/dalvikvm(9871): GCFORALLOC freed 4K, 20% free 22167K/27596K, paused 25ms, total 25ms

07-01 06:37:14.111: D/dalvikvm(9871): GCFORALLOC freed 9K, 19% free 23892K/29372K, paused 27ms, total 28ms

我们从所有的垃圾收集log中截取了上述一段。其中的显式(GC_EXPLICIT)和并发(GC_CONCURRENT)是垃圾收集器中比较通用的清理和维护性调用。GC_FOR_ALLOC则是在内存分配器尝试分配新的内存空间,但堆空间不够用时,调用的。上面的log中,我们能看到堆空间因为分段操作而扩充了堆空间,但仍然无法装下大对象。在整个大对象分配的过程中,停顿时间长达90ms。

相比之下,下面这段log是从Android L预览版本的ART运行log中提取的。

07-01 06:35:19.718: I/art(10844): Heap transition to ProcessStateJankPerceptible took 17.989063ms saved at least -138KB

07-01 06:35:24.171: I/art(1256): Heap transition to ProcessStateJankImperceptible took 42.936250ms saved at least 258KB

07-01 06:35:24.806: I/art(801): Explicit concurrent mark sweep GC freed 85790(3MB) AllocSpace objects, 4(10MB) LOS objects, 850% free, 35MB/56MB, paused 961us total 83.110ms

我们目前还不知道log中的”Heap Transition”表达的什么意思,不过可以猜测应该是堆空间大小重设机制。在应用程序已经运行之后,唯一的对垃圾收集器的调用仅消耗的961us。我们并没有在这段截取的log之前,发现任何对垃圾收集器的调用操作。这段log中比较有趣的,就是LOS的统计。能够看到,在LOS中有4个较大的对象,共10MB。这块内存并没有分配在堆空间内,否则应该会有类似Dalvik的提示。

ART的内存分配系统本身也被重写了。虽然ART相比Dalvik,在内存分配方面,能够带来大约25%的性能提升,不过Google显然对此不满意,因此引入了一个新的内存分配器来取代当前使用的“malloc”分配器。

这个新的内存分配器,“rosalloc”(Runs-of-Slots-Allocator)是依据多线程Java应用程序的特点而设计的。此内存分配器有更细粒度的锁机制,可以直接对独立的对象上锁,而非对整个待分配的内存空间上锁。在线程局部区域中的小对象的分配,完全可以无视锁的存在了。没有了锁的请求和释放,线程局部小对象的访问速度也就大幅提升了。

这个新的内存分配器大幅提升了内存分配的速度,加速比达到了10x。

同时,ART的垃圾回收算法也做了改进,提升了用户使用体验,避免应用程序的卡顿。这些算法在Google内部目前仍然正在开发中。近期,Google仅仅介绍了一个新算法,“Moving Garbage Collector”.核心思想是,当应用程序运行在后台时,将程序的堆空间做段合并操作。

3 64位支持

ART在设计时充分考虑了将日后可能运行的各种平台进行模块化。因此,ART提供了大量的编译器后端,用于生成目前常见的体系结构的代码,例如ARM,X86和MIPS,其中包括对ARM64, X86-64的支持,以及尚未实现的对MIPS64的支持。

 

64位Android系统的优势

 

对于ARM的64位系统带来的好处,相比很多朋友都了解了。更大的内存地址空间,普适的性能提升,以及加解密的能力和性能提升,此外还有对已有32位应用程序的兼容。

除此之外,Google还在ART中引入了引用压缩技术,来避免ART堆空间内部因为64位指针的引入导致的内存占用变大问题。其实,就是在执行时,所有的指针都采用32位表示,而非64位系统应该采用的64位指针。

 

64位平台性能提升

 

Google公开了一些ARM和X86平台上应用程序在64位和32位模式下的性能对比。这只是一些预览性质数据。X86的性能测试在Intel的 BayTrail系统上进行,对于不同的RenderScript测试程序,性能提升从2x到4.5x不等。ARM平台方面,分别在A57和A53系统上,对crypto的性能做了对比。这些数据因为都是针对非常小的例子,所以代表性不大,因此还无法代表实际应用场景的情况。

不过,Google也放出了一些有趣的数据,这些数据是在他们内部使用的系统Panorama上测试的。通过简单的从32位ABI转换为64位 ABI,能够获得13%到19%的性能提升。还有个喜人的结论,那就是ARM的Cortex A53在AArch64模式下能获得性能提升比A57核要多。

Google还声称,目前应用商店中85%的应用程序都可以直接在64位模式下运行,也就是说仅有15%的应用程序在某种程度上使用了本地代码,需要重新为64位平台编译该应用程序。这对Google来说将是一个非常大的优势。明年,当大多数芯片厂商都开始推64位片上系统的时候,从32位 Android系统到64位Android系统的的切换将会非常快。

4 结论

结合上面介绍的诸多方面,ART是Google发布的一款性能提升大杀器,并且ART也解决了多个数年来困扰Android系统的诸多问题。ART 有效地改进了多个解释执行应用程序面临的问题,也提供了一个自动化的高效的存储管理系统。对于开发者来说,许多过去需要手工添加代码解决的性能问题,现在都能被ART轻松hold住了。

这也意味着Android系统终于能够在系统平滑度,应用程序性能方面与IOS势均力敌了。对消费者来说,是件喜大普奔的事情。

Google目前仍在,而且在未来一段时间内还将大力改进ART。ART目前的状况,与6个月前已经大不相同了,预计等到Android L真正发布的时候,又会有翻天覆地的变化。前途是光明的,让我们拭目以待,翘首期盼吧。

在64位的Ubuntu上运行android sdk遇到“-bash: ./emulator: No such file or directory”的错误提示,发现是64位的Ubuntu系统上刚好缺少所依赖的32位的库。

前几天在Xen或者KVM的guest上运行Android emulator来模拟Android系统的运行。当时是使用64位的Ubuntu 12.10系统作为guest,在其中运行emulator或emulator-x86时遇到“-bash: ./emulator: No such file or directory”的错误提示,这个错误提示的有点莫名其妙,很难看明白到底发生了什么情况。后来,我发现是由于这两个二进制文件是32bit的它会依赖一些32bit的共享库文件,而该64位Ubuntu系统上刚好缺少所需的32位的库。所以,这个问题的解决方法是用“sudo apt-get install ia32-libs”命令安装32位的库。另外可以用“ldd emulator”命令查看一下,emulator执行文件说依赖的共享库有哪些,可能会出现一些是“not found”的情况,然后依次安装相应的共享库即可。

 代码如下 复制代码
linux@Ubuntu12.10:~/android-sdk-linux/tools$ ./emulator-x86 -avd test1
-bash: ./emulator-x86: No such file or directory
linux@Ubuntu12.10:~/android-sdk-linux/tools$ ./emulator -avd test1
-bash: ./emulator: No such file or directory
 
linux@Ubuntu12.10:~/android-sdk-linux/tools$ sudo apt-get install ia32-libs
 
linux@Ubuntu12.10:~/android-sdk-linux/tools$ ldd emulator
        linux-gate.so.1 =>  (0xf7776000)
        libutil.so.1 => /lib/i386-linux-gnu/libutil.so.1 (0xf7757000)
        librt.so.1 => /lib/i386-linux-gnu/librt.so.1 (0xf774e000)
        libpthread.so.0 => /lib/i386-linux-gnu/libpthread.so.0 (0xf7732000)
        libstdc++.so.6 => /usr/lib/i386-linux-gnu/libstdc++.so.6 (0xf7649000)
        libm.so.6 => /lib/i386-linux-gnu/libm.so.6 (0xf761d000)
        libgcc_s.so.1 => /lib/i386-linux-gnu/libgcc_s.so.1 (0xf75ff000)
        libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7455000)
        /lib/ld-linux.so.2 (0xf7777000)


另外,使用KVM加速的Android emulator在x86平台上用起来还是真不错的。顺手记几个命令吧,下次如果使用时,我也可以方便参考。

 代码如下 复制代码
linux@Ubuntu12.10:~/android-sdk-linux/tools$ ./android list targets
Available Android targets:
----------
............
----------
id: 3 or "android-16"
     Name: Android 4.1
     Type: Platform
     API level: 16
     Revision: 2
     Skins: HVGA, WXGA800, WSVGA, WXGA800-7in, WQVGA432, QVGA, WVGA800 (default), WVGA854, WXGA720, WQVGA400
     ABIs : armeabi-v7a, x86
 
linux@Ubuntu12.10:~/android-sdk-linux/tools$ ./android create avd -n test1 -t 3 -b x86
 
linux@Ubuntu12.10:~/android-sdk-linux/tools$ ./emulator-x86 -avd test1 -qemu -m 1024 -enable-kvm


关于Android emulator的参考参考资料:
Android SDK下载地址:http://developer.android.com/sdk/index.html#download
emulator的使用:http://developer.android.com/tools/devices/emulator.html
命令行管理AVD文件:http://developer.android.com/tools/devices/managing-avds-cmdline.html

下面来为各位介绍一个关于Android优化双缓存的图片异步加载工具(LruCache+SoftReference)例子,希望对大家有帮助.


之前在郭大神的博客看到使用LruCache算法实现图片缓存的.这里仿效他的思路,自己也写了一个. 并加入ConcurrentHashMap<String, SoftReference<Bitmap>>去实现二级缓存,因为ConcurrentHashMap是多个锁的线程安全,支持高并发.很适合这种频繁访问读取内存的操作.


下面整个思路是,使用了系统提供的LruCache类做一级缓存, 大小为运行内存的1/8,当LruCache容量要满的时候,会自动将系统移除的图片放到二级缓存中,但为了避免OOM的问题,这里将SoftReference软引用加入来,当系统快要OOM的时候会自动清除里面的图片内存,当然内存充足时就会继续保存这些二级缓存的图片.强调一点,不要用SoftReference去做一级缓存,现在的java中垃圾回收加强了对SoftReference软引用的回收机制,它只适合临时的保存一些数据缓存,并不适合长期的(相对临时而言,并不是真正的长期).


直接上代码,拿来即用哦:

/**
 * Created on 3/11/2015
 * <br>图片异步加载工具(支持本地图片加载,网络图片URL和项目内图片资源加载)
 * <br>支持双缓存: LruCache和SoftReference
 * @author Mr.Et
 *
 */
public class ImageLoadManager {
 /** 图片源类型: 文件,网络,资源ID **/
 public enum IMAGE_LOAD_TYPE
 {
  FILE_PATH,FILE_URL,FILE_RESOURCE_ID
 }
 
 private String TAG = "ImageLoadManager...";
 
 private Context context;
 
 private Set<ImageLoadTask> taskCollection;
 
 /** 最大内存 **/
 final static int maxCacheSize = (int)(Runtime.getRuntime().maxMemory() / 8);
 
 /** 建立线程安全,支持高并发的容器 **/
 private static ConcurrentHashMap<String, SoftReference<Bitmap>> currentHashmap
  = new ConcurrentHashMap<String, SoftReference<Bitmap>>();
 
 
 
 
 
 
 
 public ImageLoadManager(Context context)
 {
  super();
  this.context = context;
  taskCollection = new HashSet<ImageLoadManager.ImageLoadTask>();
 }
 
 private static LruCache<String, Bitmap> BitmapMemoryCache = new LruCache<String, Bitmap>(maxCacheSize)
 {
  @Override
  protected int sizeOf(String key, Bitmap value)
  {
   if(value != null)
   {
    return value.getByteCount();
    //return value.getRowBytes() * value.getHeight(); //旧版本的方法
   }
   else
   {
    return 0;
   }
  }
  
  //这个方法当LruCache的内存容量满的时候会调用,将oldValue的元素移除出来腾出空间给新的元素加入
  @Override
  protected void entryRemoved(boolean evicted, String key,Bitmap oldValue, Bitmap newValue)
  {
   if(oldValue != null)
   {
    // 当硬引用缓存容量已满时,会使用LRU算法将最近没有被使用的图片转入软引用缓存   
    currentHashmap.put(key, new SoftReference<Bitmap>(oldValue));
   }
  }
  
 };
 
 /**
  * 针对提供图片资源ID来显示图片的方法
  * @param loadType 图片加载类型
  * @param imageResourceID 图片资源id
  * @param imageView 显示图片的ImageView
  */
 public void setImageView(IMAGE_LOAD_TYPE loadType, int imageResourceID, ImageView imageView)
 {
  if(loadType == IMAGE_LOAD_TYPE.FILE_RESOURCE_ID)
  {
//   if(ifResourceIdExist(imageResourceID))
//   {
//    imageView.setImageResource(imageResourceID);
//    
//   }else{ //映射无法获取该图片,则显示默认图片
//    imageView.setImageResource(R.drawable.pic_default);
//   }
   try
   {
    imageView.setImageResource(imageResourceID);
    return;
   } catch (Exception e) {
    Log.e(TAG, "Can find the imageID of "+imageResourceID);
    e.printStackTrace();
   }
   //默认图片
   imageView.setImageResource(R.drawable.pic_default);
  }
 }
 
 /**
  * 针对提供图片文件链接或下载链接来显示图片的方法
  * @param loadType  图片加载类型
  * @param imageFilePath 图片文件的本地文件地址或网络URL的下载链接
  * @param imageView 显示图片的ImageView
  */
 public void setImageView(IMAGE_LOAD_TYPE loadType, String imageFilePath, ImageView imageView)
 {
  if(imageFilePath == null || imageFilePath.trim().equals(""))
  {
   imageView.setImageResource(R.drawable.pic_default);
   
  }else{
   Bitmap bitmap = getBitmapFromMemoryCache(imageFilePath);
   if(bitmap != null)
   {
    imageView.setImageBitmap(bitmap);
   }
   else
   {
    imageView.setImageResource(R.drawable.pic_default);
    ImageLoadTask task = new ImageLoadTask(loadType, imageView);
    taskCollection.add(task);
    task.execute(imageFilePath);
   }
  }
 }
 
 /**
  * 从LruCache中获取一张图片,如果不存在就返回null
  * @param key  键值可以是图片文件的filePath,可以是图片URL地址
  * @return Bitmap对象,或者null
  */
 public Bitmap getBitmapFromMemoryCache(String key)
 { 
  try
  {
   if(BitmapMemoryCache.get(key) == null)
   {
    if(currentHashmap.get(key) != null)
    {
     return currentHashmap.get(key).get();
    }
   }
   return BitmapMemoryCache.get(key);
   
  } catch (Exception e) {
   e.printStackTrace();
  }
  return BitmapMemoryCache.get(key);
 }
 
 /**
  * 将图片放入缓存
  * @param key
  * @param bitmap
  */
 private void addBitmapToCache(String key, Bitmap bitmap)
 {
  BitmapMemoryCache.put(key, bitmap);
 }
 
 
 /**
  * 图片异步加载
  * @author Mr.Et
  *
  */
 private class ImageLoadTask extends AsyncTask<String, Void, Bitmap>
 {
  private String imagePath;
  private ImageView imageView;
  private IMAGE_LOAD_TYPE loadType;
  
  public ImageLoadTask(IMAGE_LOAD_TYPE loadType , ImageView imageView)
  {
   this.loadType = loadType;
   this.imageView = imageView;
  }
  
  @Override
  protected Bitmap doInBackground(String...params)
  {
   imagePath = params[0];
   try
   {
    if(loadType == IMAGE_LOAD_TYPE.FILE_PATH)
    {
     if(new File(imagePath).exists())
     { //从本地FILE读取图片
      BitmapFactory.Options opts = new BitmapFactory.Options();
      opts.inSampleSize = 2;
      Bitmap bitmap = BitmapFactory.decodeFile(imagePath, opts);
      //将获取的新图片放入缓存
      addBitmapToCache(imagePath, bitmap);
      return bitmap;
     }
     return null;
    }
    else if(loadType == IMAGE_LOAD_TYPE.FILE_URL)
    { //从网络下载图片
     byte[] datas = getBytesOfBitMap(imagePath);
     if(datas != null)
     {
//      BitmapFactory.Options opts = new BitmapFactory.Options();
//      opts.inSampleSize = 2;
//      Bitmap bitmap = BitmapFactory.decodeByteArray(datas, 0, datas.length, opts);
      Bitmap bitmap = BitmapFactory.decodeByteArray(datas, 0, datas.length);
      addBitmapToCache(imagePath, bitmap);
      return bitmap;
     }
     return null;
    }
    
   } catch (Exception e) {
    e.printStackTrace();
    FileUtils.saveExceptionLog(e);
    //可自定义其他操作
   }
   return null;
  }
  
  @Override
  protected void onPostExecute(Bitmap bitmap)
  {
   try
   {
    if(imageView != null)
    {
     if(bitmap != null)
     {
      imageView.setImageBitmap(bitmap);
     }
     else
     {
      Log.e(TAG, "The bitmap result is null...");
     }
    }
    else
    {
     Log.e(TAG, "The imageView is null...");
     //获取图片失败时显示默认图片
     imageView.setImageResource(R.drawable.pic_default);
    }
    
   } catch (Exception e) {
    e.printStackTrace();
    FileUtils.saveExceptionLog(e);
   }
  }
  
  
 }
 
 
 /**
  * InputStream转byte[]
  * @param inStream
  * @return
  * @throws Exception
  */
 private byte[] readStream(InputStream inStream) throws Exception{ 
        ByteArrayOutputStream outStream = new ByteArrayOutputStream(); 
        byte[] buffer = new byte[2048]; 
        int len = 0; 
        while( (len=inStream.read(buffer)) != -1){ 
            outStream.write(buffer, 0, len); 
        } 
        outStream.close(); 
        inStream.close(); 
        return outStream.toByteArray(); 
    }
 
 /**
  * 获取下载图片并转为byte[]
  * @param urlStr
  * @return
  */
 private byte[] getBytesOfBitMap(String imgUrl){
  try {
   URL url = new URL(imgUrl);
   HttpURLConnection conn = (HttpURLConnection) url.openConnection();
   conn.setConnectTimeout(10 * 1000);  //10s
   conn.setReadTimeout(20 * 1000);
         conn.setRequestMethod("GET"); 
         conn.connect();
         InputStream in = conn.getInputStream();
         return readStream(in);
  } catch (IOException e) {
   e.printStackTrace();
  } catch (Exception e) {
   e.printStackTrace();
  }
  return null;
 }
 
 /**
  * 该资源ID是否有效
  * @param resourceId 资源ID
  * @return
  */
 private boolean ifResourceIdExist(int resourceId)
 {
  try
  {
   Field field = R.drawable.class.getField(String.valueOf(resourceId));
   Integer.parseInt(field.get(null).toString());
   return true;
   
  } catch (Exception e) {
   e.printStackTrace();
  }
  return false;
 }
 
 /**
  * 取消所有任务
  */
 public void cancelAllTask()
 {
  if(taskCollection != null){
   for(ImageLoadTask task : taskCollection)
   {
    task.cancel(false);
   }
  }
 }
 
 
}

In addition, 如果需要更加完美的体验,还可以加入第三级的缓存机制, 比如将图片缓存到本地的磁盘存储空间中.但是又不想这些缓存在本地的图片被其他应用扫描到或者被用户看到怎么办? 这里有几个思路, 比如将图片用加密算法转为字符串存储,或者将图片转为自定义格式的未知文件去放在隐蔽的地方(很多应用都采取了这种方式). 这个不妨自己去尝试实现哦~

本文来分享我在Ubuntu使用Android SDK开发经验的几点总结,测试环境为:Ubuntu 12.04 x86_64,ADT Bundle Linux x86_64 。

测试硬件环境:

打开了Intel VT的PC (使用KVM时需要VT支持的)

测试软件环境:

Ubuntu 12.04 x86_64

ADT Bundle Linux x86_64 (在android官网下载:https://developer.android.com/sdk/index.html )

1. 下载所需的Image和创建AVD:

可以在Eclipse(ADT)中,”Window” -> “Andorid SDK Manager”来打开SDK管理器进行下载,需要下载相应版本(如Android 4.2.2/4.3)的SDK Platform及其对应的ARM或Intel x86 Atom的系统镜像(如果要在Intel PC上使用Emulator并且要使用KVM加速,则一定需要下载Intel x86 Atom的系统镜像)。另外,由于SDK Manager是默认到google.com的一些网站下载相关的信息,如果发现有连接不上的情况(原因你懂的),可以通过”Tools”->”Options”中设置你自己的代理。

在Eclipse-ADT中,”Window” -> “Andorid Virtual Device Manager” 来打开AVD管理器,在上面可以创建自己的AVD文件,注意:如果要在Intel平台上使用KVM加速,则创建AVD时,对CPU/ABI的选择是 “Intel Atom (x86)”。

另外,如果使用一些程序时,其定义的API没有找到,就需要自己下载了;比如,当前有Android API 18 SDK,可能某个程序需要API 17,就需要下载Android 17的SDK了。

2. KVM加速:

我们都知道,运行Android Emulator时很慢的,有时慢到难以忍受。

如果在Android Emulaor中使用了KVM,则可以实现启动和运行时程序的加速,效果可能提高5~10倍的执行速度;记得用x86那种AVD。

一些命令行操作如下:

 代码如下 复制代码
jay@jay-linux:~$ sudo modprobe kvm
jay@jay-linux:~$ sudo modprobe kvm_intel
 
jay@jay-linux:~$ lsmod | grep kvm
kvm_intel             137721  0
kvm                   415550  1 kvm_intel
 
jay@jay-linux:~$ ps -ef | grep emulator | grep -v grep
jay      11749 10704 14 19:48 pts/18   00:02:18 /home/jay/adt-bundle-linux-x86_64-20130917/sdk//tools/emulator64-x86 -avd Android-4.2-x86 -scale 0.23
 
jay@jay-linux:~$ lsmod | grep kvm
kvm_intel             137721  3
kvm                   415550  1 kvm_intel
# 可看使用Emualtor后,kvm_intel模块已经有3个引用了。

3. OpenGL ES:

如果有OpenGL ES的支持,则可以在Emulator中有更好的图像处理能力,比如:可以运行一些3D的程序。

当本机缺少或ADT没找到OpenGL相关的库时,会遇到如下的错误。

 代码如下 复制代码
[2013-10-10 18:34:19 - App2] Launching a new emulator with Virtual Device 'Android-4.2-x86'
[2013-10-10 18:34:26 - Emulator] Failed to load libGL.so
[2013-10-10 18:34:26 - Emulator] error libGL.so: cannot open shared object file: No such file or directory
[2013-10-10 18:34:26 - Emulator] Failed to load libGL.so
[2013-10-10 18:34:26 - Emulator] error libGL.so: cannot open shared object file: No such file or directory
[2013-10-10 18:34:26 - Emulator] emulator: emulator window was out of view and was recentered

解决方法也很简单,一般来说都是有了库的,只是需要添加一个软连接而已;有时,库也缺少,那么就需要先安装GL相关的软件库,再建立软连接。

 代码如下 复制代码
# 如果在/usr/lib下找不到libGL相关的东西,可以用 sudo apt-get install libgl1-mesa-glx  来安装相应的软件库。
jay@jay-linux:~$ ll /usr/lib/x86_64-linux-gnu/mesa/
total 428
drwxr-xr-x  2 root root   4096 Sep  3 17:56 ./
drwxr-xr-x 54 root root  36864 Oct 10 15:07 ../
-rw-r--r--  1 root root     31 Jun 19 04:54 ld.so.conf
lrwxrwxrwx  1 root root     12 Jun 19 04:54 libGL.so.1 -> libGL.so.1.2
-rw-r--r--  1 root root 390352 Jun 19 04:55 libGL.so.1.2
# 可以看到刚好没有libGL.so,就在 /usr/lib/x86_64-linux-gnu/mesa/ 目录下,建立一个指向libGL.so.1.2的软连接libGL.so即可
# 如果没有root权限,则可以在SDK的lib目录中建立软连接亦可,如下所示:
jay@jay-linux:~$ ln -s /usr/lib/x86_64-linux-gnu/mesa/libGL.so.1.2 /home/jay/adt-bundle-linux-x86_64-20130917/sdk/tools/lib/libGL.so

[!--infotagslink--]

相关文章

  • Android子控件超出父控件的范围显示出来方法

    下面我们来看一篇关于Android子控件超出父控件的范围显示出来方法,希望这篇文章能够帮助到各位朋友,有碰到此问题的朋友可以进来看看哦。 <RelativeLayout xmlns:an...2016-10-02
  • Android开发中findViewById()函数用法与简化

    findViewById方法在android开发中是获取页面控件的值了,有没有发现我们一个页面控件多了会反复研究写findViewById呢,下面我们一起来看它的简化方法。 Android中Fin...2016-09-20
  • Android模拟器上模拟来电和短信配置

    如果我们的项目需要做来电及短信的功能,那么我们就得在Android模拟器开发这些功能,本来就来告诉我们如何在Android模拟器上模拟来电及来短信的功能。 在Android模拟...2016-09-20
  • 夜神android模拟器设置代理的方法

    夜神android模拟器如何设置代理呢?对于这个问题其实操作起来是非常的简单,下面小编来为各位详细介绍夜神android模拟器设置代理的方法,希望例子能够帮助到各位。 app...2016-09-20
  • android自定义动态设置Button样式【很常用】

    为了增强android应用的用户体验,我们可以在一些Button按钮上自定义动态的设置一些样式,比如交互时改变字体、颜色、背景图等。 今天来看一个通过重写Button来动态实...2016-09-20
  • Android WebView加载html5页面实例教程

    如果我们要在Android应用APP中加载html5页面,我们可以使用WebView,本文我们分享两个WebView加载html5页面实例应用。 实例一:WebView加载html5实现炫酷引导页面大多...2016-09-20
  • 深入理解Android中View和ViewGroup

    深入理解Android中View和ViewGroup从组成架构上看,似乎ViewGroup在View之上,View需要继承ViewGroup,但实际上不是这样的。View是基类,ViewGroup是它的子类。本教程我们深...2016-09-20
  • Android自定义WebView网络视频播放控件例子

    下面我们来看一篇关于Android自定义WebView网络视频播放控件开发例子,这个文章写得非常的不错下面给各位共享一下吧。 因为业务需要,以下代码均以Youtube网站在线视...2016-10-02
  • Android用MemoryFile文件类读写进行性能优化

    java开发的Android应用,性能一直是一个大问题,,或许是Java语言本身比较消耗内存。本文我们来谈谈Android 性能优化之MemoryFile文件读写。 Android匿名共享内存对外A...2016-09-20
  • Android设置TextView竖着显示实例

    TextView默认是横着显示了,今天我们一起来看看Android设置TextView竖着显示如何来实现吧,今天我们就一起来看看操作细节,具体的如下所示。 在开发Android程序的时候,...2016-10-02
  • android.os.BinderProxy cannot be cast to com解决办法

    本文章来给大家介绍关于android.os.BinderProxy cannot be cast to com解决办法,希望此文章对各位有帮助呀。 Android在绑定服务的时候出现java.lang.ClassCastExc...2016-09-20
  • vscode搭建STM32开发环境的详细过程

    这篇文章主要介绍了vscode搭建STM32开发环境的详细过程,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-05-02
  • Android 实现钉钉自动打卡功能

    这篇文章主要介绍了Android 实现钉钉自动打卡功能的步骤,帮助大家更好的理解和学习使用Android,感兴趣的朋友可以了解下...2021-03-15
  • Android 开发之布局细节对比:RTL模式

    下面我们来看一篇关于Android 开发之布局细节对比:RTL模式 ,希望这篇文章对各位同学会带来帮助,具体的细节如下介绍。 前言 讲真,好久没写博客了,2016都过了一半了,赶紧...2016-10-02
  • Android中使用SDcard进行文件的读取方法

    首先如果要在程序中使用sdcard进行存储,我们必须要在AndroidManifset.xml文件进行下面的权限设置: 在AndroidManifest.xml中加入访问SDCard的权限如下: <!--...2016-09-20
  • Android开发之PhoneGap打包及错误解决办法

    下面来给各位简单的介绍一下关于Android开发之PhoneGap打包及错误解决办法,希望碰到此类问题的同学可进入参考一下哦。 在我安装、配置好PhoneGap项目的所有依赖...2016-09-20
  • 安卓开发之Intent传递Object与List教程

    下面我们一起来看一篇关于 安卓开发之Intent传递Object与List的例子,希望这个例子能够为各位同学带来帮助。 Intent 不仅可以传单个的值,也可以传对象与数据集合...2016-09-20
  • 用Intel HAXM给Android模拟器Emulator加速

    Android 模拟器 Emulator 速度真心不给力,, 现在我们来介绍使用 Intel HAXM 技术为 Android 模拟器加速,使模拟器运行度与真机比肩。 周末试玩了一下在Eclipse中使...2016-09-20
  • php微信公众账号开发之五个坑(二)

    这篇文章主要为大家详细介绍了php微信公众账号开发之五个坑,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2016-10-02
  • Android判断当前屏幕是全屏还是非全屏

    在安卓开发时我碰到一个问题就是需要实现全屏,但又需要我们来判断出用户是使用了全屏或非全屏了,下面我分别找了两段代码,大家可参考。 先来看一个android屏幕全屏实...2016-09-20