除了基本使用外,我们还可以自定义处理结果,首先,继承DisplayLeakService实现一个自定义的监控处理Service,代码如下:

public class LeakCnaryService extends DisplayLeakServcie {

private final String TAG = “LeakCanaryService”;

@Override
protected void afterDefaultHandling(HeapDump heapDump, AnalysisResult result, String leakInfo) {

}
}

重写 afterDefaultHanding 方法,在其中处理需要的数据,三个参数的定义如下:

  • heapDump:堆内存文件,可以拿到完整的hprof文件,以使用MAT分析。
  • result:监控到的内存状态,如是否泄漏等。
  • leakInfo:leak trace详细信息,除了内存泄漏对象,还有设备信息。

然后在install时,使用自定义的LeakCanaryService即可,代码如下:

public class BaseApplication extends Application {

@Override
public void onCreate() {
super.onCreate();
mRefWatcher = LeakCanary.install(this, LeakCanaryService.calss, AndroidExcludedRefs.createAppDefaults().build());
}

}

经过这样的处理,就可以在LeakCanaryService中实现自己的处理方式,如丰富的提示信息,把数据保存在本地、上传到服务器进行分析。

注意

LeakCanaryService需要在AndroidManifest中注册。

四、优化内存空间

1、对象引用

从Java 1.2版本开始引入了三种对象引用方式:SoftReference、WeakReference 和 PhantomReference 三个引用类,引用类的主要功能就是能够引用但仍可以被垃圾回收器回收的对象。在引入引用类之前,只能使用Strong Reference,如果没有指定对象引用类型,默认是强引用。下面,我们就分别来介绍下这几种引用。

1、强引用

如果一个对象具有强引用,GC就绝对不会回收它。当内存空间不足时,JVM会抛出OOM错误。

2、软引用

如果一个对象只具有软引用,则内存空间足够,GC时就不会回收它;如果内存不足,就会回收这些对象的内存。可用来实现内存敏感的高速缓存。

软引用可以和一个ReferenceQueue(引用队列)联合使用,如果软引用引用的对象被垃圾回收器回收,JVM会把这个软引用加入与之关联的引用队列中。

3、弱引用

在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间是否足够,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。

这里要注意,可能需要运行多次GC,才能找到并释放弱引用对象。

4、虚引用

只能用于跟踪即将对被引用对象进行的收集。虚拟机必须与ReferenceQueue类联合使用。因为它能够充当通知机制。

2、减少不必要的内存开销

1、AutoBoxing

自动装箱的核心就是把基础数据类型转换成对应的复杂类型。在自动装箱转化时,都会产生一个新的对象,这样就会产生更多的内存和性能开销。如int只占4字节,而Integer对象有16字节,特别是HashMap这类容器,进行增、删、改、查操作时,都会产生大量的自动装箱操作。

检测方式

使用TraceView查看耗时,如果发现调用了大量的integer.value,就说明发生了AutoBoxing。

2、内存复用

对于内存复用,有如下四种可行的方式:

  • 资源复用:通用的字符串、颜色定义、简单页面布局的复用。
  • 视图复用:可以使用ViewHolder实现ConvertView复用。
  • 对象池:显示创建对象池,实现复用逻辑,对相同的类型数据使用同一块内存空间。
  • Bitmap对象的复用:使用inBitmap属性可以告知Bitmap解码器尝试使用已经存在的内存区域,新解码的bitmap会尝试使用之前那张bitmap在heap中占据的pixel data内存区域。

3、使用最优的数据类型

1、HashMap与ArrayMap

HashMap是一个散列链表,向HashMap中put元素时,先根据key的HashCode重新计算hash值,根据hash值得到这个元素在数组中的位置,如果数组该位置上已经存放有其它元素了,那么这个位置上的元素将以链表的形式存放,新加入的放在链头,最后加入的放在链尾。如果数组该位置上没有元素,就直接将该元素放到此数组中的该位置上。也就是说,向HashMap插入一个对象前,会给一个通向Hash阵列的索引,在索引的位置中,保存了这个Key对象的值。这意味着需要考虑的一个最大问题是冲突,当多个对象散列于阵列相同位置时,就会有散列冲突的问题。因此,HashMap会配置一个大的数组来减少潜在的冲突,并且会有其他逻辑防止链接算法和一些冲突的发生。

