HarmonyOS鸿蒙Next中Js调用Java,实现定位,调用,订阅,取消订阅 API要求4~7,远程模拟器P40

HarmonyOS鸿蒙Next中Js调用Java,实现定位,调用,订阅,取消订阅 API要求4~7,远程模拟器P40

一、建立Ability页,名称JsCallJavaServerAbility.java

二、config中设置如下

1、权限

"reqPermissions": [
    {
        "name": "ohos.permission.LOCATION_IN_BACKGROUND"
    },
    {
        "name": "ohos.permission.LOCATION"
    }
]

2、服务

"type": "service",
"name": "work.yjzll.seesat11.JsCallJavaServiceAbility",
"icon": "$media:icon",
"description": "$string:jscalljavaserviceability_description",
"type": "service"

三、Js文件

import router from '@system.router';
import prompt from '@system.prompt';

// abilityType: 0-Ability; 1-Internal Ability
const ABILITY_TYPE_EXTERNAL = 0;
const ABILITY_TYPE_INTERNAL = 1;  // 选用0

// syncOption(Optional, default sync): 0-Sync; 1-Async
const ACTION_SYNC = 0;
const ACTION_ASYNC = 1;

// CODE需要自定义,多个业务就有多个CODE,处理:调用1001 / 订阅1002  / 取消订阅1003
const ACTION_GET = 1001;
const SUBSCRIBE_EVENT = 1002;
const UNSUBSCRIBE_EVENT = 1003;

var mylongitude = 123.4;
var mylatitude = 11.1;

export default {
    data: {
        title: 'World'
    },
    getAction: async function () {
        console.info('03B00 回调前,经纬度:' + mylongitude + "   " + mylatitude);
        var actionData = {};
        // 此处的两个数据,格式 double要与 RequestParam.java 中的格式一致。比如:int,double,String,int顺序一致! 格式相对应!
        actionData.firstNum = mylongitude; //顺序一致! 格式相对应!
        actionData.secondNum = mylatitude; //顺序一致! 格式相对应!
        var action = {};
        action.bundleName = 'work.yjzll.seesat11';
        action.abilityName = 'JsCallJavaServiceAbility';
        action.messageCode = ACTION_GET; // 需要PA端约定一致
        action.data = actionData; //发送到 Ability 的数据,数据字段名称需要PA端约定。只有 FeatureAbility.callAbility 接口有
        action.abilityType = ABILITY_TYPE_EXTERNAL; // 0:Ability調用方式  // 1:Internal Ability調用方式 ABILITY_TYPE_INTERNAL
        action.syncOption = ACTION_SYNC;  // 0:同步方式,默认方式 // 1:异步方式
        var result = await FeatureAbility.callAbility(action);
        var ret = JSON.parse(result);
        if (ret.code == 0) {
            //以字符串形式显示调用返回的结果,abilityResult1和abilityResult2,名称和数据类型,要与myLocationAbility.java中的一致
            mylongitude = JSON.stringify(ret.abilityResult1); //名称和数据类型,要与myLocationAbility.java中的一致
            mylatitude = JSON.stringify(ret.abilityResult2); //名称和数据类型,要与myLocationAbility.java中的一致
            console.info('03B00 回调结果,经纬度:' + mylongitude + "   " + mylatitude);
        } else {
            console.error('03B00 回调结果,错误码:' + JSON.stringify(ret.code));
        }
    },
    mySubscribe: async function () {
        var action = {};
        action.bundleName = 'work.yjzll.seesat11';
        action.abilityName = 'JsCallJavaServiceAbility';
        action.messageCode = SUBSCRIBE_EVENT;
        action.abilityType = ABILITY_TYPE_EXTERNAL;
        action.syncOption = ACTION_SYNC;
        var result = await FeatureAbility.subscribeAbilityEvent(action, function (resultFunc) {
            var r1 = JSON.parse(resultFunc);
            var r2 = JSON.stringify(r1.data);
            // r2 = {"msg1":114.535703,"msg2":30.495864}
            var r3 = JSON.parse(r2);
            mylongitude = r3.msg1;
            mylatitude = r3.msg2;
            var ret = JSON.parse(result);
            if (ret.code == 0) {
                console.info('03B00 订阅公共事件 经度=' + mylongitude + " 纬度=" + mylatitude);
            } else {
                console.error('03B00 订阅公共事件 错误 ,结果 = ' + result);
            }
        });
    },
    myUnSubscribe: async function () {
        var action = {};
        action.bundleName = 'work.yjzll.seesat11';
        action.abilityName = 'JsCallJavaServiceAbility';
        action.messageCode = UNSUBSCRIBE_EVENT;
        action.abilityType = ABILITY_TYPE_EXTERNAL;
        action.syncOption = ACTION_SYNC;
        var result = await FeatureAbility.unsubscribeAbilityEvent(action);
        var ret = JSON.parse(result);
        if (ret.code == 0) {
            console.info('unsubscribe success, result: ' + result);
        } else {
            console.error('unsubscribe error, result: ' + result);
        }
    },
    showToast: function (msg) {
        prompt.showToast({
            message: msg,
            duration: 1000,
        });
    }
}

