Fresco内存机制(Ashmem匿名共享内存)
Fresco的内存机制
Fresco是Facebook出品的高性能图片加载库,采用了Ashmem匿名共享内存机制, 来解决图片加载中的OOM问题。这里不对Fresco做深入分析,只关注Fresco在Android Bitmap的管理上采用了哪些黑科技。
Android的内存区域
Java Heap(Dalvik Heap),这部分的内存区域是由Dalvik虚拟机管理,通过Java中 new 关键字来申请一块新内存。这块区域的内存是由GC直接管理,能够自动回收内存。这块内存的大小会受到系统限制,当内存超过APP最大可用内存时会OOM
Native Heap,这部分内存区域是在C++中申请的,它不受限于APP的最大可用内存限制,而只是受限于设备的物理可用内存限制。它的缺点在于没有自动回收机制,只能通过C++语法来释放申请的内存
Ashmem(Android匿名共享内存),这部分内存类似于Native内存区,但是它是受Android系统底层管理的,当Android系统内存不足时,会回收Ashmem区域中状态是 unpin 的对象内存块,如果不希望对象被回收,可以通过 pin 来保护一个对象
Purgeable Bitmap
Ashmem一般在应用层中是无法直接访问的,除了几个特例之外。其中之一就是 decode bitmap ,我们可以通过设置 BitmapFactory.Optinons.inPurgeable = true 来创建一个 Purgeable Bitmap ,这样decode出来的bitmap是在Ashmem内存中,GC无法自动回收它。当该Bitmap在被使用时会被 pin 住,使用完之后就 unpin ,这样系统就可以在将来某一时间释放这部分内存。
如果一个 unpinned 的bitmap在之后又要被使用,系统会运行时又将它重新decode,但是这个decode操作是发生在UI线程中的有可能会造成掉帧现象,因此改做法已经被Google废弃掉,转为鼓励使用 inBitmap 来告知bitmap解码器去尝试使用已经存在的内存区域,新解码的bitmap会尝试去使用之前那张bitmap在heap中所占据的pixel data内存区域,而不是去问内存重新申请一块区域来存放bitmap。利用这种特性,即使是上千张的图片,也只会仅仅只需要占用屏幕所能够显示的图片数量的内存大小。
但是使用 inBitmap 需要注意几个限制条件:
在SDK 11 -> 18之间,重用的bitmap大小必须是一致的,例如给inBitmap赋值的图片大小为100-100,那么新申请的bitmap必须也为100-100才能够被重用。从SDK 19开始,新申请的bitmap大小必须小于或者等于已经赋值过的bitmap大小。 新申请的bitmap与旧的bitmap必须有相同的解码格式,例如大家都是8888的,如果前面的bitmap是8888,那么就不能支持4444与565格式的bitmap了。 我们可以创建一个包含多种典型可重用bitmap的对象池,这样后续的bitmap创建都能够找到合适的“模板”去进行重用。
Pin Bitmap
为了让inPurgeable的bitmap不被自动 unpinned ,可以通过使用jni函数 AndroidBitmap_lockPixels() 函数来强制 pin bitmap ,这样我们就可以在bitmap被使用时不会被系统自动 unpinned ,从而也就避免了 unpinned 的bitmap在重新被使用时又会被重新decode而引起的掉帧问题。同样的,Android也提供了 AndroidBitmap_unlockPixels() 来让bitmap重新变为 unpinned 状态,这样系统在内存不足时就可自动回收这部分内存
参考文献
Facebook tricks for image handling in Android
Introducing Fresco: A new image library for Android
------------------------------------------分割线-----------------------------------------------------------------
Fresco (Facebook图片加载器)
Fresco是Facebook开源的一个图片加载和管理库, 同类型的开源库市面有非常多,比如Picasso, Universal Image Loader, Glide, Volley.
而Fresco的最大特点在于,图片不在Java Heap上分配内存! 对,你没看错,困扰许多开发很久的爆Java Heap抛出OutOfMemoryError的无解难题看到了曙光!
那到底Fresco都把图片存到内存的那一片区域了呢?
答案是:Ashmem,匿名共享内存. 关于Ashmem的介绍,推荐看Android大牛罗升阳的这3篇技术Blog.
1. Android系统匿名共享内存Ashmem(Anonymous Shared Memory)简要介绍和学习计划
2. Android系统匿名共享内存Ashmem(Anonymous Shared Memory)驱动程序源代码分析
3. Android系统匿名共享内存Ashmem(Anonymous Shared Memory)在进程间共享的原理分析
Ashmem:
在Android系统里面,Ashmem这个区域的内存并不属于Java Heap,也不属于Native Heap.而Ashmem的使用,又有一点像Java的垃圾回收.
当Ashmem中的某个内存空间想要被释放的时候,会通过系统调用unpin来告知, 但实际上这块内存空间的数据并没有被真正的擦除;
当Android系统发现内存吃紧时,就会把unpin的内存空间利用起来去存储所需的数据;
而被unpin的内存空间,是可以被重新pin的,如果此时的该内存空间还没有被其他人使用的话,就节省了重新往Ashmem重新写入数据的过程了.
所以,Ashmem这个工作原理是一种延迟释放.
Bitmap在Ashmem中的使用
Ashmem内存区域是不能被Java应用直接使用的,但这其中有一些例外,而Bitmap是其中一个.
BitmapFactory.Options = new BitmapFactory.Options();
options.inPurgeable = true;
Bitmap bitmap = BitmapFactory.decodeByteArray(jpeg, 0, jpeg.length, options);
Purgeable被设置成true以后,这个Bigmap就是保存在Ashmem内存区域中的,Java的垃圾回收是不能回收这篇区域的内存的.
当Android系统需要渲染这个Bitmap的时候,会调用pin,渲染完成后会调用unpin.而unpin后的内存空间表示能被其他人所使用.
如果被unpin的Bitmap需要重新渲染,系统会再次Decode这个Bitmap.而这个Decode的过程是在UI线程上完成的.所以Google后来废弃了这个pureable的参数.
后来Google提供了另外一个Flag,叫inBitmap.很遗憾的是,知道Android4.4后,这个新的Flag才得到完善.而Fresco致力于实现一个包括Android2.3以及以上的Android系统都能完美工作的图片加载管理开源库,因此Fresco放弃了使用inBitmap的解决方案.
Fresco是如何利用Ashmem去给Bitmap分配和管理内存?
上面说到的pin和unpin两个操作,对应的NDK调用是AndroidBitmap_lockPixels和unlockPixels.按照我们一惯认知,为了避免内存泄漏,这两者必须成对出现.而Fresco为了避免Bitmap再次渲染而导致的在UI线程Decode的过程,偏偏不在渲染完成后调用unlockPixels.
这做后,Fresco需要自己去管理这块内存区域,保证当这个Bitmap不再使用时,Ashmem的内存空间能被unpin.
而Fresco选择在Bitmap离开屏幕可视范围时候(onDetachWindow等时候),去做unpin.
Fresco还提供了哪些实用功能?
1. 加载和展示GIF,WebP;
2. 分别控制4个角的不同圆角;
3. 把图片切成圆形;
4. focusCrop,从指定的focus位置做类似centerCrop的scaleType;
5. 图片加载进度条,类似我们看新浪微博客户端GIF图片加载过程的那种进度,可自定义样式;
6. 点击加载失败的图片,可重新加载;
具体更多的能力提供,参考Fresco官方网页.
对比Fresco和Picasso
package com.example.garena.myapplication; import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView; import com.facebook.drawee.drawable.ScalingUtils;
import com.facebook.drawee.view.SimpleDraweeView;
import com.squareup.picasso.Picasso; import java.util.ArrayList; public class MainActivity extends ActionBarActivity {
private static final ArrayList<String> URL_LIST = new ArrayList<>();
static {
URL_LIST.add("http://upload.wikimedia.org/wikipedia/en/thumb/7/7a/Manchester_United_FC_crest.svg/1010px-Manchester_United_FC_crest.svg.png");
URL_LIST.add("http://www.newstylesports.com/wp-content/uploads/2014/09/Manchester-United-Salary-2014.jpg");
URL_LIST.add("http://www.businessofsoccer.com/wp-content/uploads/2013/06/Manchester-United-Logo-Full-HD-Wallpaper.jpg");
URL_LIST.add("http://createapk.com/project/2014/07/lightkretek/manchester-united-wallpapers/image/104267-manchester-united-wallpapers.jpg");
URL_LIST.add("http://i2.manchestereveningnews.co.uk/incoming/article339323.ece/alternates/s2197/Manchester-United.jpg");
URL_LIST.add("http://www.thedrum.com/uploads/drum_basic_article/156824/main_images/ManchesterUnitedLogo_0.png");
URL_LIST.add("http://www.manutd.com/~/media/7317FC0A0B9B4A8ABD47E29E8E47D5EA.ashx?w=2560&h=1600");
URL_LIST.add("http://niaje.com/wp-content/uploads/2015/02/manu.jpg");
URL_LIST.add("http://teamspictures.com/wp-content/uploads/2015/03/Manchester-united-Football-Club-Wallpaper.jpg");
URL_LIST.add("http://g.foolcdn.com/editorial/images/146745/manchester-united-stock_large.PNG");
URL_LIST.add("http://i2.mirror.co.uk/incoming/article4918329.ece/ALTERNATES/s1227b/Yeovil-v-Manchester-United.jpg");
URL_LIST.add("http://worldsoccertalk.com/wp-content/uploads/2014/01/manchester-united.jpg");
URL_LIST.add("http://i.dailymail.co.uk/i/pix/2014/09/03/1409773352229_wps_66_Cristiano_Ronaldo_of_Manc.jpg");
URL_LIST.add("http://i.guim.co.uk/static/w-620/h--/q-95/sys-images/Football/Pix/pictures/2014/10/5/1412513201728/Radamel-Falcao-celebrates-010.jpg");
URL_LIST.add("http://i.dailymail.co.uk/i/pix/2014/09/13/1410639943046_wps_18_MANCHESTER_ENGLAND_SEPTEM.jpg");
URL_LIST.add("http://www.manutd.com/sitecore/shell/~/media/CCE3892A29474CB7A126398691568B19.ashx?w=480&h=270&rgn=0,130,800,581");
URL_LIST.add("http://img.skysports.com/14/07/660x350/manchester-united-team-los-angeles-galaxy-la_3177246.jpg");
URL_LIST.add("http://i.dailymail.co.uk/i/pix/2014/08/02/1407019617274_wps_31_Manchester_United_defende.jpg");
URL_LIST.add("http://static.guim.co.uk/sys-images/Guardian/Pix/pictures/2013/11/27/1385582053011/Bayer-Leverkusen-v-Manche-002.jpg");
URL_LIST.add("http://www.simbasports.co.uk/wp-content/uploads/2013/08/Manchester-United.jpg");
URL_LIST.add("http://www.footballwood.com/wp-content/uploads/2014/07/Manchester-United-2014-pre-season-schedule-fixtures.jpg");
URL_LIST.add("https://lh5.googleusercontent.com/-HM991djPFX4/UTbiLMwiuNI/AAAAAAAAAFs/GFlG8v56TT0/w800-h800/Manchester%2BUnited%2Bin%2BNaruto%2BWallpaper.jpg");
} @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getSupportActionBar().hide(); ListView listView = (ListView) findViewById(R.id.fresco_list_view);
Adapter adapter = new Adapter(this, URL_LIST);
listView.setAdapter(adapter);
adapter.notifyDataSetChanged();
}private static class Adapter extends BaseAdapter {
private final ArrayList<String> mUrlList;
private final LayoutInflater mInflater;
private final Context mContext; public Adapter(Context context, ArrayList<String> urlList) {
mUrlList = urlList;
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mContext = context;
} @Override
public int getCount() {
return mUrlList.size();
} @Override
public String getItem(int position) {
return mUrlList.get(position);
} @Override
public long getItemId(int position) {
return 0;
} @Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.fresco_item, parent, false);
viewHolder = new ViewHolder(convertView);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
Uri uri = Uri.parse(getItem(position));
// viewHolder.draweeView.setImageURI(uri);
// viewHolder.normalImageView.setVisibility(View.GONE); Picasso.with(mContext).load(uri).into(viewHolder.normalImageView);
viewHolder.draweeView.setVisibility(View.GONE);
return convertView;
} private static class ViewHolder {
private final SimpleDraweeView draweeView;
private final ImageView normalImageView; public ViewHolder(View view) {
draweeView = (SimpleDraweeView) view.findViewById(R.id.fresco_image_view);
normalImageView = (ImageView) view.findViewById(R.id.normal_image_view);
}
}
}
}
上面是一个非常简单的Activity,一个ListView展示一个列表的网络图片.
Android2.3.7,Fresco:
Android2.3.7,Picasso:
Android4.4.4,Fresco:
Android4.4.4,Picasso:
Android5.1,Fresco:
Android5.1,Picasso:
从上面的对比中发现:
1. Android2.3.7,Fresco反而内存占用比Picasso高.
2. Android4.4.4,Fresco完胜Picasso,无论怎么滚动ListView展示图片,内存都是静止的一条直线.
3. Android5.1,Fresco和Picasso不相伯仲.这是因为Fresco没有使用Ashmem.
Fresco至于我的疑虑
最后,Fresco让我疑虑的一点是,粗略查看了Fresco提供的能力,貌似木有发现有类似Picasso.pauseTag和resumeTag的API.
这两个API对于scrolling的操作非常重要.当scrolling的时候,应该暂停图片的加载的线程,当滚动停止时才恢复图片加载的线程运行.
Fresco内存机制(Ashmem匿名共享内存)的更多相关文章
- Android系统匿名共享内存(Anonymous Shared Memory)C++调用接口分析
文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6939890 在Android系统中,针对移动设 ...
- Android 匿名共享内存C++接口分析
在上一篇Android 匿名共享内存C接口分析中介绍了Android系统的匿名共享内存C语言访问接口,本文在前文的基础上继续介绍Android系统的匿名共享内存提供的C++访问接口.在C++层通过引入 ...
- Android 匿名共享内存Java接口分析
在Android 匿名共享内存驱动源码分析中介绍了匿名共享内存的驱动实现过程,本文在Android匿名共享内存驱动基础上,介绍Android匿名共享内存对外Android系统的匿名共享内存子系统的主体 ...
- Android 匿名共享内存C接口分析
在Android 匿名共享内存驱动源码分析中详细分析了匿名共享内存在Linux内核空间的实现,虽然内核空间实现了匿名共享内存,但仍然需要在用户空间为用户使用匿名共享内存提供访问接口.Android系统 ...
- Android系统匿名共享内存Ashmem(Anonymous Shared Memory)在进程间共享的原理分析
文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6666491 在前面一篇文章Android系统匿 ...
- Android系统匿名共享内存Ashmem(Anonymous Shared Memory)驱动程序源代码分析
文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6664554 在上一文章Android系统匿名共 ...
- Android系统匿名共享内存(Anonymous Shared Memory)Java调用接口分析
一.Ashmem驱动程序 ~/Android/kernel/goldfish ----include ----linux ----ashmem.h ----mm ----ashmem.c 驱动程序具体 ...
- MemoryFile匿名共享内存
Android提供了一个高效的共享内存机制.如果应用中涉及到在多个进程间交换数据时使用Android提高的共享内存机制将会大大的提高效率.但是也许是出于安全考虑,在应用层使用共享内存机制将会遇到很多障 ...
- 共享内存之——system V共享内存
System V 的IPC对象有共享内存.消息队列.信号灯(量). 注意:在IPC的通信模式下,不管是共享内存.消息队列还是信号灯,每个IPC的对象都有唯一的名字,称为"键(key)&quo ...
随机推荐
- Android中一张图片加载后所占用内存大小的获取与测试
Android程序中一旦加载的图片比较多,就有可能出现Out of Memory而导致程序崩溃.这个一方面是因为Android系统本身对于每个单独的进程有内存大小的限制(有16M,64M,128M,2 ...
- 用nodejs搭建一个简单的服务器
使用nodejs搭建一个简单的服务器 nodejs优点:性能高(读写文件) 数据操作能力强 官网:www.nodejs.org 验证是否安装成功:cmd命令行中输入node -v 如果显示版本号表示安 ...
- MVC code first数据迁移 转
coptto:http://www.cnblogs.com/miro/p/4164076.html 本篇是相对独立的一篇,主要讲解不丢失数据进行数据库结构升级. 前面我们讲解EF功能时(见第三篇文章) ...
- BZOJ 2705: [SDOI2012]Longge的问题
2705: [SDOI2012]Longge的问题 Time Limit: 3 Sec Memory Limit: 128 MBSubmit: 2554 Solved: 1566[Submit][ ...
- 个人PHP开发环境的选择与搭建
入职一个多月,重新调整了一下自己电脑的开发环境,现在写出来,算是作为自己的笔记. 如果你是该文章的读者,请忍受文章内的所有小章节都没有具体的步骤. 因为平时还要打游戏(划掉),所以电脑系统一直是Win ...
- Pycharm: keyboard reference
Source: Official set ♥ Editing Ctrl + Space Basic code completion (the name of any class, method or ...
- mysql游标循环的使用
CREATE PROCEDURE `test`.`new_procedure` () BEGIN DECLARE done INT DEFAULT FALSE; -- 需要定义接收游标数据的变量 ); ...
- [转]RabbitMQ消息队列在PHP下的应用
FROM : http://www.cnblogs.com/phpinfo/p/4104551.html 参考资料: http://www.yuansir-web.com/tag/rabbitmq/ ...
- 跟我学习Storm_Storm基本架构
Storm集群类似于一个Hadoop集群. 然而你在Hadoop的运行“MapReduce job”,在Storm上你运行 “topologies”. “job”和“topologies”本身有很大的 ...
- 基于FPGA的电压表与串口通信(上)
实验原理 该实验主要为利用TLC549采集模拟信号,然后将模拟信号的数字量通过串口发送到PC上上位机进行显示,使用到的TLC549驱动模块在进阶实验已经使用到了,串口模块在基础实验也已经使用到了,本实 ...