ArrayMap提供了和HashMap一样的功能,但避免了过多的内存开销,方法是使用两个小数组,而不是一个大数组。并且ArrayMap在内存上是连续不间断的。

总体来说,在ArrayMap中执行插入或者删除操作时,从性能角度上看,比HashMap还要更差一些,但如果只涉及很小的对象数,比如1000以下,就不需要担心这个问题了。因为此时ArrayMap不会分配过大的数组

此外,Android自身还提供了一系列优化过后的数据集合工具类,如 SparseArray、SparseBooleanArray、LongSparseArray,使用这些API可以让我们的程序更加高效。HashMap 工具类会相对比较 低效,因为它 需要为每一个键值对都提供一个对象入口,而 SparseArray避免 掉了 基本数据类型转换成对象数据类型的时间

2、使用 IntDef和StringDef 替代枚举类型

使用枚举类型的dex size是普通常量定义的dex size的13倍以上,同时,运行时的内存分配,一个enum值的声明会消耗至少20bytes。

枚举最大的优点是类型安全,但在Android平台上,枚举的内存开销是直接定义常量的三倍以上。所以Android提供了注解的方式检查类型安全。目前提供了int型和String型两种注解方式:IntDef和StringDef,用来提供编译期的类型检查。

注意

使用IntDef和StringDef需要在Gradle配置中引入相应的依赖包:

compile ‘com.android.support:support-annotations:22.0.0’

3、LruCache

最近最少使用缓存,使用强引用保存需要缓存的对象,它内部维护了一个由LinkedHashMap组成的双向列表,不支持线程安全,LruCache对它进行了封装,添加了线程安全操作。当其中的一个值被访问时,它被放到队列的尾部,当缓存将满时,队列头部的值(最近最少被访问的)被丢弃,之后可以被GC回收。

除了普通的get/set方法之外,还有sizeOf方法,它用来返回每个缓存对象的大小。此外,还有entryRemoved方法,当一个缓存对象被丢弃时调用的方法,当第一个参数为true:表明缓存对象是为了腾出空间而被清理。否则,表明缓存对象的entry是被remove移除或者被put覆盖。

注意

分配LruCache大小时应考虑应用剩余内存有多大。

4、图片内存优化

在Android默认情况下,当图片文件解码成位图时,会被处理成32bit/像素。红色、绿色、蓝色和透明通道各8bit,即使是没有透明通道的图片,如JEPG隔世是没有透明通道的,但然后会处理成32bit位图,这样分配的32bit中的8bit透明通道数据是没有任何用处的,这完全没有必要,并且在这些图片被屏幕渲染之前,它们首先要被作为纹理传送到GPU,这意味着每一张图片会同时占用CPU内存和GPU内存。下面,我总结了减少内存开销的几种常用方式,如下所示:

1、设置位图的规格:当显示小图片或对图片质量要求不高时可以考虑使用RGB_565,用户头像或圆角图片一般可以尝试ARGB_4444。通过设置inPreferredConfig参数来实现不同的位图规格,代码如下所示:

BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565;
BitmapFactory.decodeStream(is, null, options);

2、inSampleSize:位图功能对象中的inSampleSize属性实现了位图的缩放功能,代码如下所示:

BitampFactory.Options options = new BitmapFactory.Options();
// 设置为4就是宽和高都变为原来1/4大小的图片
options.inSampleSize = 4;
BitmapFactory.decodeSream(is, null, options);

3、inScaled,inDensity和inTargetDensity实现更细的缩放图片:当inScaled设置为true时,系统会按照现有的密度来划分目标密度,代码如下所示:

BitampFactory.Options options = new BitampFactory.Options();
options.inScaled = true;
options.inDensity = srcWidth;
options.inTargetDensity = dstWidth;
BitmapFactory.decodeStream(is, null, options);

上述三种方案的缺点:使用了过多的算法,导致图片显示过程需要更多的时间开销,如果图片很多的话,就影响到图片的显示效果。最好的方案是结合这两个方法,达到最佳的性能结合,首先使用inSampleSize处理图片,转换为接近目标的2次幂,然后用inDensity和inTargetDensity生成最终想要的准确大小,因为inSampleSize会减少像素的数量,而基于输出密码的需要对像素重新过滤。但获取资源图片的大小,需要设置位图对象的inJustDecodeBounds值为true,然后继续解码图片文件,这样才能生产图片的宽高数据,并允许继续优化图片。总体的代码如下所示:

