在HarmonyOS中使用Picasso
自从HarmonyOS2.0发布以来,各种争论从来没有断过,但是发现HarmonyOS上能无缝使用android的apk和HarmonyOS的hap,关于HarmonyOS的hap是如何运行的就有了一个大概猜测(应该还是运行在android上,做了一些桥接工作,毕竟在这么短的时间来搞出一个全新的系统不现实),正在最近在学习HarmonyOS的相关api,发现图片加载库居然没有, 需要自己造轮子,于是想着能不能通过使用android的图片加载库来印证自己的想法
android的图片加载库需要android的运行环境,如果能成功在HarmonyOS的App上跑起来的话,就能直接证明鸿蒙app就是运行在android之上
因为android 的类最终优化打包成dex文件,然后通过classloader进行加载,于是想着鸿蒙的类加载是否会有不同,通过断点调试查看鸿蒙的clasloader:
我们知道PathClassloader是通过DexBaseClassLoader继承而来,成员DexPathList对象中的dexElements用来存放apk的dex路径,一些热修方案就是通过将热修包的dex插入到dexElements中实现的功能热修复,从断点上看,鸿蒙应用的加载与android应用没有任何区别,dexElements中发现base.apk和应用本身的hap包,大胆推测应该是在hap安装时对hap生成相应的base.apk包,作为壳工程来负责桥接android运行时api(与应用加壳的原理有点像),如果确实如推测,那么鸿蒙的hap完全就是运行在android上,能使用android的图片加载就理所当然了
于是开始尝试使用android的图片加载库,这里选择Picasso的1.0版本,原因是相对与Glide,Fresco等库,Picasso的api相对简单,代码量更少,选择1.0原因是没有多余的特殊逻辑,但是图片加载的三级缓存等逻辑一样不少
Picasso加载图片的方法有两个:
public void into(Target target) {}
public void into(ImageView target) {}
使用ImageView肯定不行ImageView是android控件,鸿蒙这边布局使用的是独立一套UI,所以这里选择使用Target方法,直接监听图片加载结果:
即便如此,这里仍然遇到两个问题
Picasso需要android的Context对象
Target回调给出的Bitmap对象
对于第一个问题,如果鸿蒙应用最终运行的就是android,那么我们应该是能获取到Context对象的,于是断点查看鸿蒙应用的context(不是android的Context)里面有什么:
其中类型为MainAbilityShellActivity,这个应该就是页面作为桥接的Activity了,既然有android的context,那么我们可以通过反射获取到,这里就直接反射abilityShellContext对象:
static Object getRealContext(Context c) {
Context context = ((AbilitySlice) c).getContext();
Field contextField = null;
try {
contextField = context.getClass().getDeclaredField("abilityShellContext");
contextField.setAccessible(true);
Object realContext = contextField.get(context);
return realContext;
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
反射后发现这里编译不过:
因为Picasso的with方法需要android的Context对象传入,而我们的鸿蒙编译环境是获取不到android的Context的,虽然编译获取不到,但是我们运行时有, 所以这里可以自己写个类占坑,于是创建一个占坑的依赖库:
类空实现就行,保证我们编译能过,运行时会自动从真实android对象读,然后依赖中使用compileOnly,在编译时生效,相当于c的头文件作用,不然运行时或有类加载冲突
同理, 问题2的bitmap相同的方式处理:
发现编译已经ok
但是这样并不能编译成功,因为Picasso还依赖其他android组件,需要都创建占坑:
这样创建完毕后,编译没问题了,但是Target返回的Bitmap对象,而鸿蒙中的位图对象是PixelMap,这里需要将Bitmap转成字节数组然后再转换成鸿蒙PixelMap,通过鸿蒙的Image控件进行加载:
Picasso.with((android.content.Context) getRealContext(abilitySlice)).load(sampleItem.image).into(new Target() {
@Override
public void onSuccess(android.graphics.Bitmap bitmap) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
byte[] data = baos.toByteArray();
ImageSource imageSource = ImageSource.create(data, new ImageSource.SourceOptions());
PixelMap pixelMap = imageSource.createPixelmap(new ImageSource.DecodingOptions());
image.setPixelMap(pixelMap);
HiLog.error(label, "-------------Picasso onSuccess-------------: " + pixelMap);
}
@Override
public void onError() {
HiLog.error(label, "-------------Picasso onError-------------");
}
});
因为这里需要用到Bitmap的方法compress和枚举CompressFormat所以占坑类也要加载compress方法和枚举:
package android.graphics;
import java.io.OutputStream;
public class Bitmap {
public boolean compress(CompressFormat format, int quality, OutputStream stream) {
return false;
}
public enum CompressFormat {
JPEG (0),
@Deprecated
WEBP (2),
WEBP_LOSSY (3),
WEBP_LOSSLESS (4);
CompressFormat(int nativeInt) {
this.nativeInt = nativeInt;
}
final int nativeInt;
}
}
然后点击运行:
可以看到通过picasso加载的图片能正常展示了
结论
在手机上目前鸿蒙应用还是运行在android上,通过桥接与android 运行时关联, 不过目前鸿蒙的一些基本api还算比较全,后面逐步替换掉android布局也说不定,对于鸿蒙应用本身来说是无感知的