1、action.bundleName = ‘work.yjzll.seesat11’;

action.abilityName = 'JsCallJavaServiceAbility';

2、action.abilityType = ABILITY_TYPE_EXTERNAL; // 0:Ability調用方式

3、数据类型和名称要与RequestParamEvent.java和JsCallJavaServiceAbility.java保持一致

四、RequestParamEvent.java

package work.yjzll.seesat11;

public class RequestParamEvent {
    //此处的数据,格式 double要与 RequestParam.java 中的格式一致。比如:int,double,String,int顺序一致! 格式相对应!
    // myLocationAbility.java RequestParam.java pageSetting.js 三者数据要全部对应,顺序一致! 格式相对应!
    private double firstNum; //顺序一致! 格式相对应!
    private double secondNum; //顺序一致! 格式相对应!
    public double getFirstNum() {
        return firstNum;
    }
    public void setFirstNum(double firstNum) {
        this.firstNum = firstNum;
    }
    public double getSecondNum() {
        return secondNum;
    }
    public void setSecondNum(double secondNum) {
        this.secondNum = secondNum;
    }
}

五、JsCallJavaServiceAbility.java

package work.yjzll.seesat11;

import ohos.aafwk.ability.Ability;
import ohos.aafwk.content.Intent;
import ohos.agp.window.dialog.ToastDialog;
import ohos.app.Context;
import ohos.bundle.IBundleManager;
import ohos.event.commonevent.CommonEventSubscriber;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.eventhandler.InnerEvent;
import ohos.location.Locator;
import ohos.location.LocatorCallback;
import ohos.location.Location;
import ohos.location.LocationBean;
import ohos.location.LocatorResult;
import ohos.location.GeoAddress;
import ohos.location.GeoConvert;
import ohos.location.RequestParam;
import ohos.location.SubAdministrative;
import ohos.location.RoadName;
import ohos.location.Locality;
import ohos.location.Administrative;
import ohos.location.CountryName;
import ohos.location.IRemoteObject;
import ohos.location.RemoteObject;
import ohos.location.IRemoteBroker;
import ohos.location.MessageParcel;
import ohos.location.MessageOption;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
import ohos.utils.zson.ZSONObject;
import java.io.IOException;
import java.util.*;

public class JsCallJavaServiceAbility extends Ability {
    private static final String TAG = JsCallJavaServiceAbility.class.getSimpleName();
    // 定义日志标签
    private static final HiLogLabel LABEL_LOG =
        new HiLogLabel(HiLog.LOG_APP, 0xD003B00, TAG);

    // 定位使用-------------------------------------------------------
    private static final int EVENT_ID = 0x12;
    private static final String PERM_LOCATION = "ohos.permission.LOCATION";
    private final LocatorResult locatorResult = new LocatorResult();
    private Context context;
    private Locator locator;
    private GeoConvert geoConvert;
    private List<GeoAddress> gaList;
    private LocationBean locationDetails;
    //--------------------------------------------------------------

    double mylongitude = 7.0;
    double mylatitude = 0.0;