BitmapFactory.Options options = new BitampFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(is, null, options);
options.inScaled = true;
options.inDensity = options.outWidth;
options.inSampleSize = 4;
Options.inTargetDensity = desWith * options.inSampleSize;
options.inJustDecodeBounds = false;
BitmapFactory.decodeStream(is, null, options);

5、inBitmap

可以结合LruCache来实现,在LruCache移除超出cache size的图片时,暂时缓存Bitamp到一个软引用集合,需要创建新的Bitamp时,可以从这个软引用集合中找到最适合重用的Bitmap,来重用它的内存区域。

需要注意,新申请的Bitmap与旧的Bitmap必须有相同的解码格式,并且在Android 4.4之前,只能重用相同大小的Bitamp的内存区域,而Android 4.4之后可以重用任何bitmap的内存区域。

6、图片放置优化

只需要UI提供一套高分辨率的图,图片建议放在drawable-xxhdpi文件夹下,这样在低分辨率设备中图片的大小只是压缩,不会存在内存增大的情况。如若遇到不需缩放的文件,放在drawable-nodpi文件夹下。

7、在App可用内存过低时主动释放内存

在App退到后台内存紧张即将被Kill掉时选择重写 onTrimMemory/onLowMemory 方法去释放掉图片缓存、静态缓存来自保。

8、item被回收不可见时释放掉对图片的引用

  • ListView:因此每次item被回收后再次利用都会重新绑定数据,只需在ImageView onDetachFromWindow的时候释放掉图片引用即可。
  • RecyclerView:因为被回收不可见时第一选择是放进mCacheView中,这里item被复用并不会只需bindViewHolder来重新绑定数据,只有被回收进mRecyclePool中后拿出来复用才会重新绑定数据,因此重写Recycler.Adapter中的onViewRecycled()方法来使item被回收进RecyclePool的时候去释放图片引用。

9、避免创作不必要的对象

例如,我们可以在字符串拼接的时候使用StringBuffer,StringBuilder。

10、自定义View中的内存优化

例如,在onDraw方法里面不要执行对象的创建,一般来说,都应该在自定义View的构造器中创建对象。

11、其它的内存优化注意事项

除了上面的一些内存优化点之外,这里还有一些内存优化的点我们需要注意,如下所示:

  • 尽使用static final 优化成员变量。
  • 使用增强型for循环语法。
  • 在没有特殊原因的情况下,尽量使用基本数据类型来代替封装数据类型,int比Integer要更加有效,其它数据类型也是一样。
  • 在合适的时候适当采用软引用和弱引用。
  • 采用内存缓存和磁盘缓存。
  • 尽量采用静态内部类,可避免潜在由于内部类导致的内存泄漏。

五、图片管理模块的设计与实现

在设计一个模块时,需要考虑以下几点:

  • 1、单一职责
  • 2、避免不同功能之间的耦合
  • 3、接口隔离

在编写代码前先画好UML图确定每一个对象、方法、接口的功能,首先尽量做到功能单一原则,在这个基础上,再明确模块与模块的直接关系,最后使用代码实现。

1、实现异步加载功能

1.实现网络图片显示

ImageLoader是实现图片加载的基类,其中ImageLoader有一个内部类BitmapLoadTask是继承AsyncTask的异步下载管理类,负责图片的下载和刷新,MiniImageLoader是ImageLoader的子类,维护类一个ImageLoader的单例,并且实现了基类的网络加载功能,因为具体的下载在应用中有不同的下载引擎,抽象成接口便于替换。代码如下所示:

