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匿名共享内存)的更多相关文章

  1. Android系统匿名共享内存(Anonymous Shared Memory)C++调用接口分析

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6939890 在Android系统中,针对移动设 ...

  2. Android 匿名共享内存C++接口分析

    在上一篇Android 匿名共享内存C接口分析中介绍了Android系统的匿名共享内存C语言访问接口,本文在前文的基础上继续介绍Android系统的匿名共享内存提供的C++访问接口.在C++层通过引入 ...

  3. Android 匿名共享内存Java接口分析

    在Android 匿名共享内存驱动源码分析中介绍了匿名共享内存的驱动实现过程,本文在Android匿名共享内存驱动基础上,介绍Android匿名共享内存对外Android系统的匿名共享内存子系统的主体 ...

  4. Android 匿名共享内存C接口分析

    在Android 匿名共享内存驱动源码分析中详细分析了匿名共享内存在Linux内核空间的实现,虽然内核空间实现了匿名共享内存,但仍然需要在用户空间为用户使用匿名共享内存提供访问接口.Android系统 ...

  5. Android系统匿名共享内存Ashmem(Anonymous Shared Memory)在进程间共享的原理分析

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6666491 在前面一篇文章Android系统匿 ...

  6. Android系统匿名共享内存Ashmem(Anonymous Shared Memory)驱动程序源代码分析

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6664554 在上一文章Android系统匿名共 ...

  7. Android系统匿名共享内存(Anonymous Shared Memory)Java调用接口分析

    一.Ashmem驱动程序 ~/Android/kernel/goldfish ----include ----linux ----ashmem.h ----mm ----ashmem.c 驱动程序具体 ...

  8. MemoryFile匿名共享内存

    Android提供了一个高效的共享内存机制.如果应用中涉及到在多个进程间交换数据时使用Android提高的共享内存机制将会大大的提高效率.但是也许是出于安全考虑,在应用层使用共享内存机制将会遇到很多障 ...

  9. 共享内存之——system V共享内存

    System V 的IPC对象有共享内存.消息队列.信号灯(量). 注意:在IPC的通信模式下,不管是共享内存.消息队列还是信号灯,每个IPC的对象都有唯一的名字,称为"键(key)&quo ...

随机推荐

  1. 嵌入式Linux驱动学习之路(一)嵌入式系统的软硬件架构

    硬件资料: 操作系统:(非虚拟机) zws@z-pc:~$ lsb_release -aNo LSB modules are available.Distributor ID: Ubuntu Desc ...

  2. AlertDialog禁止返回键

    android 如何让dialog不消失,即使是用户按了返回键dialog也不消失 解决的问题:软件提示升级的dialog时候,用户有可能按了返回键,但是现在的需求是用户只能按"确定升级&q ...

  3. 使用SQL如何把用逗号等字符隔开的字符串转换成列表(转)

    如何把用逗号等字符隔开的字符串转换成列表,下面依逗号分隔符为例: 比如有一个字符串,其值为:香港,张家港,北京,上海用SQL把这个字符串转换成列表的方法是: 1.方法一 WITH A AS (SELE ...

  4. Validation failed for one or more entities. See 'EntityValidationErrors' property for more details.

    Validation failed for one or more entities. See 'EntityValidationErrors' property for more details. ...

  5. BZOJ 3631 【JLOI2014】 松鼠的新家

    Description 松鼠的新家是一棵树,前几天刚刚装修了新家,新家有n个房间,并且有n-1根树枝连接,每个房间都可以相互到达,且俩个房间之间的路线都是唯一的.天哪,他居然真的住在"树&q ...

  6. Jenkins进阶系列之——18Jenkins语言本地化

    在Jenkins中,英语一大片,看着各种蛋疼.非常高兴的是,Jenkins作为一个主流流行的持续构建工具,提供了一个本地化语言的配置界面. 你可以找到它,在Jenkins每页的左下角.如下图: 点击帮 ...

  7. c++ 静态持续变量

    c++为静态存储持续性变量提供了3种链接性: 外部链接性(可在其他文件中访问) 内部链接性(只能在当前文件中访问) 无链接性(别有用心能在当前函数或代码中访问) 如果没有显示的初始化静态变量会把它设置 ...

  8. Bootstrap系列 -- 7. 列表排版方式

    一. 去点列表 1. 使用class=list-unstyled <ul > <li>无序列表项目</li> <li>无序列表项目</li> ...

  9. 自己存档:ajax 动态提交form

    $.ajax({                    cache: true,                    type: "POST",                  ...

  10. 系统升级日记(3)- 升级SharePoint解决方案和Infopath

    最近一段时间在公司忙于将各类系统进行升级,其最主要的目标有两个,一个是将TFS2010升级到TFS2013,另外一个是将SharePoint 2010升级到SharePoint 2013.本记录旨在记 ...