    @Override
    public void onStart(Intent intent) {
        HiLog.info(LABEL_LOG, "JsCallJavaServiceAbility::onStart");
        super.onStart(intent);
        // 初始化定位的参数
        registerLocation(this);
        // 启动定位功能
        registerLocationEvent();
    }

    @Override
    public void onBackground() {
        super.onBackground();
        HiLog.info(LABEL_LOG, "JsCallJavaServiceAbility::onBackground");
    }

    @Override
    public void onStop() {
        super.onStop();
        // 停止定位
        unregisterLocationEvent();
        HiLog.info(LABEL_LOG, "JsCallJavaServiceAbility::onStop");
    }

    @Override
    public void onCommand(Intent intent, boolean restart, int startId) {
    }

    @Override
    public IRemoteObject onConnect(Intent intent) {
        super.onConnect(intent);
        // 指定订阅功能的主管是谁?
        return remote.asObject();
    }

    @Override
    public void onDisconnect(Intent intent) {
    }

    //-------------------------定位功能---------------------------------------------------------
    private final EventHandler handler = new EventHandler(EventRunner.current()) {
        // 定位更新的事件处理
        @Override
        protected void processEvent(InnerEvent event) {
            if (event.eventId == EVENT_ID) {
                HiLog.info(LABEL_LOG, "processEvent消息接收到,并处理");
                //定位数据的去更新其他内容,本次没有要处理的
                notifyLocationChange(locationDetails);
                //调用订阅功能,实现数据回传和订阅消息发送,很重要
                remote.startNotify();
            }
        }
    };

    public void registerLocation(Context ability) {
        this.context = ability;
        // 定位权限检查
        requestPermission();
    }

    private void requestPermission() {
        // 检查定位权限
        if (context.verifySelfPermission(PERM_LOCATION) != IBundleManager.PERMISSION_GRANTED) {
            context.requestPermissionsFromUser(new String[]{PERM_LOCATION}, 0);
        }
    }

    private void registerLocationEvent() {
        if (hasPermissionGranted()) {
            // 启动定位功能
            int timeInterval = 0;
            int distanceInterval = 0;
            locator = new Locator(context);
            RequestParam requestParam = new RequestParam(RequestParam.PRIORITY_ACCURACY, timeInterval, distanceInterval);
            locator.startLocating(requestParam, locatorResult);
        }
    }

    private void unregisterLocationEvent() {
        if (locator != null) {
            // 停止定位
            locator.stopLocating(locatorResult);
        }
    }

    private boolean hasPermissionGranted() {
        return context.verifySelfPermission(PERM_LOCATION) == IBundleManager.PERMISSION_GRANTED;
    }

    private class LocatorResult implements LocatorCallback {
        @Override
        public void onLocationReport(Location location) {
            // 定位数据更新
            mylongitude = location.getLongitude();
            mylatitude = location.getLatitude();
            HiLog.info(LABEL_LOG, "onLocationReport 报告: " + mylongitude + " 和 " + mylatitude);
            setLocation(location);
        }

        @Override
        public void onStatusChanged(int statusCode) {
            HiLog.info(LABEL_LOG, "MyLocatorCallback 状态改变:" + statusCode);
        }

        @Override
        public void onErrorReport(int errorCode) {
            HiLog.info(LABEL_LOG, "MyLocatorCallback 异常 : " + errorCode);
        }
    }

    private void notifyLocationChange(LocationBean locationDetails) {
        update(locationDetails);
    }

    private void update(LocationBean locationDetails) {
        // 取得定位数据
        //locationDetails.getLatitude();
        //locationDetails.getLongitude();
        //locationDetails.getSpeed();
        //locationDetails.getDirection();
        //locationDetails.getTime();
        //SubAdministrative = locationDetails.getSubAdministrative();
        //RoadName = locationDetails.getRoadName();
        //Locality = locationDetails.getLocality();
        //Administrative = locationDetails.getAdministrative();
        //CountryName = locationDetails.getCountryName();
    }

