JS FA 调用 PA (展示本地相册图片) 原创 精华

ruchan
发布于 2022-3-3 21:23
浏览
4收藏

一、效果展示

::: hljs-center

JS FA 调用 PA (展示本地相册图片)-鸿蒙开发者社区

:::

二、项目介绍

$\qquad$本项目以 ArkUI(JS)开发界面,利用 JS FA 调用 JAVA PA 的接口功能,通过 JAVA 端拉取本地相册,选择图片后返回,并将图片复制到 JS IMAGE 组件可访问的目录下以及将图片路径返回至 JS 端。最终实现 JS 界面展示本地相册图片的功能。
 
$\qquad$项目提供两种方案,Internal Ability 以及 LocalParticleAbility ,两种方案思路基本一致,最终效果也都一样。
 
$\qquad$通过这个项目来和大家分享我目前使用 JS FA 调用 JAVA PA 功能的经验以及 JS 展示相册照片的思路,同时也希望各位能指出我代码中的不足。项目中部分代码来源于各教程。

三、代码结构展示

::: hljs-center

JS FA 调用 PA (展示本地相册图片)-鸿蒙开发者社区

:::

  1. Java 端
    • getPhotoInternalAbility:Internal Ability 方式与 JS 端交互
    • getPhotoLocalParticleAbility:LocalParticleAbility 方式与 JS 端交互
    • Utils:工具类,保存照片
    • MainAbility:主 Ability
  2. Js 端:基本展示页面(index.hml、index.css、index.js)

四、关键问题

4.1 权限问题

由于要访问用户媒体文件,需要申请媒体文件相关权限。

::: hljs-center

权限 说明
ohos.permission.MEDIA_LOCATION 允许应用访问用户媒体文件中的地理位置信息
ohos.permission.READ_MEDIA 允许应用读取用户外部存储中的媒体文件信息
ohos.permission.WRITE_MEDIA 允许应用读写用户外部存储中的媒体文件信息

:::

config.json 权限配置:

    "reqPermissions": [
      {
        "name": "ohos.permission.READ_MEDIA"
      },
      {
        "name": "ohos.permission.WRITE_MEDIA"
      },
      {
        "name": "ohos.permission.MEDIA_LOCATION"
      }
    ]

可选择添加动态申请权限,添加则有权限提示。

// 动态申请权限
private void requestPermissions() {
    String[] permissions =
            {
                    SystemPermission.MEDIA_LOCATION,
                    SystemPermission.WRITE_MEDIA,
                    SystemPermission.READ_MEDIA,
            };
    List<String> permissionFiltered =
            Arrays.stream(permissions)
                    .filter(permission -> verifySelfPermission(permission)
                            != IBundleManager.PERMISSION_GRANTED).collect(Collectors.toList());
    requestPermissionsFromUser(permissionFiltered.toArray(new String[permissionFiltered.size()]), 0);
}

4.2 页面跳转问题

$\qquad$与正常跳转其它 Ability 页面相同,我们需要通过 Intent 来指定启动目标。

$\qquad$此处我们根据需要选择图片的能力来构建 intent 并传入 startAbility,以达到跳转本地相册的目的。
 

Intent intent = new Intent();
Operation opt=new Intent.OperationBuilder()
        .withAction(IntentConstants.ACTION_CHOOSE)
        .build();
intent.setOperation(opt);
intent.addFlags(Intent.FLAG_NOT_OHOS_COMPONENT);
intent.setType("image/*");
abilityContext.startAbility(intent,code); // 此处 code 随意

$\qquad$关键点在于 abilityContext 的获取,很容易我们可以得知 startAbility 是抽象类 AbilityContext 中的方法,MainAbility 继承自 AceAbility ,而 AceAbility 的父类 Ability 则是 AbilityContext 的具体实现类。
 
$\qquad$我们操作的主线程(UI 线程)上下文应当是由 MainAbility 管理,此时我们需要从一个 Ability 跳转至另一个 Ability,很明显 abilityContext 的获取与 MainAbility 有关。
 
$\qquad$通过浏览官方文档不难发现,Internal Ability 以及 LocalParticleAbility 的实现均与 MainAbility 有着密切关系,这两种方式都存在注册与注销的方法,所需参数不正是 MainAbility ?

4.2.1 Internal Ability 方式

getPhotoInternalAbility

private AbilityContext abilityContext;

/**
 * Internal ability 注册接口。
 */
public static void register(AbilityContext abilityContext) {
    instance = new getPhotoInternalAbility();
    instance.onRegister(abilityContext);
}

private void onRegister(AbilityContext abilityContext) {
    this.abilityContext = abilityContext;
	...
}

MainAbility

@Override
public void onStart(Intent intent) {
    getPhotoInternalAbility.register(this);
    super.onStart(intent);
}

4.2.2 LocalParticleAbility

getPhotoLocalParticleAbility

// 重写注册方法即可
private AbilityContext abilityContext;

