HarmonyOS 鸿蒙Next应用(Java)开发实战之天气预报App(三)

发布于 1周前 作者 phonegap100 来自 鸿蒙OS

HarmonyOS 鸿蒙Next应用(Java)开发实战之天气预报App(三)

前言

上一篇见 HarmonyOS应用(Java)开发实战之天气预报App(二)-华为开发者论坛 (huawei.com),这一篇我们将完成整个App的构建。

参考资料

《鸿蒙操作系统开发入门经典》 徐礼文 清华大学出版社(2021-07出版)

正文

多线程处理事件和网络请求

鸿蒙开发禁止在主线程处理耗时的操作,这里我们通过EventHandler实现多线程处理事件和获取数据,创建一个用来切换线程的帮助类

public class MyEventHandler {
    //在主线程执行
    public static void runMainUI(Runnable runnable) {
        //切换到主线程
        EventRunner runner = EventRunner.getMainEventRunner();
        //获取主线程中的runner
        EventHandler eventHandler = new EventHandler(runner);
        eventHandler.postSyncTask(runnable);
    }

    //在子线程执行任务
    public static void runBackGround(Runnable runnable) {
        //创建一个子线程
        EventRunner runner = EventRunner.create(true);
        EventHandler eventHandler = new EventHandler(runner);
        eventHandler.postTask(runnable, 0, EventHandler.Priority.IMMEDIATE);
    }
}

格式化JSON数据

在应用级build.gradle文件中添加以下依赖,引入Jackson框架,用来解析JSON数据

implementation 'com.fasterxml.jackson.core:jackson-annotations:2.12.1'
implementation 'com.fasterxml.jackson.core:jackson-core:2.12.1'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.12.1'

在使用Jackson把下面的JSON数据转换成Java对象前,需要创建一个和JSON相同格式的Java类

{
    "status": "1",
    "count": "1",
    "info": "OK",
    "infocode": "10000",
    "lives": [
        {
            "province": "北京",
            "city": "北京市",
            "adcode": "110000",
            "weather": "多云",
            "temperature": "13",
            "winddirection": "南",
            "windpower": "4",
            "humidity": "44",
            "reporttime": "2022-04-05 22:02:47"
        }
    ]
}

依据上面接口返回的JSON数据格式创建Weather类

class Weather {
    public String status;
    public String count;
    public String info;
    public String infocode;
    public List<Lives> lives;

    static class Lives {
        public String province;
        public String city;
        public String adcode;
        public String weather;
        public String temperature;
        public String winddirection;
        public String windpower;
        public String humidity;
        public String reporttime;
    }
}

使用ObjectMapper方法把JSON字符串转换成Java对象

ObjectMapper mapper = new ObjectMapper();
try {
    Weather weather = mapper.readValue(jsonStr, Weather.class);
    ...
} catch (JsonProcessingException e) {
    e.printStackTrace();
}

封装网络访问类获取网络数据

单击每行中的城市名称,获取该城市的天气数据信息,通过城市的编号调用第三方数据接口,并返回单击城市的天气信息。

在本案例中,我们使用高德地图的天气接口获取数据。首先我们在 我的应用 | 高德控制台 (amap.com) 上创建新应用,如下图所示,选择Web服务,完成后会得到一个Key

定义接口IMyNet,定义get方法和回调接口NetListener

public interface IMyNet {
    void get(String URL, Map<String, String> params, NetListener listener);

    interface NetListener {
        void onSuccess(String jsonStr);

        void onError(String error);
    }
}

创建一个用来处理URL解析的工具类

public class NetUtil {
    public static String buildParams(String URL, Map<String, String> params) {
        if (params == null) {
            return URL;
        }
        StringBuilder stringBuilder = new StringBuilder(URL);
        boolean isFirst = true;
        for (String key : params.keySet()) {
            String value = params.get(key);
            if (key != null && value != null) {
                if (isFirst) {
                    isFirst = false;
                    stringBuilder.append("?");
                } else {
                    stringBuilder.append("&");
                }
            }
            stringBuilder.append(key)
                    .append("=")
                    .append(value);
        }
        return stringBuilder.toString();
    }

