Glide笔记
一、简介
在泰国举行的谷歌开发者论坛上,谷歌为我们介绍了一个名叫Glide的图片加载库,作者是bumptech。这个库被广泛的运用在Google的开源项目中,包括2014年Google I/O大会上发布的官方App。
Glide是一款由Bump Technologies开发的图片加载框架,使得我们可以在Android平台上以极度简单的方式加载和展示图片。Glide默认使用HttpUrlConnection进行网络请求,为了让App保持一致的网络请求形式,可以让Glide使用我们指定的网络请求形式请求网络资源。
二、依赖
1.jar包
Github地址:https://github.com/bumptech/glide/releases
2.Gradle
dependencies {
compile 'com.github.bumptech.glide:glide:3.7.0'
compile 'com.android.support:support-v4:23.3.0'
}
三、权限
四、混淆
-keep public class * implements com.bumptech.glide.module.GlideModule
-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {
**[] $VALUES;
public *;
}
五、使用
-
Glide.with(context).load(imageUrl).into(imageView);
//从URL中加载 -
Glide.with(context).load(R.mipmap.ic_launcher).into(imageView);
//从Res资源中加载 -
File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "Running.jpg");
Glide.with(context).load(file).into(imageView);
//从文件加载 -
Uri uri = resourceIdToUri(context, R.mipmap.ic_launcher);
Glide.with(context).load(uri).into(imageView);
//从Uri加载
Glide.with()方法用于创建一个加载图片的实例。with()方法可以接收Context、Activity、Fragment或者FragmentActivity类型的参数。特别需要注意的是with()方法中传入的实例会决定Glide加载图片的生命周期,如果传入的是Activity、Fragment或者FragmentActivity的实例,那么当其被销毁时图片加载也会停止,如果传入的是ApplicationContext时只有当应用程序被杀掉的时候图片加载才会停止。
使用Glide加载图片不用担心内存浪费,甚至是内存溢出的问题。因为Glide不会直接将图片的完整尺寸全部加载到内存中,而是用多少加载多少。Glide会自动判断ImageView的大小,然后只将这么大的图片像素加载到内存当中,帮助我们节省内存开支。
下面一个小的工具函数可以将资源id转换为一个Uri:
public static final String ANDROID_RESOURCE = "android.resource://";
public static final String FOREWARD_SLASH = "/";
private static Uri resourceIdToUri(Context context, int resourceId) {
return Uri.parse(ANDROID_RESOURCE + context.getPackageName() + FOREWARD_SLASH + resourceId);
}
六、方法
1、指定图片格式
如果调用了.asBitmap()
方法,则.load()中的参数指向的可以是一个静态图片也可以是GIF图片,如果是一张GIF图片,则加载之后只会展示GIF图片的第一帧。
如果调用的.asGif()
方法,则.load()方法中的参数指向的必须是一个GIF图片,如果是一张静态图片,则图片加载完成之后展示的只是出错占位符(如果没有设置出错占位符,则什么也不展示)。
//显示静态图片(若加载的是gif图那么就会显示第一帧的图片)
.asBitmap()
//显示动态图片(若加载的是静态图会加载失败)
.asGif()
2、指定占位图显示
//加载时显示的图片
.placeholder(R.drawable.image_load)
//加载失败时显示的图片
.error(R.drawable.image_error)
3、设置缓存
Android应用中一个较好的图片处理框架,会最小化网络请求的消耗。Glide也是一样,默认使用内存和磁盘缓存来避免不必要的网络请求。然而,如果你的图片变化的非常快,你需要禁止一些缓存。
比如你请求一个1000x1000像素的图片,你的ImageView是500x500像素,Glide会保存两个版本的图片到缓存里。
//禁止内存缓存,但仍然会缓存到磁盘
.skipMemoryCache(true)
//禁止磁盘缓存(Glide默认缓存策略是:DiskCacheStrategy.RESULT)
.diskCacheStrategy(DiskCacheStrategy.NONE)
//缓存参数
//ALL:缓存源资源和转换后的资源(即缓存所有版本图像,默认行为)
//NONE:不作任何磁盘缓存,然而默认的它将仍然使用内存缓存
//SOURCE:仅缓存源资源(原来的全分辨率的图像),上面例子里的1000x1000像素的图片
//RESULT:缓存转换后的资源(最终的图像,即降低分辨率后的或者是转换后的)
如果你有一个图片你需要经常处理它,会生成各种不同的版本的图片,缓存它的原始的分辨率图片才有意义。我们
使用DiskCacheStrategy.SOURCE
去告诉Glide只缓存原始版本:
Glide.with(context).load("url").diskCacheStrategy(DiskCacheStrategy.SOURCE).into(imageView);
4、设置加载尺寸
Glide在缓存和内存里自动限制图片的大小去适配ImageView的尺寸。用Glide时,如果图片不需要自动适配ImageView,调用override(horizontalSize, verticalSize),
它会在将图片显示在ImageView之前调整图片的大小。
//加载图片为100*100像素的尺寸
.override(100, 100)
5、设置图片缩放
如果调用了.centerCrop()
方法,则显示图片的时候短的一边填充容器,长的一边跟随缩放;
如果调用了.fitCenter()
方法,则显示图片的时候长的一边填充容器,短的一边跟随缩放;
这两个方法可以都调用,如果都调用,则最终显示的效果是后调用的方法展示的效果。
//它是一个裁剪技术,即缩放图像让它填充到ImageView界限内并且裁剪额外的部分,ImageView可能会完全填充,但图像可能不会完整显示
.centerCrop()
//它是一个裁剪技术,即缩放图像让图像都测量出来等于或小于ImageView的边界范围,该图像将会完全显示,但可能不会填满整个ImageView
.fitCenter()
Glide
.with(context)
.load(UsageExampleListViewAdapter.eatFoodyImages[0])
.override(600, 200) // resizes the image to these dimensions (in pixel)
.centerCrop() // this cropping technique scales the image so that it fills the requested bounds and then crops the extra.
.into(imageViewResizeCenterCrop);
6、设置资源加载优先级
假设你正在创建一个信息展示界面,包含顶部的一个主要照片,还有底部的2个并不重要的小图。对于用户体验,我们最好先加载主角照片,然后再加载底部不紧急的图片。Glide里的.priority()方法和Priority的枚举变量支持你的想法。
Priority枚举变量,以递增方式列出:
- Priority.LOW
- Priority.NORMAL
- Priority.HIGH
- Priority.IMMEDIATE
你应当明白优先级并不是非常严格的。Glide会将它们作为一个指导来最优化处理请求。但并不意味着所有的图片都能够按请求的顺序加载。
.priority(Priority.HIGH)
7、设置圆角或圆形图片
//圆角图片
.transform(new GlideRoundTransform(this))
//圆形图片
.transform(new GlideCircleTransform(this))
8、设置缩略图
缩略图的优点
缩略图不同于前面提到的占位图。占位图应当是跟app绑定在一起的资源。缩略图是一个动态的占位图,可以从网络加载。缩略图也会被先加载,直到实际图片请求加载完毕。如果因为某些原因,缩略图获得的时间晚于原始图片,它并不会替代原始图片,而是简单地被忽略掉。
提示:另外一个非常棒的平滑图片显示的方法,通过加载图片主色调的占位图。
Glide提供了2个不同的方法产生缩略图。
第一种:简单的缩略图
通过在加载的时候指定一个小的分辨率,产生一个缩略图。这个方法在ListView和详细视图的组合中非常有用。如果你已经在ListView中用到了250x250像素的图片,那么在在详细视图中会需要一个更大分辨率的图片。然而从用户的角度,我们已经看见了一个小版本的图片,为什么需要好几秒,同样的图片(高分辨率的)才能被再次加载出来呢?
在这种情况下,从显示250x250像素版本的图片平滑过渡到详细视图里查看大图更有意义。Glide里的.thumbnail()方法让这个变为可能。这里,.thumbnal()的参数是一个(0,1)之间浮点数:
Glide.with(context).load("url")
.thumbnail(0.1f)//系数需在(0,1)之间,这样会先加载缩略图然后加载全图
.into(imageView);
这里传递一个0.1f作为参数,Glide会加载原始图片大小的10%的图片。如果原始图片有1000x1000像素,缩略图的分辨率为100x100像素。由于图片将会比ImageView小,你需要确保缩放类型是否正确。
注意到你所有的请求设置都会影响到你的缩略图。例如,如果你使用了一个变换让你的图片变为灰度图,缩略图也同样将会是灰度图。
第二种:高级缩略图请求(原图与缩略图完全不同 )
.thumbnail()传入一个浮点类型的参数,非常简单有效,但并不是总是有意义。如果缩略图需要从网络加载同样全分辨率图片,可能根本都不快。这样,Glide提供了另一个方法去加载和显示缩略图:传递一个新的Glide请求作为参数。
private void loadImageThumbnailRequest() {
// setup Glide request without the into() method
DrawableRequestBuilder thumbnailRequest = Glide
.with( context )
.load( eatFoodyImages[2] );
// pass the request as a a parameter to the thumbnail request
Glide
.with( context )
.load( UsageExampleGifAndVideos.gifUrl )
.thumbnail( thumbnailRequest )
.into( imageView3 );
}
区别在于第一个缩略图请求是完全独立于第二个原始请求的。缩略图可以来自不同资源或者图片URL,你可以在它上面应用不同的变换。
9、设置动画
加载图片时所展示的动画,可以是Animator类型的属性动画,也可以是int类型的动画资源。这个动画只在第一次加载的时候会展示,以后都会从缓存中获取图片,因此也就不会展示动画了(图片的改变时才会有用)。
//设置加载动画
.animate(R.anim.alpha_in)
//实现ViewPropertyAnimation.Animator接口
.animate(ViewPropertyAnimation.Animator animator)
//淡入淡出动画,也是默认动画,动画默认的持续时间是300毫秒。类似:.crossFade(int duration)
.crossFade()
//移除所有动画
.dontAnimate()
ViewPropertyAnimation.Animator animationObject = new ViewPropertyAnimation.Animator() {
@Override
public void animate(View view) {
// if it's a custom view class, cast it here
// then find subviews and do the animations
// here, we just use the entire view for the fade animation
view.setAlpha( 0f );
ObjectAnimator fadeAnim = ObjectAnimator.ofFloat( view, "alpha", 0f, 1f );
fadeAnim.setDuration( 2500 );
fadeAnim.start();
}
};
10、加载本地视频(相当于一张缩略图)
//只能加载本地视频(显示的只是视频的第一帧图像,相当于一张缩略图,不能播
//放视频),网络视频无法加载。如果你想要从网络URL播放视频,参考VideoView
String files = Environment.getExternalStorageDirectory().getAbsolutePath() + "/glide.avi";
Glide.with(this).load(files).into(view);
11、定制view中使用SimpleTarget和ViewTarget
Glide中的回调:Target
假设我们并没有ImageView作为图片加载的目标。我们只需要Bitmap本身。Glide提供了一个用Target获取Bitmap资源的方法。Target只是用来回调,它会在所有的加载和处理完毕时返回想要的结果。
Glide提供了多种多样有各自明确目的Target。先从SimpleTarget介绍。
SimpleTarget
private SimpleTarget target = new SimpleTarget() {
@Override
public void onResourceReady(Bitmap bitmap, GlideAnimation glideAnimation) {
// do something with the bitmap
// for demonstration purposes, let's just set it to an ImageView
imageView1.setImageBitmap( bitmap );
}
};
Glide
.with(context) // could be an issue!
.load(eatFoodyImages[0])
.asBitmap()
.into(target);
使用Target注意事项:
- 第一个是SimpleTarget对象的定义。java/Android可以允许你在.into()内匿名定义,但这会显著增加在Glide处理完
图片请求前Android垃圾回收清理匿名target对象的可能性。最终,会导致图片被加载了,但是回调永远不会被调用。
所以,请确保将你的回调定义为一个字段对象,防止被万恶的Android垃圾回收给清理掉。 - 第二个关键部分是Glide的.with( context )。这个问题实际上是Glide一个特性问题:当你传递了一个context,例如
当前app的activity,当activity停止后,Glide会自动停止当前的请求。这种整合到app生命周期内是非常有用的,但也
是很难处理的。如果你的target是独立于app的生命周期。这里的解决方案是使用application的context:.with(context.getApplicationContext())。当app自己停止运行的时候,Glide会只取消掉图片的请求。
特定大小的Target
另外一个潜在问题是Target没有一个明确的大小。如果你传递一个ImageView作为.into()的参数,Glide会使用ImageView的
大小来限制图片的大小。例如如果要加载的图片是1000x1000像素,但是ImageView的尺寸只有250x250像素,Glide会降低图片到小尺寸,以节省处理时间和内存。显然,由于target没有具体大小,这对target并不起效。但是,如果你有个期望的具体大小,你可以增强回调。如果你知道图片应当为多大,那么在你的回调定义里应当指明,以节省内存:
private SimpleTarget target2 = new SimpleTarget(250, 250) {
@Override
public void onResourceReady(Bitmap bitmap, GlideAnimation glideAnimation) {
imageView2.setImageBitmap(bitmap);
}
};
ViewTarget
有很多原因导致我们不能直接使用ImageView。前面已经介绍了如何获取Bitmap。现在,我们将更深入学习。假设你有个自定义的View。由于没有已知的方法在哪里设置图片,Glide并不支持加载图片到定制的View内。然而用ViewTarget会让这个更简单。
让我们看一个简单的定制View,它继承于FrameLayout,内部使用了一个ImageView:
public class FutureStudioView extends FrameLayout {
...
public void setImage(Drawable drawable) {
iv = (ImageView) findViewById(R.id.custom_view_image);
iv.setImageDrawable(drawable);
}
}
由于我们定制的View并不是继承自ImageView,这里不能使用常规的.into()方法。因此,我们只能创建一个ViewTarget,用来传递给.into()方法:
void loadImageViewTarget() {
FutureStudioView customView = (FutureStudioView) findViewById(R.id.custom_view);
viewTarget = new ViewTarget(customView) {
@Override
public void onResourceReady(GlideDrawable resource, GlideAnimation super GlideDrawable> glideAnimation) {
this.view.setImage(resource.getCurrent());
}
};
Glide.with(context.getApplicationContext()) // safer!
.load(eatFoodyImages[2])
.into(viewTarget);
}
在target的回调方法中,我们在定制view上使用我们创建的setImage(Drawable drawable)方法设置图片。同时,确保你注意到我们已经在ViewTarget的构造方法里传递了我们的定制view:new ViewTarget
12、设置监听请求接口
首先,创建一个listener作为一个字段对象,避免被垃圾回收:
private RequestListener requestListener
= new RequestListener() {
@Override
public boolean onException(Exception e, String model,
Target target, boolean isFirstResource) {
// todo log exception
// important to return false so the error placeholder can be placed
//加载异常
return false;
}
@Override
public boolean onResourceReady(GlideDrawable resource, String model,
Target target, boolean isFromMemoryCache,
boolean isFirstResource) {
//加载成功
//view.setImageDrawable(resource);
return false;
}
};
在onException方法中,你可以抓取问题,并决定你需要做什么,比如记录日志。如果Glide应当处理这个后果,比如显示一个出错占位图,在onException方法中返回false是很重要的。
Glide
.with( context )
.load(UsageExampleListViewAdapter.eatFoodyImages[0])
.listener( requestListener )
.error( R.drawable.cupcake )
.into( imageViewPlaceholder );
只有在listener的onException方法里返回false,R.drawable.cupcake才会显示出来。
13、设置取消或恢复请求
以下两个方法是为了保证用户界面的滑动流畅而设计的。当在ListView中加载图片的时候,如果用户滑动ListView的时候继续加载图片,就很有可能造成滑动不流畅、卡顿的现象,这是由于Activity需要同时处理滑动事件以及Glide加载图片。Glide为我们提供了这两个方法,让我们可以在ListView等滑动控件滑动的过程中控制Glide停止加载或继续加载,可以有效的保证界面操作的流畅。
//当列表在滑动的时候可以调用pauseRequests()取消请求
Glide.with(context).pauseRequests();
//当列表滑动停止时可以调用resumeRequests()恢复请求
Glide.with(context).resumeRequests();
// ListView滑动时触发的事件
lv.setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
switch (scrollState) {
case AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
case AbsListView.OnScrollListener.SCROLL_STATE_FLING:
// 当ListView处于滑动状态时,停止加载图片,保证操作界面流畅
Glide.with(MainActivity.this).pauseRequests();
break;
case AbsListView.OnScrollListener.SCROLL_STATE_IDLE:
// 当ListView处于静止状态时,继续加载图片
Glide.with(MainActivity.this).resumeRequests();
break;
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
}
});
14、获取缓存大小
new GetDiskCacheSizeTask(textView).execute(new File(getCacheDir(), DiskCache.Factory.DEFAULT_DISK_CACHE_DIR));
private class GetDiskCacheSizeTask extends AsyncTask {
private final TextView resultView;
public GetDiskCacheSizeTask(TextView resultView) {
this.resultView = resultView;
}
@Override
protected void onPreExecute() {
resultView.setText("Calculating...");
}
@Override
protected void onProgressUpdate(Long... values) {
super.onProgressUpdate(values);
}
@Override
protected Long doInBackground(File... dirs) {
try {
long totalSize = 0;
for (File dir : dirs) {
publishProgress(totalSize);
totalSize += calculateSize(dir);
}
return totalSize;
} catch (RuntimeException ex) {
final String message = String.format("Cannot get size of %s: %s", Arrays.toString(dirs), ex);
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
resultView.setText("error");
Toast.makeText(resultView.getContext(), message, Toast.LENGTH_LONG).show();
}
});
}
return 0L;
}
@Override
protected void onPostExecute(Long size) {
String sizeText = android.text.format.Formatter.formatFileSize(resultView.getContext(), size);
resultView.setText(sizeText);
}
private long calculateSize(File dir) {
if (dir == null) return 0;
if (!dir.isDirectory()) return dir.length();
long result = 0;
File[] children = dir.listFiles();
if (children != null)
for (File child : children)
result += calculateSize(child);
return result;
}
}
15、清除内存缓存
//可以在UI主线程中进行
Glide.get(this).clearMemory();
16、清除磁盘缓存
//需要在子线程中执行
Glide.get(this).clearDiskCache();
17、图片裁剪、模糊、滤镜等处理
变换
在图片显示出之前可以对图片进行变换处理。例如,如果你的app需要显示一张灰度图,但只能获取到一个原始全色彩的版本,你可以使用一个变换去将图片从有明艳色彩的版本转换成惨淡的黑白版。不要误会我们,变换不仅限于颜色。你可以改变图片的很多属性:大小、边框、色彩、像素点,等等!在之前介绍用Glide调整图片大小时,已经介绍了自带的两个
变换fitCenter和 centerCrop。这两个方案都有一个显著的特征,他们有他们自己的Glide转换方法,所以,这篇文章不再介绍了。
实现自己的变换
为了实现你自己自定义的变换,你需要创建一个新的类去实现变换接口。这个方法需要实现的内容还是相当复杂的,你需要深入探索Glide的内部结构才能让其工作好。如果你只是想要常规的图片(不包括Gif和视频)变换,我们建议只要处理抽象的BitmapTransformation类。它简化了相当多的实现,能覆盖95%的使用范围。
所以,让我们先看一下BitmapTransformation实现的一个例子。用Renderscript去模糊图片。我们可以用之前用过的代码去实现一个Glide变换。我们的框架必须继承BitmapTransformation类:
public class BlurTransformation extends BitmapTransformation {
public BlurTransformation(Context context) {
super( context );
}
@Override
protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
return null; // todo
}
@Override
public String getId() {
return null; // todo
}
}
现在,我们用前面文章的代码,借助Renderscript来实现图片的模糊处理。
public class BlurTransformation extends BitmapTransformation {
private RenderScript rs;
public BlurTransformation(Context context) {
super( context );
rs = RenderScript.create( context );
}
@Override
protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
Bitmap blurredBitmap = toTransform.copy( Bitmap.Config.ARGB_8888, true );
// Allocate memory for Renderscript to work with
Allocation input = Allocation.createFromBitmap(
rs,
blurredBitmap,
Allocation.MipmapControl.MIPMAP_FULL,
Allocation.USAGE_SHARED
);
Allocation output = Allocation.createTyped(rs, input.getType());
// Load up an instance of the specific script that we want to use.
ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
script.setInput(input);
// Set the blur radius
script.setRadius(10);
// Start the ScriptIntrinisicBlur
script.forEach(output);
// Copy the output to the blurred bitmap
output.copyTo(blurredBitmap);
toTransform.recycle();
return blurredBitmap;
}
@Override
public String getId() {
return "blur";
}
}
getId()方法为这个变换描述了一个独有的识别。Glide使用那个关键字作为缓存系统的一部分。防止出现异常问题,确保其唯一。
应用一个简单的变换
Glide有两个不同的方式进行变换。第一个是传递一个你的类的实例作为.transform()的参数。不管是图片还是gif,都可以进行变换。另一个则是使用.bitmapTransform(),
它只接受bitmap的变换。由于我们的实现都是基于bitmap,我们可以使用第一个:
Glide.with(context)
.load(eatFoodyImages[0])
.transform(new BlurTransformation(context))
//.bitmapTransform(new BlurTransformation(context)) // this would work too!
.into(imageView1);
这足够让Glide从网络下载的图片自动实现模糊算法。非常有用!
实现多重变换
通常,Glide的流接口(fluent interface)允许方法被连接在一起,然而变换并不是这样的。确保你只调用.transform()或者.bitmapTransform()一次,不然,之前的设置将会被覆盖!然而,你可以通过传递多个转换对象当作参数到.transform()(或者.bitmapTransform())中来进行多重变换:
Glide.with(context)
.load(eatFoodyImages[1])
.transform(new GreyscaleTransformation(context), new BlurTransformation(context))
.into(imageView2);
在这段代码中,我们先对图片进行了灰度变换,然后模糊处理。Glide会为你自动进行两个转换。牛逼吧!
提示:当你使用变换的时候,你不能使用.centerCrop()或者.fitCenter()。
Glide的变换集合
如果你已经对你的app里要用什么变换有了想法,在花点时间看看下面的库吧:Glide-transformations(https://github.com/wasabeef/glide-transformations)。它提供了许多变换的集合。值得去看一下你的idea是否已经被实现了。
这个库有2个不同版本。扩展库包括更多的变换,并且是用手机的GPU进行计算。需要一个额外的依赖,所以这两个版本的设置还有点不一样。你应当看看支持的变换的列表,再决定你需要用哪个版本。
Glide变换的设置
设置是很简单的!对于基本版,你可以在你的build.gradle里加一行:
dependencies {
compile 'jp.wasabeef:glide-transformations:2.0.0'
}
如果你想要使用GPU变换:
repositories {
jcenter()
mavenCentral()
}
dependencies {
compile 'jp.wasabeef:glide-transformations:2.0.0'
compile 'jp.co.cyberagent.android.gpuimage:gpuimage-library:1.3.0'
}
Glide变换的使用
在你同步了Android Studio的builde.gradle文件后,你已经可以进行使用变换集合了。使用方式与使用自定义变换一样。假如我们要用glide变换集合去模糊图片:
Glide
.with( context )
.load( eatFoodyImages[2] )
.bitmapTransform( new jp.wasabeef.glide.transformations.BlurTransformation( context, 25 ) )
.into( imageView3 );
你也可以像上面一样应用一组变换。一个单独的变换或者一组变换,.bitmapTransform()都可以接受!
示例:圆角处理
Glide.with(mContext)
.load(R.drawable.image_example)
.bitmapTransform(new RoundedCornersTransformation(mContext, 30, 0, RoundedCornersTransformation.CornerType.BOTTOM))
.into(imageView);
可实现Transformation接口,进行更灵活的图片处理,如进行简单地圆角处理。
public class RoundedCornersTransformation implements Transformation {
private BitmapPool mBitmapPool;
private int mRadius;
public RoundedCornersTransformation(Context context, int mRadius) {
this(Glide.get(context).getBitmapPool(), mRadius);
}
public RoundedCornersTransformation(BitmapPool mBitmapPool, int mRadius) {
this.mBitmapPool = mBitmapPool;
this.mRadius = mRadius;
}
@Override
public Resource transform(Resource resource, int outWidth, int outHeight) {
//从其包装类中拿出Bitmap
Bitmap source = resource.get();
int width = source.getWidth();
int height = source.getHeight();
Bitmap result = mBitmapPool.get(width, height, Bitmap.Config.ARGB_8888);
if (result == null) {
result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
}
Canvas canvas = new Canvas(result);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setShader(new BitmapShader(source, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP));
canvas.drawRoundRect(new RectF(0, 0, width, height), mRadius, mRadius, paint);
//返回包装成Resource的最终Bitmap
return BitmapResource.obtain(result, mBitmapPool);
}
@Override
public String getId() {
return "com.wiggins.glide.widget.GlideCircleTransform(radius=" + mRadius + ")";
}
}
自定义图片处理时为了避免创建大量Bitmap以及减少GC,可以考虑重用Bitmap,这就需要使用BitmapPool,例如从Bitmap池中取一个Bitmap,用这个Bitmap生成一个Canvas,然后在这个Canvas上画初始的Bitmap并使用Matrix、Paint或者Shader处理这张图片。为了有效并正确重用Bitmap需要遵循以下三条准则:
- 永远不要把transform()传给你的原始resource或原始Bitmap给recycle()了,更不要放回BitmapPool,因为这些都自动完成了。值得注意的是,任何从BitmapPool取出的用于自定义图片变换的辅助Bitmap,如果不经过transform()方法返回,就必须主动放回BitmapPool或者调用recycle()回收。
- 如果你从BitmapPool拿出多个Bitmap或不使用你从BitmapPool拿出的一个Bitmap,一定要返回extras给BitmapPool。
- 如果你的图片处理没有替换原始resource(例如由于一张图片已经匹配了你想要的尺寸,你需要提前返回),transform()方法就返回原始resource或原始Bitmap。例如:
private static class MyTransformation extends BitmapTransformation {
public MyTransformation(Context context) {
super(context);
}
@Override
protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
Bitmap result = pool.get(outWidth, outHeight, Bitmap.Config.ARGB_8888);
// 如果BitmapPool中找不到符合该条件的Bitmap,get()方法会返回null,就需要我们自己创建Bitmap了
if (result == null) {
// 如果想让Bitmap支持透明度,就需要使用ARGB_8888
result = Bitmap.createBitmap(outWidth, outHeight, Bitmap.Config.ARGB_8888);
}
//创建最终Bitmap的Canvas.
Canvas canvas = new Canvas(result);
Paint paint = new Paint();
paint.setAlpha(128);
// 将原始Bitmap处理后画到最终Bitmap中
canvas.drawBitmap(toTransform, 0, 0, paint);
// 由于我们的图片处理替换了原始Bitmap,就return我们新的Bitmap就行。
// Glide会自动帮我们回收原始Bitmap。
return result;
}
@Override
public String getId() {
// Return some id that uniquely identifies your transformation.
return "com.wiggins.glide.MyTransformation";
}
}
七、GlideModule使用
GlideModule是一个接口,全局改变Glide行为的一种方式,通过全局GlideModule配置Glide(GlideModule#applyOptions
),用GlideBuilder设置选项,用Glide注册ModelLoader(GlideModule#registerComponents
)等。你需要创建Glide的实例,来访问GlideBuilder。可以通过创建一个公共的类,实现GlideModule的接口来定制Glide。所有的GlideModule实现类必须是public的,并且只拥有一个空的构造器,以便在Glide延迟初始化时,可以通过反射将它们实例化。
1、自定义一个GlideModule
public class MyGlideModule implements GlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {
// Apply options to the builder here.
}
@Override
public void registerComponents(Context context, Glide glide) {
// register ModelLoaders here.
}
}
2、AndroidManifest.xml注册
//value是固定的
3、混淆处理
-keepnames class com.wiggins.glide.MyGlideModule
# or more generally
#-keep public class * implements com.bumptech.glide.module.GlideModule
4、多个GlideModule冲突问题
GlideModule不能指定调用顺序,所以应该避免不同的GlideModule之间有冲突的选项设置,可以考虑将所有的设置都放到一个GlideModule里面,或者排除掉某个manifest文件的某个Module。
5、更改Glide配置
已经知道如何使用Glide module去自定义Glide。现在我们看一下接口的第一个方法:applyOptions(Context context, GlideBuilder builder)
。这个方法将GlideBuilder的对象当作参数,并且是void返回类型,所以你在这个方法里能调用GlideBuilder可以用的方法。
.setMemoryCache(MemoryCache memoryCache)
.setBitmapPool(BitmapPool bitmapPool)
.setDiskCache(DiskCache.Factory diskCacheFactory)
.setDiskCacheService(ExecutorService service)
.setResizeService(ExecutorService service)
.setDecodeFormat(DecodeFormat decodeFormat)
显而易见,GlideBuilder对象可以让你访问到Glide的核心部分。使用文中的方法,你可以改变磁盘缓存、内存缓存等等。
5.1 设置Glide内存缓存大小
MemoryCache用来把resources缓存在内存里,以便能马上能拿出来显示。默认情况下Glide使用LruResourceCache,我们可以通过它的构造器设置最大缓存内存大小。
//获取系统分配给应用的总内存大小
int maxMemory = (int) Runtime.getRuntime().maxMemory();
//设置图片内存缓存占用八分之一
int memoryCacheSize = maxMemory / 8;
//设置内存缓存大小
builder.setMemoryCache(new LruResourceCache(memoryCacheSize));
获取默认的使用内存
//MemoryCache和BitmapPool的默认大小由MemorySizeCalculator类决定,MemorySizeCalculator会根据给定屏幕大小可用内存算出合适的缓存大小,
这也是推荐的缓存大小,我们可以根据这个推荐大小做出调整
MemorySizeCalculator calculator = new MemorySizeCalculator(context);
int defaultMemoryCacheSize = calculator.getMemoryCacheSize();
int defaultBitmapPoolSize = calculator.getBitmapPoolSize();
5.2 设置Glide磁盘缓存大小
//方式一
//指定的是数据的缓存地址
File cacheDir = context.getExternalCacheDir();
//最多可以缓存多少字节的数据
int diskCacheSize = 1024 * 1024 * 30;
//设置磁盘缓存大小
builder.setDiskCache(new DiskLruCacheFactory(cacheDir.getPath(), "glide", diskCacheSize));
//方式二
//存放在data/data/xxxx/cache/
builder.setDiskCache(new InternalCacheDiskCacheFactory(context, "glide", diskCacheSize));
//方式三
//存放在外置文件
builder.setDiskCache(new ExternalCacheDiskCacheFactory(context, "glide", diskCacheSize));
5.3 设置图片解码格式
默认格式RGB_565相对于ARGB_8888的4字节/像素可以节省一半的内存,但是图片质量就没那么高了,而且不支持透明度。
builder.setDecodeFormat(DecodeFormat.PREFER_ARGB_8888);
5.4 设置BitmapPool缓存内存大小
Bitmap池用来允许不同尺寸的Bitmap被重用,这可以显著地减少因为图片解码像素数组分配内存而引发的垃圾回收。默认情况下Glide使用LruBitmapPool作为Bitmap池,LruBitmapPool采用Lru算法保存最近使用的尺寸的Bitmap,我们可以通过它的构造器设置最大缓存内存大小。
builder.setBitmapPool(new LruBitmapPool(memoryCacheSize));
5.5 设置用来检索cache中没有的Resource的ExecutorService
//为了使缩略图请求正确工作,实现类必须把请求根据Priority优先级排好序
builder.setDiskCacheService(ExecutorService service);
builder.setResizeService(ExecutorService service);
6、集成网络框架
Glide包含一些小的、可选的集成库,目前Glide集成库当中包含了访问网络操作的Volley和OkHttp,也可以通过Glide的ModelLoader接口自己写网络请求。
6.1 将OkHttp集成到Glide当中
a)添加依赖
dependencies {
//OkHttp 2.x
compile 'com.github.bumptech.glide:okhttp-integration:1.4.0@aar'
compile 'com.squareup.okhttp:okhttp:2.7.5'
//OkHttp 3.x
compile 'com.github.bumptech.glide:okhttp3-integration:1.4.0@aar'
compile 'com.squareup.okhttp3:okhttp:3.2.0'
}
结尾的@aar可以将库中的AndroidManifest.xml文件一起导出,Gradle自动合并必要的GlideModule到AndroidManifest.xml,然后使用所集成的网络连接,所以不用再
将以下文本添加到项目的AndroidManifest.xml文件中:
b)创建OkHttp集成库的GlideModule
c)混淆配置
-keep class com.wiggins.glide.okhttp.OkHttpGlideModule
#or
-keep public class * implements com.bumptech.glide.module.GlideModule
注意:
a.OkHttp 2.x和OkHttp 3.x需使用不同的集成库;
b.Gradle会自动将OkHttpGlideModule合并到应用的manifest文件中;
c.如果你没有对所有的GlideModule配置混淆规则(即没有使用-keep public class * implements com.bumptech.glide.module.GlideModule
),则需要把OkHttp的GlideModule进行混淆配置:-keep class com.wiggins.glide.okhttp.OkHttpGlideModule
。
6.2 将Volley集成到Glide当中
a)添加依赖
dependencies {
compile 'com.github.bumptech.glide:volley-integration:1.4.0@aar'
compile 'com.mcxiaoke.volley:library:1.0.8'
}
b)创建Volley集成库的GlideModule
c)混淆配置
-keep class com.wiggins.glide.volley.VolleyGlideModule
#or
-keep public class * implements com.bumptech.glide.module.GlideModule
7、替换Glide组件、使用ModelLoader自定义数据源
7.1、替换Glide组件
替换Glide组件功能需要在自定义模块的registerComponents()方法中加入具体的替换逻辑。相比于更改Glide配置,替换Glide组件这个功能的难度就明显大了不少。Glide中的组件非常繁多,也非常复杂,但其实大多数情况下并不需要我们去做什么替换。不过,有一个组件却有着比较大的替换需求,那就是Glide的HTTP通讯组件。
替换Glide组件功能需要在自定义模块的GlideModule#registerComponents(Context context, Glide glide)
方法中加入具体的替换逻辑,需要在方法中调用Glide#register(Class
,其中modelClass表示 数据模型的类型,一般为GlideUrl
,Glide.with(context).load("url")底层就是将转化为了GlideUrl;resourceClass表示URL所指向的资源的类型,一般为InputStream。
默认情况下,Glide使用的是基于原生HttpURLConnection进行订制的HTTP通讯组件,但是现在大多数的Android开发者都更喜欢使用OkHttp,因此将Glide中的HTTP通讯组件修改成OkHttp的这个需求比较常见,那么今天我们也会以这个功能来作为例子进行讲解。
Model:数据模型,一般为URL字符串
Resource:URL所指向的网络资源
它主要和三个接口有关:
ModelLoader:数据模型Loader,将任意复杂的数据模型转化为具体的可被DataFetcher使用的数据类型。需要返回一个从url拉取数据的DataFetcher,泛型类型为上面指定的类型。
public interface ModelLoader {
DataFetcher getResourceFetcher(T var1, int var2, int var3);
}
ModelLoaderFactory:ModelLoader的工厂,build方法返回ModelLoader。
public interface ModelLoaderFactory {
ModelLoader build(Context var1, GenericLoaderFactory var2);
void teardown();
}
DataFetcher:获取 由model表示的resource要解码的数据
public interface DataFetcher {
T loadData(Priority var1) throws Exception; //重要方法,返回给glide的数据
void cleanup();
String getId();
void cancel();
}
默认地,Glide内部使用标准的HTTPUrlConnection去下载图片。Glide也提供两个集成库。这三个方法优点是在安全设置上都是相当严格的。唯一的不足之处是当你从一个使用HTTPS,还是self-signed的服务器下载图片时,Glide并不会下载或者显示图片,因为self-signed认证会被认为存在安全问题。
首先创建一个跳过SSL认证的OkHttpClient
public class UnsafeOkHttpClient {
public static OkHttpClient getUnsafeOkHttpClient() {
try {
// Create a trust manager that does not validate certificate chains
final TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
@Override
public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
//return null;//删除这行,多谢下面评论的几位小伙伴指出空指针问题,并提供解决方案。
}
}
};
// Install the all-trusting trust manager
final SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
// Create an ssl socket factory with our all-trusting manager
final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.setSslSocketFactory(sslSocketFactory);
okHttpClient.setProtocols(Arrays.asList(Protocol.HTTP_1_1));
okHttpClient.setHostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
});
return okHttpClient;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
创建的OkHttpClient关闭了所有的SSL认证检查。
集成到 Glide
Glide的OkHTTP集成库做的都是一样的工作,所以我们可以跟随他们的步骤。首先,我们需要在GlideModule里声明我们的定制。你应该想到,我们需要在registerComponents()方法里做适配。我们可以调用.register()方法去交换Glide基础构成。Glide使用一个ModelLoader去链接到数据模型创建一个具体的数据类型。我们的例子中,我们需要创建一个ModelLoader,它连接到一个URL,通过GlideUrl类响应并转化为输入流。Glide需要能够创建我们的新ModelLoader的实例,所以我们在.register()方法中传入一个工厂:
public class UnsafeOkHttpGlideModule implements GlideModule {
@Override
public void applyOptions(Context context, GlideBuilder glideBuilder) {
}
@Override
public void registerComponents(Context context, Glide glide) {
glide.register(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory());
}
}
public class OkHttpUrlLoader implements ModelLoader {
@Override
public DataFetcher getResourceFetcher(GlideUrl glideUrl, int i, int i1) {
return new OkHttpStreamFetcher(client, glideUrl);
}
private final OkHttpClient client;
public OkHttpUrlLoader(OkHttpClient client) {
this.client = client;
}
/**
* The default factory for {@link OkHttpUrlLoader}s.
*/
public static class Factory implements ModelLoaderFactory {
private static volatile OkHttpClient internalClient;
private OkHttpClient client;
private static OkHttpClient getInternalClient() {
if (internalClient == null) {
synchronized (Factory.class) {
if (internalClient == null) {
internalClient = UnsafeOkHttpClient.getUnsafeOkHttpClient();
}
}
}
return internalClient;
}
/**
* Constructor for a new Factory that runs requests using a static singleton client.
*/
public Factory() {
this(getInternalClient());
}
/**
* Constructor for a new Factory that runs requests using given client.
*/
public Factory(OkHttpClient client) {
this.client = client;
}
@Override
public ModelLoader build(Context context, GenericLoaderFactory factories) {
return new OkHttpUrlLoader(client);
}
@Override
public void teardown() {
// Do nothing, this instance doesn't own the client.
}
}
}
public class OkHttpStreamFetcher implements DataFetcher {
private final OkHttpClient client;
private final GlideUrl url;
private InputStream stream;
private ResponseBody responseBody;
public OkHttpStreamFetcher(OkHttpClient client, GlideUrl url) {
this.client = client;
this.url = url;
}
@Override
public InputStream loadData(Priority priority) throws Exception {
Request.Builder requestBuilder = new Request.Builder()
.url(url.toStringUrl());
for (Map.Entry headerEntry : url.getHeaders().entrySet()) {
String key = headerEntry.getKey();
requestBuilder.addHeader(key, headerEntry.getValue());
}
Request request = requestBuilder.build();
Response response = client.newCall(request).execute();
responseBody = response.body();
if (!response.isSuccessful()) {
throw new IOException("Request failed with code: " + response.code());
}
long contentLength = responseBody.contentLength();
stream = ContentLengthInputStream.obtain(responseBody.byteStream(), contentLength);
return stream;
}
@Override
public void cleanup() {
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
// Ignored
}
}
if (responseBody != null) {
responseBody.close();
}
}
@Override
public String getId() {
return url.getCacheKey();
}
@Override
public void cancel() {
}
}
7.2、使用ModelLoader自定义数据源
如果需要根据不同的要求请求不同尺寸不同质量的图片,这时我们就可以使用自定义数据源。
a)定义处理URL接口
public interface IDataModel {
String buildDataModelUrl(int width, int height);
}
b)实现不同的处理URL接口
public class JpgDataModel implements IDataModel {
private String dataModelUrl;
public JpgDataModel(String dataModelUrl) {
this.dataModelUrl = dataModelUrl;
}
@Override
public String buildDataModelUrl(int width, int height) {
//http://78re52.com1.z0.glb.clouddn.com/resource/gogopher.jpg?imageView2/1/w/200/h/200/format/jpg
return String.format("%s?imageView2/1/w/%d/h/%d/format/jpg", dataModelUrl, width, height);
}
}
c)实现ModelLoader
public class MyDataLoader extends BaseGlideUrlLoader {
public MyDataLoader(Context context) {
super(context);
}
public MyDataLoader(ModelLoader urlLoader) {
super(urlLoader, null);
}
@Override
protected String getUrl(IDataModel model, int width, int height) {
return model.buildDataModelUrl(width, height);
}
public static class Factory implements ModelLoaderFactory {
@Override
public ModelLoader build(Context context, GenericLoaderFactory factories) {
return new MyDataLoader(factories.buildModelLoader(GlideUrl.class, InputStream.class));
}
@Override
public void teardown() {
}
}
}
d)根据不同的要求采用不同的策略加载图片
//加载jpg图片
Glide.with(this).using(new MyDataLoader(this)).load(new JpgDataModel(imageUrl)).into(imageView);
e)如何跳过.using()
public class MyGlideModule implements GlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {
}
@Override
public void registerComponents(Context context, Glide glide) {
glide.register(IDataModel.class, InputStream.class, new MyDataLoader.Factory());
}
}
//加载jpg图片
Glide.with(this).load(new JpgDataModel(imageUrl)).into(imageView);
八、特点
使用简单;
可配置度及自适应程度高;
支持常见图片格式如jpg、png、gif、webp等;
支持多种数据源如网络、本地、资源、Uri等;
高效缓存策略(支持Memory和Disk图片缓存,默认Bitmap格式采用RGB_565内存使用至少减少一半);
生命周期集成(根据Context/Activity/Fragment/FragmentActivity生命周期自动管理请求);
高效处理Bitmap(使用BitmapPool使Bitmap复用,主动调用recycle回收需要回收的Bitmap,减小系统回收压力)。
九、优点
多样化媒体加载,支持Gif、WebP、Video及缩略图以等类型;
生命周期集成,提供多种方式与生命周期绑定,可以更好的让加载图片请求的生命周期动态管理起来;
高效的缓存策略
- 支持Memory和Disk图片缓存;
- 缓存相应大小的图片尺寸(Picasso只会缓存原始尺寸图片,而Glide会根据你ImageView的大小来缓存相应大小的图片尺寸,因此Glide会比Picasso加载的速度要快);
- 内存开销小(Picasso默认的是ARGB_8888格式,而Glide默认的Bitmap格式是RGB_565格式,这个内存开销大约可以减小一半)。
十、缺点
使用方法复杂:由于Glide功能强大,所以使用的方法非常多,其源码也相对的复杂;
包较大:Glide(v3.7.0)的大小约465kb。
十一、使用场景
需要更多的内容表现形式(如Gif、缩略图等);
更高的性能要求(缓存、加载速度等)。
十二、特别说明
1.ImageView的setTag问题
问题描述:如果使用Glide的into(imageView)为ImageView设置图片的同时使用ImageView的setTag(final Object tag)方法,将会导致java.lang.IllegalArgumentException: You must not call setTag() on a view Glide is targeting异常。因为Glide的ViewTarget中通过view.setTag(tag)和view.getTag()标记请求的,由于Android 4.0之前Tag存储在静态map里,如果Glide使用setTag(int key, final Object tag)方法标记请求则可能会导致内存泄露,所以Glide默认使用view.setTag(tag)标记请求,你就不能重复调用了。
解决办法:如果你需要为ImageView设置Tag,必须使用setTag(int key, final Object tag)及getTag(int key)方法,其中key必须是合法的资源id以确保key
的唯一性,典型做法就是在资源文件中声明type="id"的item资源。
2.placeholder()导致的图片变形问题
问题描述:使用.placeholder()方法在某些情况下会导致图片显示的时候出现图片变形的情况。这是因为Glide默认开启的crossFade动画导致的TransitionDrawable绘制异常,具体描述可以查看https://github.com/bumptech/glide/issues/363。根本原因就是你的placeholder图片和你要加载显示的图片宽高比不一样,而Android的
TransitionDrawable无法很好地处理不同宽高比的过渡问题,这是Android也是Glide的Bug。
解决办法:使用.dontAnimate()方法禁用过渡动画,或者使用animate()方法自己写动画,再或者自己修复TransitionDrawable的问题。
3.ImageView的资源回收问题
问题描述:默认情况下Glide会根据with()使用的Activity或Fragment的生命周期自动调整资源请求以及资源回收。但是如果有很占内存的Fragment或Activity不销毁而仅仅是隐藏视图,那么这些图片资源就没办法及时回收,即使是GC的时候。
解决办法:可以考虑使用WeakReference,如:
final WeakReference imageViewWeakReference = new WeakReference<>(imageView);
ImageView target = imageViewWeakReference.get();
if (target != null) {
Glide.with(context).load(uri).into(target);
}
4.由于Bitmap复用导致的在某些设备上图片错乱的问题
问题描述: Glide默认使用BitmapPool的方式对应用中用到的Bitmap进行复用,以减少频繁的内存申请和内存回收,而且默认使用的Bitmap模式为RGB565以减少内存开销。但在某些设备上(通常在Galaxy系列5.X设备上很容易复现)某些情况下会出现图片加载错乱的问题,具体详见https://github.com/bumptech/glide/issues/601。原因初步确定是OpenGL纹理渲染异常。
解决办法:GlideModule使用PREFER_ARGB_8888(Glide4.X已经默认使用该模式了),虽然内存占用比RGB565更多一点,但可以更好地处理有透明度Bitmap的复用问
题,或者禁用Bitmap复用setBitmapPool(new BitmapPoolAdapter())来修复这个问题(不推荐这种处理方式)。
5.异步线程完成后加载图片的崩溃问题
问题描述:通常情况下异步线程会被约束在Activity生命周期内,所以异步线程完成后使用Glide加载图片是没有问题的。但如果你的异步线程在Activity销毁时没
有取消掉,那么异步线程完成后Glide就无法为一个已销毁的Activity加载图片资源,抛出的异常如下(在with()方法中就进行判断并抛出异常):
java.lang.IllegalArgumentException: You cannot start a load for a destroyed activity
at com.bumptech.glide.manager.RequestManagerRetriever.assertNotDestroyed(RequestManagerRetriever.java:134)
at com.bumptech.glide.manager.RequestManagerRetriever.get(RequestManagerRetriever.java:102)
at com.bumptech.glide.Glide.with(Glide.java:653)
at com.frank.glidedemo.TestActivity.onGetDataCompleted(TestActivity.java:23)
at com.frank.glidedemo.TestActivity.access$000(TestActivity.java:10)
at com.frank.glidedemo.TestActivity$BackgroundTask.onPostExecute(TestActivity.java:46)
at com.frank.glidedemo.TestActivity$BackgroundTask.onPostExecute(TestActivity.java:28)
at android.os.AsyncTask.finish(AsyncTask.java:632)
at android.os.AsyncTask.access$600(AsyncTask.java:177)
at android.os.AsyncTask$InternalHandler.handleMessage(AsyncTask.java:645)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:157)
at android.app.ActivityThread.main(ActivityThread.java:5356)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:515)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1265)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1081)
at dalvik.system.NativeStart.main(Native Method)
解决办法:正确管理BackgroundThreads(异步线程),当Activity停止或销毁时,停止所有相关的异步线程及后续的UI操作,或者加载前使用isFinishing()
或isDestroyed()进行限制(不建议这种处理方式)。
参考文献
图片加载之Glide使用
Glide入门教程——1.入门简介