@Override
public void register(AceAbility ability) {
    abilityContext = ability;
    LocalParticleAbility.super.register(ability);
}

MainAbility

@Override
public void onStart(Intent intent) {
    super.onStart(intent);
    getPhotoLocalParticleAbility.getInstance().register(this);
}

4.2.3 JS 端

JS 侧只需简单调用 JAVA 有关跳转页面的代码即可。

例:

makeAction(bundleName, abilityName, code, abilityType, data) {
    const action = {};
    action.bundleName = bundleName;
    action.abilityName = abilityName;
    action.messageCode = code;
    action.abilityType = abilityType;
    action.data = data;
    action.syncOption = 0;
    return action;
},
async testGetPhotoInternalAbility() {
    const action = this.makeAction('cn.crcrc.arkui_example',
        'cn.crcrc.arkui_example.ability.getPhotoInternalAbility', 1001, 1, {});
    const result = await FeatureAbility.callAbility(action);
    console.info(result)
    if(result != null){
        this.avatarURL = result
    }
},
testGetPhotoLocalParticleAbility(){
    this.javaInterface = createLocalParticleAbility('cn.crcrc.arkui_example.ability.getPhotoLocalParticleAbility');
    this.javaInterface.getPhotoUri().then(result => {
        console.info(result);
        if(result != null){
            this.avatarURL = result
        }
    }, error => {
        console.error('testGetPhotoLocalParticleAbility error');
    });
}

4.3 返回值问题

$\qquad$AbilityContext.startAbility 的结果是可以通过 Ability 的回调方法 onAbilityResult 来获得。通过以上分析,显而易见,由于是通过 MainAbility 来 startAbility 的,那么 onAbilityResult 自然是需在 MainAbility 重写以获得结果。
 
$\qquad$此时便要解决将相册返回值传递给 Internal Ability 或 LocalParticleAbility,进而返回所得图片地址给 JS 端的问题。
 
$\qquad$此处我选择通过有序公共事件来传播消息。基本思路即在 MainAbility 的 onAbilityResult 中发布事件,附带相册图片 Uri 值;在 Internal Ability 或 LocalParticleAbility 中订阅事件,得到 Uri 值后通过 Utils 类将图片经自定义处理后存入 JS IMAGE 组件可访问的地址中。

JS FA 调用 PA (展示本地相册图片)-鸿蒙开发者社区

MainAbility

/*
 * 发布有序的公共事件
 */
private void orderlyEventPublish(String data) {
    HiLog.info(LABEL, "发布有序公共事件开始");
    //1.构建一个Intent对象,包含了自定义的事件的标识符
    Intent intent = new Intent();
    Operation oper = new Intent.OperationBuilder().withAction("cn.crcrc.arkui_example.event") //就是自定义的公共事件的标识
            .build();
    intent.setOperation(oper);
    //2.构建CommonEventData对象
    CommonEventData commonEventData = new CommonEventData(intent);

    //仅仅只有有序的公共事件,才能携带的两个专用属性,可选的参数,不是必须的
    commonEventData.setData(data);
    commonEventData.setCode(1001);
    //配置公共事件的对应权限
    CommonEventPublishInfo publishInfo = new CommonEventPublishInfo();
    publishInfo.setOrdered(true);

    //3.核心的发布事件的动作,发布的公共事件,有序的公共事件
    try {
        CommonEventManager.publishCommonEvent(commonEventData, publishInfo);
        HiLog.info(LABEL, "发布有序公共事件完成");
    } catch (RemoteException e) {
        e.printStackTrace();
    }
}

/**
 * 跳转回调
 */
@Override
protected void onAbilityResult(int requestCode, int resultCode, Intent resultData) {
    super.onAbilityResult(requestCode, resultCode, resultData);
    try {
        System.out.println("APP LOG resultData:" + resultData.getUriString());
        // 通过发布有序公共事件传递信息
        orderlyEventPublish(resultData.getUriString());
    } catch (Exception e) {
        orderlyEventPublish("fail");
        e.printStackTrace();
    }
}

Internal Ability 或 LocalParticleAbility 中订阅事件,例:

/**
 * 订阅事件
 */