public abstract class ImageLoader {
private boolean mExitTasksEarly = false; //是否提前结束
protected boolean mPauseWork = false;
private final Object mPauseWorkLock = new Object();

protected ImageLoader() {

}

public void loadImage(String url, ImageView imageView) {
if (url == null) {
return;
}

BitmapDrawable bitma 《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》无偿开源 徽信搜索公众号【编程进阶路】 pDrawable = null;
if (bitmapDrawable != null) {
imageView.setImageDrawable(bitmapDrawable);
} else {
final BitmapLoadTask task = new BitmapLoadTask(url, imageView);
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}

private class BitmapLoadTask extends AsyncTask<Void, Void, Bitmap> {

private String mUrl;
private final WeakReference imageViewWeakReference;

public BitmapLoadTask(String url, ImageView imageView) {
mUrl = url;
imageViewWeakReference = new WeakReference(imageView);
}

@Override
protected Bitmap doInBackground(Void… params) {
Bitmap bitmap = null;
BitmapDrawable drawable = null;

synchronized (mPauseWorkLock) {
while (mPauseWork && !isCancelled()) {
try {
mPauseWorkLock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

if (bitmap == null
&& !isCancelled()
&& imageViewWeakReference.get() != null
&& !mExitTasksEarly) {
bitmap = downLoadBitmap(mUrl);
}
return bitmap;
}

@Override
protected void onPostExecute(Bitmap bitmap) {
if (isCancelled() || mExitTasksEarly) {
bitmap = null;
}

ImageView imageView = imageViewWeakReference.get();
if (bitmap != null && imageView != null) {
setImageBitmap(imageView, bitmap);
}
}

@Override
protected void onCancelled(Bitmap bitmap) {
super.onCancelled(bitmap);
synchronized (mPauseWorkLock) {
mPauseWorkLock.notifyAll();
}
}
}

public void setPauseWork(boolean pauseWork) {
synchronized (mPauseWorkLock) {
mPauseWork = pauseWork;
if (!mPauseWork) {
mPauseWorkLock.notifyAll();
}
}
}

public void setExitTasksEarly(boolean exitTasksEarly) {
mExitTasksEarly = exitTasksEarly;
setPauseWork(false);
}

private void setImageBitmap(ImageView imageView, Bitmap bitmap) {
imageView.setImageBitmap(bitmap);
}

protected abstract Bitmap downLoadBitmap(String mUrl);
}

setPauseWork方法是图片加载线程控制接口,pauseWork控制图片模块的暂停和继续工作,一般在listView等控件中,滑动时停止加载图片,保证滑动流畅。另外,具体的图片下载和解码是和业务强相关的,因此在ImageLoader中不做具体的实现,只是定义类一个抽象方法。

MiniImageLoader是一个单例,保证一个应用只维护一个ImageLoader,减少对象开销,并管理应用中所有的图片加载。MiniImageLoader代码如下所示:

public class MiniImageLoader extends ImageLoader {

private volatile static MiniImageLoader sMiniImageLoader = null;

private ImageCache mImageCache = null;

public static MiniImageLoader getInstance() {
if (null == sMiniImageLoader) {
synchronized (MiniImageLoader.class) {
MiniImageLoader tmp = sMiniImageLoader;
if (tmp == null) {
tmp = new MiniImageLoader();
}
sMiniImageLoader = tmp;
}
}
return sMiniImageLoader;
}

public MiniImageLoader() {
mImageCache = new ImageCache();
}

@Override
protected Bitmap downLoadBitmap(String mUrl) {
HttpURLConnection urlConnection = null;
InputStream in = null;
try {
final URL url = new URL(mUrl);
urlConnection = (HttpURLConnection) url.openConnection();
in = urlConnection.getInputStream();
Bitmap bitmap = decodeSampledBitmapFromStream(in, null);
return bitmap;

} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
urlConnection = null;
}

if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

return null;
}

public Bitmap decodeSampledBitmapFromStream(InputStream is, BitmapFactory.Options options) {
return BitmapFactory.decodeStream(is, null, options);
}
}

其中,volatile保证了对象从主内存加载。并且,上面的try …cache层级太多,Java中有一个Closeable接口,该接口标识类一个可关闭的对象,因此可以写如下的工具类:

public class CloseUtils {

public static void closeQuietly(Closeable closeable) {
if (null != closeable) {
try {
closeable.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

改造后如下所示:

finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
CloseUtil.closeQuietly(in);
}

同时,为了使ListView在滑动过程中更流畅,在滑动时暂停图片加载,减少系统开销,代码如下所示:

listView.setOnScrollListener(new AbsListView.OnScrollListener() {

@Override
public void onScrollStateChanged(AbsListView absListView, int scrollState) {
if (scorllState == AbsListView.OnScrollListener.SCROLL_STAE_FLING) {
MiniImageLoader.getInstance().setPauseWork(true);
} else {
MiniImageLoader.getInstance().setPauseWork(false);
}

}

2 单个图片内存优化

这里使用一个BitmapConfig类来实现参数的配置,代码如下所示:

public class BitmapConfig {

private int mWidth, mHeight;
private Bitmap.Config mPreferred;

public BitmapConfig(int width, int height) {
this.mWidth = width;
this.mHeight = height;
this.mPreferred = Bitmap.Config.RGB_565;
}

public BitmapConfig(int width, int height, Bitmap.Config preferred) {
this.mWidth = width;
this.mHeight = height;
this.mPreferred = preferred;
}

public BitmapFactory.Options getBitmapOptions() {
return getBitmapOptions(null);
}

// 精确计算,需要图片is流现解码,再计算宽高比
public BitmapFactory.Options getBitmapOptions(InputStream is) {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565;
if (is != null) {
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(is, null, options);
options.inSampleSize = calculateInSampleSize(options, mWidth, mHeight);
}
options.inJustDecodeBounds = false;
return options;
}

private static int calculateInSampleSize(BitmapFactory.Options options, int mWidth, int mHeight) {
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > mHeight || width > mWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
while ((halfHeight / inSampleSize) > mHeight
&& (halfWidth / inSampleSize) > mWidth) {
inSampleSize *= 2;
}
}

return inSampleSize;
}
}

然后,调用MiniImageLoader的downLoadBitmap方法,增加获取BitmapFactory.Options的步骤:

final URL url = new URL(urlString);
urlConnection = (HttpURLConnection) url.openConnection();
in = urlConnection.getInputStream();
final BitmapFactory.Options options = mConfig.getBitmapOptions(in);
in.close();
urlConnection.disconnect();
urlConnection = (HttpURLConnection) url.openConnection();
in = urlConnection.getInputStream();
Bitmap bitmap = decodeSampledBitmapFromStream(in, options);

优化后仍存在一些问题:

  • 1.相同的图片,每次都要重新加载;
  • 2.整体内存开销不可控,虽然减少了单个图片开销,但是在片非常多的情况下,没有合理管理机制仍然对性能有严重影的。

为了解决这两个问题,就需要有内存池的设计理念,通过内存池控制整体图片内存,不重新加载和解码已经显示过的图片。

2、实现三级缓存

内存–本地–网络

1、内存缓存

使用软引用和弱引用(SoftReference or WeakReference)来实现内存池是以前的常用做法,但是现在不建议。从API 9起(Android 2.3)开始,Android系统垃圾回收器更倾向于回收持有软引用和弱引用的对象,所以不是很靠谱,从Android 3.0开始(API 11)开始,图片的数据无法用一种可遇见的方式将其释放,这就存在潜在的内存溢出风险。 使用LruCache来实现内存管理是一种可靠的方式,它的主要算法原理是把最近使用的对象用强引用来存储在LinkedHashMap中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除。使用LruCache实现一个图片的内存缓存的代码如下所示:

public class MemoryCache {

private final int DEFAULT_MEM_CACHE_SIZE = 1024 * 12;
private LruCache<String, Bitmap> mMemoryCache;
private final String TAG = “MemoryCache”;
public MemoryCache(float sizePer) {
init(sizePer);
}

private void init(float sizePer) {
int cacheSize = DEFAULT_MEM_CACHE_SIZE;
if (sizePer > 0) {
cacheSize = Math.round(sizePer * Runtime.getRuntime().maxMemory() / 1024);
}

mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
final int bitmapSize = getBitmapSize(value) / 1024;
return bitmapSize == 0 ? 1 : bitmapSize;
}

@Override
protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
super.entryRemoved(evicted, key, oldValue, newValue);
}
};
}

@TargetApi(Build.VERSION_CODES.KITKAT)
public int getBitmapSize(Bitmap bitmap) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
return bitmap.getAllocationByteCount();
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
return bitmap.getByteCount();
}

return bitmap.getRowBytes() * bitmap.getHeight();
}

public Bitmap getBitmap(String url) {
Bitmap bitmap = null;
if (mMemoryCache != null) {
bitmap = mMemoryCache.get(url);
}
if (bitmap != null) {
Log.d(TAG, “Memory cache exiet”);
}

return bitmap;
}

public void addBitmapToCache(String url, Bitmap bitmap) {
if (url == null || bitmap == null) {
return;
}

mMemoryCache.put(url, bitmap);
}

public void clearCache() {
if (mMemoryCache != null) {
mMemoryCache.evictAll();
}
}
}

上述代码中cacheSize百分比占比多少合适?可以基于以下几点来考虑:

  • 1.应用中内存的占用情况,除了图片以外,是否还有大内存的数据需要缓存到内存。
  • 2.在应用中大部分情况要同时显示多少张图片,优先保证最大图片的显示数量的缓存支持。
  • 3.Bitmap的规格,计算出一张图片占用的内存大小。
  • 4.图片访问的频率。

在应用中,如果有一些图片的访问频率要比其它的大一些,或者必须一直显示出来,就需要一直保持在内存中,这种情况可以使用多个LruCache对象来管理多组Bitmap,对Bitmap进行分级,不同级别的Bitmap放到不同的LruCache中。

2、bitmap内存复用

从Android3.0开始Bitmap支持内存复用,也就是BitmapFactoy.Options.inBitmap属性,如果这个属性被设置有效的目标用对象,decode方法就在加载内容时重用已经存在的bitmap,这意味着Bitmap的内存被重新利用,这可以减少内存的分配回收,提高图片的性能。代码如下所示:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
mReusableBitmaps = Collections.synchronizedSet(newHashSet<SoftReference>());
}

因为inBitmap属性在Android3.0以后才支持,在entryRemoved方法中加入软引用集合,作为复用的源对象,之前是直接删除,代码如下所示:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
mReusableBitmaps.add(new SoftReference(oldValue));
}

