问题产生的背景:Launcher多应用页面,APP可以拖拽改变位置。在安装三方应用后,三方应用会在拖拽后,图标缩小,显示不正常。
分析过程:
1、分析XML文件,复现是谁变小了。到最后发现是图片变小了。ImageView大小没变。至此分析出是图片加载了问题。
2、分析Adapter中的图片加载方式,展示方式都是一样的,也未在拖拽时去修改图片。所以图片加载方式也是没有问题的。
3、发现图片数据的来源,分析来源。
分析log发现,显示异常的app,icon是 AdaptiveIconDrawable 类型的Drawable,其他正常的显示的app都是 BitmapDrawable 类型的Drawable。所以就可以知道问题出在这里。
那是AdaptiveIconDrawable 什么呢?android官网描述如下:AdaptiveIconDrawable | Android Developers
8.0以后Google添加了自适应图标AdaptiveIconDrawable,它是用来做自适应的。至于为什么会导致拖拽后,图标缩小这个问题,还不得而知。但是解决这个问题的思路就是,所有的都用 BitmapDrawable 渲染显示。
本地代码中获取APP信息的方法:
List<AppInfo> customList = new ArrayList<>();
//获取全部应用
PackageManager packageManager = context.getPackageManager();
Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
List<ResolveInfo> resolveInfos = packageManager.queryIntentActivities(mainIntent, 0);
//循环得到APP数据
for (ResolveInfo resolveInfo : resolveInfos) {
AppInfo bean = new AppInfo();
String appPackageName = resolveInfo.activityInfo.packageName;
if (!TextUtils.isEmpty(appPackageName)) {
String appName = resolveInfo.loadLabel(packageManager).toString();
String className = resolveInfo.activityInfo.name;
Drawable appIcon = resolveInfo.loadIcon(packageManager);
bean.setTitle(appName);// 应用名称
bean.setPackageName(appPackageName);// 包名
bean.setClassName(className);// 入口类名
bean.setIcon(appIcon);// 应用图标
}
customList.add(bean);
}
解决思路: 将 resolveInfo.loadIcon(packageManager);得到的Drable,转为BitmapDrawable。代码如下:
public static BitmapDrawable drawableToBitmap(Drawable drawable, Context context) {
if (drawable instanceof BitmapDrawable) {
return (BitmapDrawable) drawable;
}
Bitmap bitmap;
if ((drawable.getIntrinsicWidth() <= 0) || (drawable.getIntrinsicHeight() <= 0)) {
bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
} else {
bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
}
return new BitmapDrawable(context.getResources(), bitmap);
}
好了,代码跑进去,之前错误的APP不见了!为什么呢,哦,忘了使用画布去画出图片了,修改一下,再跑。
public static BitmapDrawable drawableToBitmap(Drawable drawable, Context context) {
if (drawable instanceof BitmapDrawable) {
return (BitmapDrawable) drawable;
}
Bitmap bitmap;
if ((drawable.getIntrinsicWidth() <= 0) || (drawable.getIntrinsicHeight() <= 0)) {
bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
} else {
bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
}
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return new BitmapDrawable(context.getResources(), bitmap);
}
好喔,测试没有问题!但是有大佬指出,在Bitmap使用的时候会导致内存泄露。!!!内存泄漏,赶紧解决,不可以存在!!!
根据大佬的,赶紧修改代码,再测!!!
public static BitmapDrawable drawableToBitmap(Drawable drawable, Context context) {
if (drawable instanceof BitmapDrawable) {
return (BitmapDrawable) drawable;
}
Bitmap bitmap;
try {
if ((drawable.getIntrinsicWidth() <= 0) || (drawable.getIntrinsicHeight() <= 0)) {
bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
} else {
bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
}
} catch (OutOfMemoryError error) {
LogUtil.error(TAG, "BitmapDrawable OutOfMemoryError");
bitmap = BitmapFactory.decodeResource(context.getResources(), R.mipmap.ic_launcher);
error.printStackTrace();
}
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
BitmapDrawable bitmapDrawable = new BitmapDrawable(context.getResources(), bitmap);
if (bitmap != null && !bitmap.isRecycled()){
bitmap.recycle();
}
return bitmapDrawable;
}
咦,怎么滑一下页面就有问题了呢,看看是啥问题?Crash了!!!报错:
trying to use a recycled bitmap android.graphics.Bitmap@41d
原因分析如下:如果缓存Drawable不为空,那么就返回原先使用的Drawable,在这里,这个Drawable引用的Bitmap已经被recycle了,但是Drawable这个缓存对象是一直并引用那个失效的Bitmap。
所以,在页面退出的时候去recycle bitmap,最总版代码如下:
List<AppInfo> customList = new ArrayList<>();
//获取全部应用
PackageManager packageManager = context.getPackageManager();
Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
List<ResolveInfo> resolveInfos = packageManager.queryIntentActivities(mainIntent, 0);
//循环得到APP数据
for (ResolveInfo resolveInfo : resolveInfos) {
AppInfo bean = new AppInfo();
String appPackageName = resolveInfo.activityInfo.packageName;
if (!TextUtils.isEmpty(appPackageName)) {
String appName = resolveInfo.loadLabel(packageManager).toString();
String className = resolveInfo.activityInfo.name;
//使用下面的方法读取icon,读取到应用的默认的ICON图标可能会是一个adaptive-icon类型
//AdaptiveIconDrawable需转为BitmapDrawable使用
Drawable icon = resolveInfo.loadIcon(packageManager);
BitmapDrawable appIcon = drawableToBitmap(icon, context);
bean.setTitle(appName);// 应用名称
bean.setPackageName(appPackageName);// 包名
bean.setClassName(className);// 入口类名
bean.setIcon(appIcon);// 应用图标
}
customList.add(bean);
}
//得到一个BitmapDrawable
public static BitmapDrawable drawableToBitmap(Drawable drawable, Context context) {
if (drawable instanceof BitmapDrawable) {
return (BitmapDrawable) drawable;
}
Bitmap bitmap;
try {
if ((drawable.getIntrinsicWidth() <= 0) || (drawable.getIntrinsicHeight() <= 0)) {
bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
} else {
bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
}
} catch (OutOfMemoryError error) {
LogUtil.error(TAG, "BitmapDrawable OutOfMemoryError");
bitmap = BitmapFactory.decodeResource(context.getResources(), R.mipmap.ic_launcher);
error.printStackTrace();
}
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
//不可以在此处调用 recycle() 方法。缓存Drawable不为空,那么就返回原先使用的Drawable。
//如果在这里recycle,这个Drawable引用的Bitmap已经被recycle了,但是Drawable这个缓存对象是一直并引用那个失效的Bitmap。
return new BitmapDrawable(context.getResources(), bitmap);
}