    public static void close(Closeable closeable) {
        if (closeable != null) {
            try {
                closeable.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

创建MyNet类,实现IMyNet接口,实现get方法

public class MyNet implements IMyNet {
    private NetManager netManager;

    public MyNet() {
        netManager = NetManager.getInstance(null);
    }

    @Override
    public void get(String URL, Map<String, String> params, NetListener listener) {
        String finalURL = NetUtil.buildParams(URL, params);
        MyEventHandler.runBackGround(new Runnable() {
            @Override
            public void run() {
                doGet(finalURL, listener);
            }
        });
    }

    private void doGet(String finalURL, NetListener listener) {
        NetHandle netHandle = netManager.getDefaultNet();
        HttpURLConnection connection = null;
        InputStream inputStream = null;
        ByteArrayOutputStream bos = null;

        try {
            URL url = new URL(finalURL);
            URLConnection urlConnection = netHandle.openConnection(url, Proxy.NO_PROXY);
            if (urlConnection instanceof HttpURLConnection) {
                connection = (HttpURLConnection) urlConnection;
            }
            connection.setRequestMethod("GET");
            connection.connect();
            if (connection.getResponseCode() == 200) {
                inputStream = connection.getInputStream();
                bos = new ByteArrayOutputStream();
                int readLen;
                byte[] bytes = new byte[1024];
                while ((readLen = inputStream.read(bytes)) != -1) {
                    bos.write(bytes, 0, readLen);
                }
                String result = bos.toString();
                MyEventHandler.runMainUI(new Runnable() {
                    @Override
                    public void run() {
                        listener.onSuccess(result);
                    }
                });
            } else {
                listener.onError("请求错误:" + connection.getResponseCode());
            }
        } catch (Exception e) {
            listener.onError("请求错误:" + e.toString());
        } finally {
            if (connection != null) {
                connection.disconnect();
                NetUtil.close(inputStream);
                NetUtil.close(bos);
            }
        }
    }
}

实现loadData方法,使用封装类MyNet,通过类的get方法获取接口的数据

private void loadData(CityModel co) {
    Map<String, String> params = new HashMap<>();
    params.put("city", co.cityCode);
    params.put("key", "申请的key字符串");
    MyNet myNet = new MyNet();
    myNet.get("https://restapi.amap.com/v3/weather/weatherInfo", params, new IMyNet.NetListener() {
        @Override
        public void onSuccess(String jsonStr) {
            ObjectMapper mapper = new ObjectMapper();
            try {
                Weather weather = mapper.readValue(jsonStr, Weather.class);
                bindWeatherView(weather);
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onError(String error) {

        }
    });
}

获取数据后,绑定到页面

private void bindWeatherView(Weather weather) {
    //北京:东风≤3,空气湿度:89
    String txtTips = weather.lives.get(0).city + ":" + weather.lives.get(0).winddirection + "风" + weather.lives.get(0).windpower + "空气湿度:" + weather.lives.get(0).humidity;
    Text tips = findComponentById(ResourceTable.Id_tips);
    tips.setText(txtTips);

    Text temp = findComponentById(ResourceTable.Id_temperature);
    temp.setText(weather.lives.get(0).temperature + "℃");

    Text weth = findComponentById(ResourceTable.Id_weather);
    weth.setText(weather.lives.get(0).weather);

    Image wicon = findComponentById(ResourceTable.Id_wicon);
    if (weather.lives.get(0).weather.contains("晴")) {
        wicon.setImageAndDecodeBounds(ResourceTable.Media_qing);
    } else if (weather.lives.get(0).weather.contains("雨")) {
        wicon.setImageAndDecodeBounds(ResourceTable.Media_baoyu);
    } else if (weather.lives.get(0).weather.contains("雪")) {
        wicon.setImageAndDecodeBounds(ResourceTable.Media_xue);
    }
}

通过设备地理定位获取默认天气

App启动后,默认通过设备地理位置定位并获取坐标信息,然后把坐标信息转换成位置信息。

这里需要使用两个类:Locator类和GeoConvert类。

创建MyLocatorCallback类,实现LocatorCallback回调接口

//获取地理位置成功回调类
private class MyLocatorCallback implements LocatorCallback {

    //返回成功的地理位置信息
    @Override
    public void onLocationReport(Location location) {
        //获取纬度
        double latitude = location.getLatitude();
        //获取经度
        double longitude = location.getLongitude();
        //坐标转化地理位置信息
        GeoConvert geoConvert = new GeoConvert();

        try {
            List<GeoAddress> addrs = geoConvert.getAddressFromLocation(latitude, longitude, 1);

            /**
             * getLocale()  zh_CN
             * getAdministrativeArea() xx省
             * getLocality() xx市
             * getSubLocality() xx区
             * getSubAdministrativeArea() xx街道
             * getPlaceName() xx单位
             * getRoadName() xx路
             * getSubRoadName() 122号
             * getDescriptionsSize() 0 下面的index=0
             * getDescriptions(0)  xx省xx市xx区xxx单位
             */
        } catch (IOException e) {
            System.out.println(e.getStackTrace());
        }
    }
}

实例化LocatorCallback对象,用于向系统提供位置上报的途径

MyLocatorCallback locatorCallback = new MyLocatorCallback();

定义loadLocation方法,并在onActive函数中调用

private void loadLocation() {
    RequestParam requestParam = new RequestParam(RequestParam.SCENE_DAILY_LIFE_SERVICE);
    Locator locator = new Locator(MainAbilitySlice.this);
    locator.startLocating(requestParam, locatorCallback);
}

通过语音查询天气

创建soundSearch方法,通过鸿蒙的AI系统接口AsrClient类监听语音,并返回识别的信息,通过识别的城市信息进行接口搜索。

private void soundSearch() {
    AsrClient asrClient = AsrClient.createAsrClient(MainAbilitySlice.this).orElse(null);
    AsrIntent asrIntent = new AsrIntent();
    // 设置后置的端点检测(VAD)时间
    asrIntent.setVadEndWaitMs(2000);
    // 设置前置的端点检测(VAD)时间
    asrIntent.setVadFrontWaitMs(4800);
    // 设置语音识别的超时时间
    asrIntent.setTimeoutThresholdMs(20000);
    asrClient.startListening(asrIntent);
    // buffer需要替换为真实的音频数据
    byte[] buffer = new byte[]{0, 1, 0, 10, 1};
    // 对于长度大于1280的音频,需要多次调用writePcm分段传输
    asrClient.writePcm(buffer, 1280);
    asrClient.init(asrIntent, new AsrListener() {
        @Override
        public void onInit(PacMap pacMap) {
            System.out.println("----------onInit--------");
        }

        @Override
        public void onBeginningOfSpeech() {
            System.out.println("----------onBeginningOfSpeech--------");
        }

        @Override
        public void onRmsChanged(float v) {
            System.out.println("----------onRmsChanged--------");
        }

        @Override
        public void onBufferReceived(byte[] bytes) {
            System.out.println("----------onBufferReceived--------");
        }

        @Override
        public void onEndOfSpeech() {
            System.out.println("----------onEndOfSpeech--------");
        }

        @Override
        public void onError(int i) {
            System.out.println("----------onError--------" + i);
        }

        @Override
        public void onResults(PacMap results) {
            System.out.println("----------onResults--------" + results);
        }

        @Override
        public void onIntermediateResults(PacMap pacMap) {

        }

        @Override
        public void onEnd() {
            System.out.println("----------onEnd--------");
        }

        @Override
        public void onEvent(int i, PacMap pacMap) {

        }

        @Override
        public void onAudioStart() {
            System.out.println("----------onAudioStart--------");
        }

        @Override
        public void onAudioEnd() {
            System.out.println("----------onAudioEnd--------");
        }
    });
    //开始监听语言
    asrClient.startListening(asrIntent);
}

AsrListener中的onResults(PacMap results)方法返回的结果封装在JSON格式的文件中,需要解析才能得到

演示效果

演示效果

项目地址

https://gitee.com/Haoc_249/Haoc_-HarmonyDemo/tree/master/WeatherForecast

结语

整个App的实战到这里就结束了,最后的语音搜索部分,还没有完整地实现,开发者小伙伴们有兴趣可以尝试将结果解析并进行搜索。在实现该App并且将实战步骤整理出来的这个过程中,我也是不断地加深对这个项目代码的了解,希望大家也能一样收获满满!如有表达错误之处,欢迎指出!


更多关于HarmonyOS 鸿蒙Next应用(Java)开发实战之天气预报App(三)的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html

3 回复

跟着学

更多关于HarmonyOS 鸿蒙Next应用(Java)开发实战之天气预报App(三)的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


在鸿蒙系统(HarmonyOS)上开发天气预报App,除了编程语言(尽管标题提到了Java,但按要求不深入此方面)外,关键在于理解鸿蒙系统的分布式能力、UI框架、以及系统服务。鸿蒙系统支持跨设备协同,这意味着你的天气预报App可以设计为多设备同步显示,如手机、平板和智能屏。

在开发过程中,你会利用鸿蒙的ArkUI框架来设计用户界面,它支持声明式UI和类Web的开发方式,使得界面开发更加高效。同时,鸿蒙提供了丰富的系统API,用于访问设备硬件功能、网络通信、数据存储等,这些都是构建天气预报App不可或缺的部分。

此外,鸿蒙系统强调安全和隐私保护,因此在开发过程中需确保App符合鸿蒙的安全规范,处理好用户数据的访问和存储。

如果问题依旧没法解决请联系官网客服,官网地址是 https://www.itying.com/category-93-b0.html

回到顶部