    private void setLocation(Location location) {
        // 更新地理信息
        if (location != null) {
            Date date = new Date(location.getTimeStamp());
            locationDetails = new LocationBean();
            locationDetails.setTime(date.toString());
            locationDetails.setLatitude(location.getLatitude());
            locationDetails.setLongitude(location.getLongitude());
            locationDetails.setPrecision(location.getAccuracy());
            locationDetails.setSpeed(location.getSpeed());
            locationDetails.setDirection(location.getDirection());
            fillGeoInfo(locationDetails, location.getLatitude(), location.getLongitude());
            // 定位改变后,由handler发送自定义消息,processEvent处理该消息
            handler.sendEvent(EVENT_ID);
            gaList.clear();
        } else {
            HiLog.info(LABEL_LOG, "EventNotifier 或 Location response 为空");
            //new ToastDialog(context).setText("EventNotifier 或 Location response 为空").show();
        }
    }

    private void fillGeoInfo(LocationBean locationDetails, double geoLatitude, double geoLongitude) {
        if (geoConvert == null) {
            geoConvert = new GeoConvert();
        }
        if (geoConvert.isGeoAvailable()) {
            try {
                gaList = geoConvert.getAddressFromLocation(geoLatitude, geoLongitude, 1);
                if (!gaList.isEmpty()) {
                    GeoAddress geoAddress = gaList.get(0);
                    setGeo(locationDetails, geoAddress);
                }
            } catch (IllegalArgumentException | IOException e) {
                HiLog.error(LABEL_LOG, "fillGeoInfo 异常");
            }
        }
    }

    private void setGeo(LocationBean locationDetails, GeoAddress geoAddress) {
        locationDetails.setRoadName(checkIfNullOrEmpty(geoAddress.getRoadName()));
        locationDetails.setLocality(checkIfNullOrEmpty(geoAddress.getLocality()));
        locationDetails.setSubAdministrative(checkIfNullOrEmpty(geoAddress.getSubAdministrativeArea()));
        locationDetails.setAdministrative(checkIfNullOrEmpty(geoAddress.getAdministrativeArea()));
        locationDetails.setCountryName(checkIfNullOrEmpty(geoAddress.getCountryName()));
    }

    private String checkIfNullOrEmpty(String value) {
        if (value == null || value.isEmpty()) {
            return "NA";
        }
        return value;
    }

    // -------------------------------订阅----------------------------------------------------
    private CommonEventSubscriber subscriber; // 订阅公共事件使用,此处不使用
    // 这里的 remote 是订阅功能的主管
    private MyRemote remote = new MyRemote("JsCallJavaServiceAbility");
    private static final int SUCCESS = 0;
    private static final int ERROR = 1;

    class MyRemote extends RemoteObject implements IRemoteBroker {
        private static final int ACTION_GET = 1001; // JS端发送的业务请求编码,与本PA端的定义保持一致
        private static final int SUBSCRIBE_EVENT = 1002;
        private static final int UNSUBSCRIBE_EVENT = 1003;
        // 支持多FA订阅,如果仅支持单FA订阅,可直接使用变量存储:private IRemoteObject remoteObjectHandler;
        private Set<IRemoteObject> remoteObjectHandlers = new HashSet<>();
        private Thread thread = null;  // 每隔0.5秒自动发送方式使用,此处不使用

        public MyRemote(String descriptor) {
            super(descriptor);
        }

