在HarmonyOS中使用Picasso

在HarmonyOS中使用Picasso

七月 19, 2021

自从HarmonyOS2.0发布以来,各种争论从来没有断过,但是发现HarmonyOS上能无缝使用android的apk和HarmonyOS的hap,关于HarmonyOS的hap是如何运行的就有了一个大概猜测(应该还是运行在android上,做了一些桥接工作,毕竟在这么短的时间来搞出一个全新的系统不现实),正在最近在学习HarmonyOS的相关api,发现图片加载库居然没有, 需要自己造轮子,于是想着能不能通过使用android的图片加载库来印证自己的想法

android的图片加载库需要android的运行环境,如果能成功在HarmonyOS的App上跑起来的话,就能直接证明鸿蒙app就是运行在android之上

因为android 的类最终优化打包成dex文件,然后通过classloader进行加载,于是想着鸿蒙的类加载是否会有不同,通过断点调试查看鸿蒙的clasloader:

image-20210719220535939

我们知道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方法,直接监听图片加载结果:

image-20210719222302766

即便如此,这里仍然遇到两个问题

  1. Picasso需要android的Context对象

  2. Target回调给出的Bitmap对象

对于第一个问题,如果鸿蒙应用最终运行的就是android,那么我们应该是能获取到Context对象的,于是断点查看鸿蒙应用的context(不是android的Context)里面有什么:

image-20210719222704692

其中类型为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;
    }

反射后发现这里编译不过:

image-20210719223102650

因为Picasso的with方法需要android的Context对象传入,而我们的鸿蒙编译环境是获取不到android的Context的,虽然编译获取不到,但是我们运行时有, 所以这里可以自己写个类占坑,于是创建一个占坑的依赖库:

image-20210719223610620

类空实现就行,保证我们编译能过,运行时会自动从真实android对象读,然后依赖中使用compileOnly,在编译时生效,相当于c的头文件作用,不然运行时或有类加载冲突

image-20210719223750298

同理, 问题2的bitmap相同的方式处理:

image-20210719224039707

发现编译已经ok

image-20210719224129367

但是这样并不能编译成功,因为Picasso还依赖其他android组件,需要都创建占坑:

image-20210719224308780

这样创建完毕后,编译没问题了,但是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;
    }
}

然后点击运行:

image-20210719225047575

可以看到通过picasso加载的图片能正常展示了

结论

在手机上目前鸿蒙应用还是运行在android上,通过桥接与android 运行时关联, 不过目前鸿蒙的一些基本api还算比较全,后面逐步替换掉android布局也说不定,对于鸿蒙应用本身来说是无感知的