同样在3.0以上判断,需要分配一个新的bitmap对象时,首先检查是否有可复用的bitmap对象:

public static Bitmap decodeSampledBitmapFromStream(InputStream is, BitmapFactory.Options options, ImageCache cache) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
addInBitmapOptions(options, cache);
}
return BitmapFactory.decodeStream(is, null, options);
}

@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private static void addInBitmapOptions(BitmapFactory.Options options, ImageCache cache) {
options.inMutable = true;
if (cache != null) {
Bitmap inBitmap = cache.getBitmapFromReusableSet(options);
if (inBitmap != null) {
options.inBitmap = inBitmap;
}
}

}

接着,我们使用cache.getBitmapForResubleSet方法查找一个合适的bitmap赋值给inBitmap。代码如下所示:

// 获取inBitmap,实现内存复用
public Bitmap getBitmapFromReusableSet(BitmapFactory.Options options) {
Bitmap bitmap = null;

if (mReusableBitmaps != null && !mReusableBitmaps.isEmpty()) {
final Iterator<SoftReference> iterator = mReusableBitmaps.iterator();
Bitmap item;

while (iterator.hasNext()) {
item = iterator.next().get();

if (null != item && item.isMutable()) {
if (canUseForInBitmap(item, options)) {

Log.v(“TEST”, “canUseForInBitmap!!!”);

bitmap = item;

// Remove from reusable set so it can’t be used again
iterator.remove();
break;
}
} else {
// Remove from the set if the reference has been cleared.
iterator.remove();
}
}
}

return bitmap;
}