        @Override
        public boolean onRemoteRequest(int code, MessageParcel data, MessageParcel reply, MessageOption option) {
            double num1 = 0, num2 = 0;
            switch (code) {
                case ACTION_GET: {
                    // 读JSON格式字符串,如{"code":0,"data": {"firstNum":123.4,"secondNum":11.1}}
                    String dataStr = data.readString();
                    // dataStr = {"firstNum":123.4,"secondNum":11.1}
                    // RequestParamEvent 指的是 RequestParamEvent.java 文件中的类
                    RequestParamEvent param = new RequestParamEvent();
                    try {
                        //此处已获得传入的值,两个double数,存入param
                        param = ZSONObject.stringToClass(dataStr, RequestParamEvent.class);
                        num1 = param.getFirstNum();
                        num2 = param.getSecondNum();
                        HiLog.info(LABEL_LOG, "传入二个数:" + num1 + " 和:" + num2);
                    } catch (RuntimeException e) {
                        HiLog.info(LABEL_LOG, "传入失败");
                    }
                    // 此处通过其他计算,得到计算结果,准备返回num1,num2
                    // 返回结果当前仅支持String,对于复杂结构可以序列化为ZSON字符串上报
                    Map<String, Object> result = new HashMap<>();
                    result.put("code", SUCCESS);
                    //以字符串形式显示调用返回的结果,abilityResult1和abilityResult2,名称和数据类型,与pageSetting.js中的一致
                    //更多数据,一条条接着加上即可
                    result.put("abilityResult1", mylongitude); //名称和数据类型,与js中的一致
                    result.put("abilityResult2", mylatitude); //名称和数据类型,与js中的一致
                    // SYNC
                    if (option.getFlags() == MessageOption.TF_SYNC) {
                        reply.writeString(ZSONObject.toZSONString(result));
                    } else {
                        // ASYNC
                        MessageParcel responseData = MessageParcel.obtain();
                        responseData.writeString(ZSONObject.toZSONString(result));
                        IRemoteObject remoteReply = reply.readRemoteObject();
                        try {
                            remoteReply.sendRequest(SUCCESS, responseData, MessageParcel.obtain(), new MessageOption());
                        } catch (RemoteException exception) {
                            return false;
                        } finally {
                            responseData.reclaim();
                        }
                    }
                    break;
                }
                case SUBSCRIBE_EVENT: {
                    // 如果仅支持单FA订阅,可直接覆盖:remoteObjectHandler = data.readRemoteObject();
                    remoteObjectHandlers.add(data.readRemoteObject());
                    // 此处接受数据没有处理,如果需要,可按上面的方式处理:num1,num2
                    startNotify();
                    Map<String, Object> result = new HashMap<>();
                    result.put("code", SUCCESS);
                    reply.writeString(ZSONObject.toZSONString(result));
                    break;
                }
                case UNSUBSCRIBE_EVENT: {
                    // 如果仅支持单FA订阅,可直接置空:remoteObjectHandler = null;
                    remoteObjectHandlers.clear();
                    Map<String, Object> result = new HashMap<>();
                    result.put("code", SUCCESS);
                    reply.writeString(ZSONObject.toZSONString(result));
                    break;
                }
                default: {
                    Map<String, Object> result = new HashMap<>();
                    result.put("FA调用PA错误", ERROR);
                    reply.writeString(ZSONObject.toZSONString(result));
                    return false;
                }
            }
            return true;
        }
    }

