HarmonyOS 鸿蒙Next应用(Java)开发实战之天气预报App(三)
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
跟着学
更多关于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,