上述方法从软引用集合中查找规格可利用的Bitamp作为内存复用对象,因为使用inBitmap有一些限制,在Android 4.4之前,只支持同等大小的位图。因此使用了canUseForInBitmap方法来判断该Bitmap是否可以复用,代码如下所示:

@TargetApi(Build.VERSION_CODES.KITKAT)
private static boolean canUseForInBitmap(
Bitmap candidate, BitmapFactory.Options targetOptions) {

if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
return candidate.getWidth() == targetOptions.outWidth
&& candidate.getHeight() == targetOptions.outHeight
&& targetOptions.inSampleSize == 1;
}
int width = targetOptions.outWidth / targetOptions.inSampleSize;
int height = targetOptions.outHeight / targetOptions.inSampleSize;

int byteCount = width * height * getBytesPerPixel(candidate.getConfig());

return byteCount <= candidate.getAllocationByteCount();
}

3、磁盘缓存

由于磁盘读取时间是不可预知的,所以图片的解码和文件读取都应该在后台进程中完成。DisLruCache是Android提供的一个管理磁盘缓存的类。

1、首先调用DiskLruCache的open方法进行初始化,代码如下:

public static DiskLruCache open(File directory, int appVersion, int valueCou9nt, long maxSize)

directory一般建议缓存到SD卡上。appVersion发生变化时,会自动删除前一个版本的数据。valueCount是指Key与Value的对应关系,一般情况下是1对1的关系。maxSize是缓存图片的最大缓存数据大小。初始化DiskLruCache的代码如下所示:

private void init(final long cacheSize,final File cacheFile) {
new Thread(new Runnable() {
@Override
public void run() {
synchronized (mDiskCacheLock) {
if(!cacheFile.exists()){
cacheFile.mkdir();
}
MLog.d(TAG,“Init DiskLruCache cache path:” + cacheFile.getPath() + “\r\n” + “Disk Size:” + cacheSize);
try {
mDiskLruCache = DiskLruCache.open(cacheFile, MiniImageLoaderConfig.VESION_IMAGELOADER, 1, cacheSize);
mDiskCacheStarting = false;
// Finished initialization
mDiskCacheLock.notifyAll();
// Wake any waiting threads
}catch(IOException e){
MLog.e(TAG,“Init err:” + e.getMessage());
}
}
}
}).start();
}

如果在初始化前就要操作写或者读会导致失败,所以在整个DiskCache中使用的Object的wait/notifyAll机制来避免同步问题。

2、写入DiskLruCache

首先,获取Editor实例,它需要传入一个key来获取参数,Key必须与图片有唯一对应关系,但由于URL中的字符可能会带来文件名不支持的字符类型,所以取URL的MD4值作为文件名,实现Key与图片的对应关系,通过URL获取MD5值的代码如下所示:

private String hashKeyForDisk(String key) {
String cacheKey;
try {
final MessageDigest mDigest = MessageDigest.getInstance(“MD5”);
mDigest.update(key.getBytes());
cacheKey = bytesToHexString(mDigest.digest());
} catch (NoSuchAlgorithmException e) {
cacheKey = String.valueOf(key.hashCode());
}
return cacheKey;
}
private String bytesToHexString(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(0xFF & bytes[i]);
if (hex.length() == 1) {
sb.append(‘0’);
}
sb.append(hex);
}
return sb.toString();
}

然后,写入需要保存的图片数据,图片数据写入本地缓存的整体代码如下所示:

public void saveToDisk(String imageUrl, InputStream in) {
// add to disk cache
synchronized (mDiskCacheLock) {
try {
while (mDiskCacheStarting) {
try {
mDiskCacheLock.wait();
} catch (InterruptedException e) {}
}
String key = hashKeyForDisk(imageUrl);
MLog.d(TAG,“saveToDisk get key:” + key);
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
if (in != null && editor != null) {
// 当 valueCount指定为1时,index传0即可
OutputStream outputStream = editor.newOutputStream(0);
MLog.d(TAG, “saveToDisk”);
if (FileUtil.copyStream(in,outputStream)) {
MLog.d(TAG, “saveToDisk commit start”);
editor.commit();
MLog.d(TAG, “saveToDisk commit over”);
} else {
editor.abort();
MLog.e(TAG, “saveToDisk commit abort”);
}

Android性能优化之内存优化相关推荐

  1. Android 系统性能优化(55)---Android 性能优化之内存优化

    Android 性能优化之内存优化 前言 Android App优化这个问题,我相信是Android开发者一个永恒的话题.本篇文章也不例外,也是来讲解一下Android内存优化.那么本篇文章有什么不同 ...

  2. App性能优化(布局优化,线程优化,app瘦身优化,页面切换优化,App启动优化,内存优化)

    Android APP性能优化(最新总结) 在目前Android开发中,UI布局可以说是每个App使用频率很高的,随着UI越来越多,布局的重复性.复杂度也随之增长,这样使得UI布局的优化,显得至关重要 ...

  3. Lua性能优化—Lua内存优化

    原文链接https://blog.uwa4d.com/archives/usparkle_luaperformance.html 这是侑虎科技第236篇原创文章,感谢作者舒航供稿,欢迎转发分享,未经作 ...

  4. android—性能优化2—内存优化

    文章目录 性能优化: 工具: memory profiler LeakCanary arthook epic 库 java内存管理机制 java 内存回收机制 Android内存管理机制 Dalvik ...

  5. Android App性能优化之内存优化

    为什么要进行内存优化? 1.App运行内存限制,OOM导致App崩溃 2.App性能:流畅性.响应速度和用户体验 Android的内存管理方式 Android系统内存分配与回收方式 ●   一个App ...

  6. Android性能优化之内存优化 1

    导语 智能手机发展到今天已经有十几个年头,手机的软硬件都已经发生了翻天覆地的变化,特别是Android阵营,从一开始的一两百M到今天动辄4G,6G内存.然而大部分的开发者观看下自己的异常上报系统,还是 ...

  7. Android 性能监测工具,优化内存、卡顿、耗电、APK的方法

    导语     安卓大军浩浩荡荡,发展已近十个年头,技术优化月新日异,如今 Android 9.0 代号P  都发布了,Android系统性能已经非常流畅了.但是,到了各大厂商手里,改源码自定系统,使得 ...

  8. Android面试-Android性能优化和内存优化、APP启动速度一线大厂的实战案例解析

    一.Android 内存管理机制 二.优化内存的意义 三.避免内存泄漏 四.优化内存空间 五.图片管理模块的设计与实现 六.总结 深入探索Android内存优化 第一章.重识内存优化 第二章.常见工具 ...

  9. Android性能优化之内存优化浅析

    一.背景 Android由于是以Java语言为主要开发语言,所以它的内存管理并不像C语言那样由开发者去管理内存的分配以及回收等,而是交由JVM虚拟机的内存回收机制去处理.这就导致我们在开发过程中难免会 ...

  10. Android 系统性能优化(30)---Android性能全面分析与优化方案研究

    Android 性能优化 1.结合以下四个部分讲解: 性能问题分类 性能优化原则和方法 借助性能优化工具分析解决问题 性能优化指标 2性能问题分类 1.渲染问题:过度绘制.布局冗杂 2.内存问题:内存 ...

最新文章

  1. python第三方库有哪些常用的、请列举15个-你想要的Python面试都在这里了【315+道题】...
  2. border属性的CSS缩写顺序
  3. 转;VC++中Format函数详解
  4. chrome 占用大量内存直到网页崩溃_Chrome吃内存太夸张!几招解决Chrome占内存硬伤...
  5. python输入十个数输出最大值_python输入十个数如何输出最大值
  6. shiro修改html不生效,shiro中anon配置不生效
  7. SSM项目使用example查询时多次查询条件相同
  8. iphone 2x 3x_iPhone X的未来:从现实到荒诞
  9. 影响 Chrome、Edge 等浏览器的 V8 引擎0day
  10. 液压外部测试系统软件,液压测试系统的软件设计
  11. js 原生 导出 excel
  12. 一张图解释DNS域名服务器的作用
  13. Android 打飞机 小游戏 的实现 - 画面移动 对象封装 碰撞效果
  14. 雷达动目标显示(MTD)的理解
  15. 纯代码告诉你:我的原弈非常(Yanj Future)是怎么下棋的
  16. 什么是数字“指纹”?
  17. 目标分割(六)RefineNet讲解
  18. 查询显卡状态 Tesla M40
  19. 子数组(Subarray)
  20. EAS F7 左树右表

热门文章

  1. Android密码明文显示和星号显示切换
  2. 机器学习_深度学习毕设题目汇总——运动活动动作
  3. glassfish插件_Eclipse4.2.2安装GlassFish插件
  4. 2020网站/APP/Webshell在线查毒 电脑软件查毒大全
  5. 使用requests爬取高德地图国内所有城市的天气情况
  6. NAT类型与P2P游戏
  7. Docker 配置加速器
  8. 青龙羊毛——可用脚本
  9. stm32f103c8t6--sd卡的读写flash地址空间的数据读取
  10. 报错 No active profile set, falling back to default profiles: default 解决