private void subscribeCommonEvent() {
    if(!isSubscribe){
        HiLog.info(LABEL,"订阅开始:");

        //1.构建MatchingSkills对象
        MatchingSkills matchingSkills = new MatchingSkills();
        matchingSkills.addEvent("cn.crcrc.arkui_example.event"); //订阅自定义的公共事件

        //2.构建订阅信息对象
        CommonEventSubscribeInfo subscribeInfo = new CommonEventSubscribeInfo(matchingSkills);

        //3.构建订阅者对象
        subscriber = new CommonEventSubscriber(subscribeInfo){
            // 回调方法
            @Override
            public void onReceiveEvent(CommonEventData commonEventData) {
                HiLog.info(LABEL,"已接收公共事件");
                Boolean setImaDataResult = false;
                if(commonEventData.getData() != null)
                {
                    System.out.println("接收到数据:" + commonEventData.getData());
                    if(!commonEventData.getData().equals("fail"))
                    {
                        Uri uri = Uri.parse(commonEventData.getData());
                        long l = System.currentTimeMillis();
                        String fileName = String.valueOf(l);
                        setImaDataResult = utils.setImaData(uri,fileName);
                        photoUriString = "internal://app/" + String.valueOf(l) + ".jpg";
                        HiLog.info(LABEL,"js 访问图片路径:" + photoUriString);
                    }
                    latch.countDown();
                }else {
                    HiLog.info(LABEL,"已接收公共事件,但数据为空");
                    System.out.println("APP LOG 已接收公共事件,但数据为空");
                }

                // 目前 onReceiveEvent 只能在 ui 主线程上执行
                // 耗时任务派发到子线程异步执行,保证不阻塞 ui 线程
                final AsyncCommonEventResult result = goAsyncCommonEvent();
                Boolean finalSetImaDataResult = setImaDataResult;
                handler.postTask(new Runnable() {
                    @Override
                    public void run() {
                        if(finalSetImaDataResult){
                            HiLog.info(LABEL,"进行数据库操作");
                            // 存入数据库
                        }
                        HiLog.info(LABEL,"数据库操作完成");
                        result.finishCommonEvent();//结束事件
                    }
                });
            }
        };

        //4.订阅公共事件的核心动作
        try {
            CommonEventManager.subscribeCommonEvent(subscriber);
            isSubscribe = true;
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }else{
        HiLog.info(LABEL,"公共事件不能重复订阅");
    }
}

可将订阅以及取消订阅动作分别放入注册与注销动作中,以 LocalParticleAbility 为例

@Override
public void register(AceAbility ability) {
    abilityContext = ability;
    LocalParticleAbility.super.register(ability);

    utils = new Utils(abilityContext);  // 得到实例
    // 注册公共事件
    subscribeCommonEvent();
}

@Override
public void deregister(AceAbility ability) {
    abilityContext = null;
    LocalParticleAbility.super.deregister(ability);

    // 取消订阅
    try{
        CommonEventManager.unsubscribeCommonEvent(subscriber);
    }catch(RemoteException e) {
        HiLog.error(LABEL,"Exception occurred during unsubscribeCommonEvent invocation.");
    }
}

注意:
 

  1. JS 调用的 JAVA 端方法会在订阅事件中获取到图片地址之前返回,导致 JS 端获得空值。故此处我采取 CountDownLatch 方法以同步传递数据。
  2. 初始化订阅事件时,其中的回调方法会执行一次,故其中要加判断条件,防止 latch.countDown() 出错。
  3. 由于可能不选择图片直接返回,导致 onAbilityResult 的 resultData 为空,故 resultData.getUriString 应 catch 异常。

$\qquad$其中解决 JAVA 端提前返回值应该有其它解决方法,比如用 LocalParticleAbility 的 CallBack 方法开线程等图片返回值,并将其异步返回。或者 JAVA 端先将其存储下来, JS 端调 JAVA PA 相关函数查询对应键值即可。

五、调试日志

::: hljs-center

JS FA 调用 PA (展示本地相册图片)-鸿蒙开发者社区

JS FA 调用 PA (展示本地相册图片)-鸿蒙开发者社区

JS FA 调用 PA (展示本地相册图片)-鸿蒙开发者社区

:::

六、对比与总结

$\qquad$编写代码时,很容易体会到 LocalParticleAbility 方式较 Internal Ability 方便,尤其是 JS 端与 JAVA 端参数传递上。

$\qquad$LocalParticleAbility 方式就好像 JS 端直接调用其中方法实现功能一样,而 Internal Ability 需要在 onRemoteRequest 根据传递的 code 判断执行哪个方法。


$\qquad$此项目再结合下 JAVA 或 JS 端数据库功能,即可实现如下效果:
 
::: hljs-center

JS FA 调用 PA (展示本地相册图片)-鸿蒙开发者社区

:::


项目中有关图片转存的函数实现,查看上传的相应文件即可。

其实 JS 端亦可拉起图片选择能力,但需要返回值的对应方法要 API7 才支持。

$$ $$

var str = {
    "want": {
        "type": "image/*",
        "action": "android.intent.action.GET_CONTENT",
        "flags": wantConstant.Flags.FLAG_NOT_OHOS_COMPONENT
    },
};
featureAbility.startAbilityForResult(str, (error, data) => {
    if (error) {
        console.error('Operation failed. Cause: ' + error);
        return;
    }
    console.info('Operation succeeded: ' + data);
});

若您对此项目的功能实现有更好的思路或方法,望不吝赐教!

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
entry.zip 2.42M 97次下载
已于2022-3-3 21:25:05修改
4
收藏 4
回复
举报
1条回复
按时间正序
/
按时间倒序
红叶亦知秋
红叶亦知秋

楼主表情包收的挺有意思,代码也写的好

回复
2022-3-4 10:37:22
回复
    相关推荐