    // 此方法是根据自定义消息,被handler的processEvent调用 remote.startNotify(),即本方法,传送sendRequest并返回订阅数据
    public void startNotify() {
        try {
            testReportEvent();
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    public void testReportEvent() throws RemoteException {
        HiLog.info(LABEL_LOG, "testReportEvent 开始处理");
        MessageParcel data = MessageParcel.obtain();
        MessageParcel reply = MessageParcel.obtain();
        MessageOption option = new MessageOption();
        Map<String, Object> event = new HashMap<>();
        event.put("msg1", mylongitude); //订阅返回值
        event.put("msg2", mylatitude); //订阅返回值
        data.writeString(ZSONObject.toZSONString(event));
        // 如果仅支持单FA订阅,可直接触发回调:remoteObjectHandler.sendRequest(SUCCESS, data, reply, option);
        for (IRemoteObject item : remoteObjectHandlers) {
            item.sendRequest(SUCCESS, data, reply, option);
        }
        reply.reclaim();
        data.reclaim();
    }

    @Override
    public IRemoteObject asObject() {
        return this;
    }
}

1、订阅数据返回经纬度有三种方法 A、目前文档中使用,通过

private final EventHandler handler = new EventHandler(EventRunner.current()) {
    // 定位更新的事件处理
    @Override
    protected void processEvent(InnerEvent event) {
        if (event.eventId == EVENT_ID) {
            HiLog.info(LABEL_LOG, "processEvent消息接收到,并处理");
            //定位数据的去更新其他内容,本次没有要处理的
            notifyLocationChange(locationDetails);
            //调用订阅功能,实现数据回传和订阅消息发送,很重要
            remote.startNotify();
        }
    }
};

中的 remote.startNotify();实现订阅数据返回

B、第二种方法,是接收公共事件CommonEventSupport.COMMON_EVENT_LOCATION_MODE_STATE_CHANGED 目前看好像无效,可能测试版吧

C、第三种方法,是每隔0.5秒自动返回订阅数据,不管数据有没有变化


更多关于HarmonyOS鸿蒙Next中Js调用Java,实现定位,调用,订阅,取消订阅 API要求4~7,远程模拟器P40的实战教程也可以访问 https://www.itying.com/category-93-b0.html

8 回复

很详细

更多关于HarmonyOS鸿蒙Next中Js调用Java,实现定位,调用,订阅,取消订阅 API要求4~7,远程模拟器P40的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


你好,我在订阅时遇到一个问题,debug调试断点走到onRemoteRequest方法时,第一次进来code值为1079135572(重启再debug仍然是这种数字),执行default代码块,F8放开,第二次进断点,code值又是我想要的1001,不明白debug为啥要这样操作两次,才得到想要的结果,求大佬指点一下

我是新手中的新手,

哈哈,大佬谦虚了,一起学习,

学习了,赞一个!

补充mainAbility权限申请

```java
package work.yjzll.seesat11;

import ohos.ace.ability.AceAbility;
import ohos.aafwk.content.Intent;
import java.util.ArrayList;
import java.util.List;

public class MainAbility extends AceAbility {

    @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
        // AceAbility 不需要注册
        requestPermissions();
    }

    @Override
    public void onStop() {
        super.onStop();
    }

    private void requestPermissions(){
        // 需要处理定位等敏感权限
        String[] permissions = {
            "ohos.permission.INTERNET",
            "ohos.permission.LOCATION",
            "ohos.permission.LOCATION_IN_BACKGROUND"
        };
        // 可动态授权的权限
        List<String> permissionsToProcess = new ArrayList<>();
        // 遍历需要处理的权限
        for (String permission : permissions){
            if ( verifySelfPermission(permission) != 0 && canRequestPermission(permission)){
                permissionsToProcess.add(permission);
            }
        }
        // 弹窗申请权限
        requestPermissionsFromUser(permissionsToProcess.toArray(new String[0]), 0);
    }
}

在HarmonyOS鸿蒙Next中,通过Js调用Java实现定位、调用、订阅、取消订阅功能,可以使用鸿蒙提供的JS FA(Feature Ability)与Java PA(Particle Ability)交互机制。具体步骤如下:

  1. 定位功能:在Java PA中实现定位逻辑,使用鸿蒙的LocationManager API获取设备位置信息。Js FA通过FeatureAbility.callAbility方法调用Java PA的定位接口,获取位置数据。

  2. 调用功能:在Java PA中定义需要调用的方法,Js FA通过FeatureAbility.callAbility方法传递参数并调用Java PA中的方法,获取返回结果。

  3. 订阅功能:在Java PA中实现订阅逻辑,使用鸿蒙的CommonEventManager API订阅系统事件。Js FA通过FeatureAbility.callAbility方法调用Java PA的订阅接口,注册事件监听。

  4. 取消订阅功能:在Java PA中实现取消订阅逻辑,Js FA通过FeatureAbility.callAbility方法调用Java PA的取消订阅接口,移除事件监听。

在远程模拟器P40上测试时,确保模拟器已配置正确的网络和位置权限,以便正常调用和测试相关API。

在HarmonyOS鸿蒙Next中,通过Js调用Java实现定位、调用、订阅和取消订阅功能,需使用API 4~7。首先,在Java层创建定位服务类,并通过@JSExport注解暴露方法。在Js中,使用import导入Java类,并调用相应方法。对于远程模拟器P40,确保设备连接正常,通过IDE配置模拟器环境。具体实现包括初始化定位服务、调用定位方法、订阅位置更新及取消订阅。详细步骤可参考官方文档和示例代码。

回到顶部