【HarmonyOS鸿蒙开发Service专题NO.2】天气预报(综合案例)

内容纲要

作者:韩茹

公司:程序咖(北京)科技有限公司

鸿蒙巴士专栏作家

本案例用到了PageSlider,PageSliderIndicator,PageSliderProvider,service服务,json解析,网络下载等等。。

项目展示

首先我们先新建一个HarmonyOS的项目:

WX20210707-174310@2x

运行效果:

tianqiyubaoyunxing1

布局文件

现在ability_main.xml中添加:

<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:height="match_parent"
    ohos:width="match_parent"
    ohos:background_element="#E9F6FE"
    ohos:left_padding="20vp"
    ohos:right_padding="20vp"
    ohos:top_padding="5vp"
    ohos:orientation="vertical">

    <Text
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:text="天气预报,丑是丑了点"
        ohos:text_size="24fp"
        ohos:text_alignment="center"
        />

    <DirectionalLayout
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:orientation="horizontal"
        ohos:top_margin="10vp"
        >
        <TextField
            ohos:id="$+id:input_city"
            ohos:height="match_content"
            ohos:width="200vp"
            ohos:hint="请输入要查询的城市"
            ohos:text_size="20fp"
            ohos:padding="10vp"
            ohos:background_element="#99D3D3D3"
            ohos:layout_alignment="center"

            />
        <Button
            ohos:id="$+id:btn_query"
            ohos:height="match_content"
            ohos:width="100vp"
            ohos:text="查 询"
            ohos:text_size="20fp"
            ohos:padding="10vp"
            ohos:background_element="$graphic:button_bg"
            ohos:layout_alignment="center"
            ohos:left_margin="10vp"
            />
    </DirectionalLayout>

<!--    查询到到天气-->

    <DirectionalLayout
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:background_element="#3CBFFE"
        ohos:left_padding="20vp"
        ohos:top_padding="10vp"
        ohos:right_padding="20vp"
        ohos:layout_alignment="center"
        ohos:top_margin="10vp"
        >
        <Text
            ohos:id="$+id:show_city"
            ohos:height="match_content"
            ohos:width="match_content"
            ohos:text="城市"
            ohos:text_color="#FFFFFF"
            ohos:text_size="30fp"
            ohos:margin="10vp"
            />
        <Text
            ohos:height="2vp"
            ohos:width="match_parent"
            ohos:background_element="#99FFFFFF"
            />
        <Text
            ohos:id="$+id:show_date"
            ohos:height="match_content"
            ohos:width="match_parent"
            ohos:text="日期"
            ohos:text_size="24fp"
            ohos:text_color="#FFFFFF"
            ohos:margin="10vp"
            />
        <Text
            ohos:id="$+id:show_temp"
            ohos:height="match_content"
            ohos:width="match_parent"
            ohos:text="温度"
            ohos:text_size="55fp"
            ohos:text_color="#FFFFFF"
            ohos:left_margin="10vp"
            ohos:bottom_margin="10vp"
            />
        <Text
            ohos:height="2vp"
            ohos:width="match_parent"
            ohos:background_element="#99FFFFFF"
            />
        <Text
            ohos:id="$+id:show_humidity"
            ohos:height="match_content"
            ohos:width="match_parent"
            ohos:text="湿度"
            ohos:text_size="20fp"
            ohos:text_color="#FFFFFF"
            ohos:margin="10vp"
            />
        <Text
            ohos:id="$+id:show_wind"
            ohos:height="match_content"
            ohos:width="match_parent"
            ohos:text="风向"
            ohos:text_size="20fp"
            ohos:text_color="#FFFFFF"
            ohos:left_margin="10vp"
            ohos:bottom_margin="10vp"
            />
    </DirectionalLayout>

</DirectionalLayout>

graphic下,button_bg.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<shape xmlns:ohos="http://schemas.huawei.com/res/ohos"
       ohos:shape="rectangle">
    <solid
        ohos:color="#D3D3D3"/>
    <corners
        ohos:radius="20vp"
        />
</shape>

预览效果:

WX20210707-175153@2x

Service服务

1、先获取布局中的组件

在MainAbilitySlice.java中,添加initComponent()方法先获取组件,并在onStart()中进行调用:

        // 初始化组件
    private void initComponent() {
        textCity = (Text) findComponentById(ResourceTable.Id_show_city);
        textDate = (Text) findComponentById(ResourceTable.Id_show_date);
        textTemp = (Text) findComponentById(ResourceTable.Id_show_temp);
        textHumidity = (Text) findComponentById(ResourceTable.Id_show_humidity);
        textSpeed = (Text) findComponentById(ResourceTable.Id_show_wind);
        inputCityTextField = (TextField) findComponentById(ResourceTable.Id_input_city);

        Button btnQuery = (Button) findComponentById(ResourceTable.Id_btn_query);

    }

        @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
        super.setUIContent(ResourceTable.Layout_ability_main);

        initComponent();

    }

2、创建Service服务

WX20210707-180518@2x

在生成的ServiceAbility中,先修改一下HiLogLabel:

    private static final HiLogLabel LABEL_LOG = new HiLogLabel(3, 0x00201, "WeatherServiceAbility");

在MainAbilitySlice中,也添加一下HiLogLabel:

    private static final HiLogLabel LABEL_LOG = new HiLogLabel(3, 0x00201, "MainAbilitySlice");

3、点击 按钮,连接 service

在MainAbilitySlice中,先 添加两个 成员变量:

        private static final String BUNDLE_NAME = "com.example.hanruweather";

    private static final String SERVICE_NAME = "WeatherServiceAbility";

添加一个方法,用于获取链接Service的Intent:

        // 获取启动本地服务的Intent
    private Intent getServiceIntent(String bundleName, String serviceName) {
        Operation operation = new Intent.OperationBuilder().withDeviceId("")
                .withBundleName(bundleName)
                .withAbilityName(serviceName)
                .build();
        Intent intent = new Intent();
        intent.setOperation(operation);
        return intent;
    }

在onStart()中,连接服务

        @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
        super.setUIContent(ResourceTable.Layout_ability_main);

        initComponent();
        HiLog.info(LABEL_LOG, "connectService------ ");
        // 连接服务
        connectAbility(getServiceIntent(BUNDLE_NAME, SERVICE_NAME), connection);

    }

接下来创建IAbilityConnection类型的连接对象:

        // 连接对象
    private IAbilityConnection connection = new IAbilityConnection() {
        @Override
        public void onAbilityConnectDone(ElementName elementName, IRemoteObject iRemoteObject, int resultCode) {
            clientRemoteProxy = new ClientRemoteProxy(iRemoteObject);
        }

        @Override
        public void onAbilityDisconnectDone(ElementName elementName, int resultCode) {
            HiLog.info(LABEL_LOG, "%{public}s", "onAbilityDisconnectDone resultCode : " + resultCode);
        }
    };

这里需要一个IRemoteBroker实现类:

// 客户端代理类
    public class ClientRemoteProxy implements IRemoteBroker {
        private static final int RESULT_SUCCESS = 0;
        private final IRemoteObject remoteObject;

        public ClientRemoteProxy(IRemoteObject remoteObject) {
            this.remoteObject = remoteObject;
        }

        // 这里是消息发送最关键的地方
        public void todoServiceJob(int command) {

        }

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

我们的网络请求分两种,一种是当天的天气,一种是预测未来4天的。

我们定义两个常量:

先定义个包constant,里面定义个常量类Constant.java:

package com.example.hanruweather.constant;

/**
 * 常量
 */
public class Constant {
    public static final int CURRENT = 1001;

    public static final int FORECAST = 1002;

    public static final int SUCCESS = 1;

    public static final int FAIL = 0 ;

}

在initComponent()方法中,点击按钮,调用todoServiceJob()方法,进行请求服务:

        // 初始化组件
    private void initComponent() {
        ...
        btnQuery.setClickedListener(component -> {

            clientRemoteProxy.todoServiceJob(Constant.CURRENT);
        });

    }

接下来,我们就要在todoServiceJob()中,先封装数据,获取输入框中用户要查询天气的城市,然后通过sendRequest()进行请求:

        public void todoServiceJob(int command) {
      // 传过去的参数:城市名称
      MessageParcel message = MessageParcel.obtain();
      String city = inputCityTextField.getText();
      HiLog.info(LABEL_LOG, "textfield-->city------ " + city);
      message.writeString(city);
      // 接收回来的参数
      MessageParcel reply = MessageParcel.obtain();
       try {
                // 发送请求
                HiLog.info(LABEL_LOG, "sendRequest------ ");
                remoteObject.sendRequest(command, message, reply, option);
        } catch (RemoteException e) {
                e.printStackTrace();
            }
    }

然后在WeatherServiceAbility中,先创建RemoteObject子类并实现IRemoteBroker:

class MyRemote extends RemoteObject implements IRemoteBroker {

        MyRemote() {
            super("MyService_MyRemote");
            HiLog.info(LABEL_LOG, "MyRemote()----->");
        }

        // 设置接收请求的条目。
        // int code:表示从对等端发送的服务请求代码。
        @Override
        public boolean onRemoteRequest(int code, MessageParcel data, MessageParcel reply, MessageOption option) {

            return true;
        }

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

声明一个成员变量:

public class WeatherServiceAbility extends Ability {
        ...
    private MyRemote remote = new MyRemote();
    ...
}

在onConnect()方法中返回:

        @Override
    protected IRemoteObject onConnect(Intent intent) {

        super.onConnect(intent);

        HiLog.info(LABEL_LOG, "onConnect()---->");
        return remote.asObject();

    }

到此我们先运行一下,输入城市并点击查询按钮:

WX20210707-183952@2x

网络请求

1、这里我们使用https://openweathermap.org/api中的json接口,这个接口中提供的天气预报 的数据是全球的,也就是说我们的程序写完,你也可以查询国外城市的天气预报。

WX20210708-092750@2x

我们先去Constant常量类中定义json数据的网址:

        // 天气预报的base地址
    public static final String BASE_URI = "https://api.openweathermap.org/data/2.5/";

    //    private static final String API_KEY ="appid={{Add your Key}}";
    public static final String API_KEY = "appid=accf59680d5b2039f92c468d4ac8e634";

    // 预测4天
    public static final String FORECAST_URL = "forecast";

    // 当天天气
    public static final String WEATHER_URL = "weather";

2、我们在onRemoteRequest()中获取MainAbilitySlice中请求传来的城市数据

String cityData = data.readString();

但是因为用户可能传入的是中文,如果是中国的城市名称,我们需要转为对应的汉语拼音。这里我们要借助于一个第三方jar包:

WX20210708-093514@2x

将这个jar包拷贝到libs目录下,并右键 Add as Library。

然后我们创建一个package,utils,里面专门存放一些工具类,先来一个TextUtils类:

package com.example.hanruweather.utils;

import net.sourceforge.pinyin4j.PinyinHelper;
import net.sourceforge.pinyin4j.format.HanyuPinyinOutputFormat;
import net.sourceforge.pinyin4j.format.HanyuPinyinToneType;
import net.sourceforge.pinyin4j.format.exception.BadHanyuPinyinOutputFormatCombination;

public class TextUtils {
    /**
     * 将汉字转为汉语拼音
     *
     * @param text
     * @return
     */
    public static String convertToHanYuPinYin(String text) {
        // 如果不是中文,直接 返回原字符串
        if (!text.matches("[\\u4E00-\\u9FA5]+")) {
            return text;
        }
        // 如果是中文,转为汉语拼音
        char[] array = text.toCharArray();
        StringBuffer sb = new StringBuffer();

        HanyuPinyinOutputFormat format = new HanyuPinyinOutputFormat();
        // 不显示拼音的声调
        format.setToneType(HanyuPinyinToneType.WITHOUT_TONE);

        for (int i = 0; i < array.length; i++) {
            try {
                sb.append(PinyinHelper.toHanyuPinyinStringArray(array[i],
                        format)[0]);
            } catch (BadHanyuPinyinOutputFormatCombination badHanyuPinyinOutputFormatCombination) {
                badHanyuPinyinOutputFormatCombination.printStackTrace();
            }
        }
        return sb.toString();
    }
}

在onRemoteRequest()中:

    HiLog.info(LABEL_LOG, "onRemoteRequest()----->");
    String cityData = data.readString();
    city = TextUtils.convertToHanYuPinYin(cityData);
    HiLog.info(LABEL_LOG, "onRemoteRequest---->" + code + ",--->cityData:" + cityData + "---city:" + city);

这里我们再提供一个方法,用来拼接URL

        // 拼接URL
    private String concatUrl(String link, String params[]) {
        StringBuilder urlFinal = new StringBuilder();
        urlFinal.append(link);
        urlFinal.append("?");
        for (int i = 0; i < params.length; i++) {
            urlFinal.append("&");
            urlFinal.append(params[i]);

        }
        HiLog.info(LABEL_LOG, "urlFinal------ " + urlFinal.toString());
        return urlFinal.toString();
    }

然后我们再提供一个网络下载的工具类,在utils包下新建一个NetDownLoadUtils.java类:

package com.example.hanruweather.utils;

import ohos.hiviewdfx.HiLog;
import ohos.net.NetHandle;
import ohos.net.NetManager;
import ohos.net.NetStatusCallback;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;

/**
 * 网络下载天气预报数据的工具类
 */
public class NetDownLoadUtils {

    /**
     * 根据指定的url,下载天气预报的数据:json格式
     * @param weatherUrl
     * @return
     */
    public static String downLoadWeatherData(String weatherUrl){
        NetManager netManager = NetManager.getInstance(null);
        if (!netManager.hasDefaultNet()) {
            return null;
        }
        NetHandle netHandle = netManager.getDefaultNet();
        // Listen to network state changes.
        NetStatusCallback callback = new NetStatusCallback() {
            // Override the callback for network state changes.
        };

        netManager.addDefaultNetStatusCallback(callback);
        // Obtain a URLConnection using the openConnection method.
        HttpURLConnection connection = null;
        try {
            URL url = new URL(weatherUrl);
            URLConnection urlConnection = netHandle.openConnection(url,
                    java.net.Proxy.NO_PROXY);
            if (urlConnection instanceof HttpURLConnection) {
                connection = (HttpURLConnection) urlConnection;
            }
            connection.setRequestMethod("GET");
            connection.connect();
            int responseCode = connection.getResponseCode();
            if (responseCode == HttpURLConnection.HTTP_OK) {
                BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream()));
                StringBuilder sb = new StringBuilder();
                String line;
                while ((line = br.readLine()) != null) {
                    sb.append(line);
                }
                br.close();
                return sb.toString();

            }
            return null;
        } catch (IOException e) {
            e.printStackTrace();
            return "IOException";
        } finally {
            if (connection != null) {
                connection.disconnect();
            }
        }
    }

}

记得在config.json中添加网络权限:

"reqPermissions": [
      {
        "name": "ohos.permission.GET_NETWORK_INFO"
      },
      {
        "name": "ohos.permission.INTERNET"
      },
      {
        "name": "ohos.permission.SET_NETWORK_INFO"
      }

    ]

到此,我们启动程序,并在输入框中输入:北京

WX20210708-095327@2x

能够将json数据下载到了。

json解析

我们首先观察json接口中的字段的描述:https://openweathermap.org/current#current_JSON

WX20210708-095517@2x

根据我们想要的字段,进行json解析,解析的方式有很多种,也有很多第三方的包,这里我使用的是JSON

WX20210708-095651@2x

可以将json字符串解析对应JavaBean的类,也可以解析到集合中,比如Map等。

然后我们在utils包下,新建一个json解析的工具类:

package com.example.hanruweather.utils;

import org.json.JSONException;
import org.json.JSONObject;

import java.util.HashMap;
import java.util.Map;

public class JsonUtils {
    /**
     * 解析当前的天气预报-->Map集合
     * @param json
     * @return
     */
    public static Map<String,String> parseCurrentWeatherJsonToMap(String json){

        Map<String,String> map = new HashMap<>();
        if(json==null|| "".equals(json)){
            return map;
        }
        try {
            JSONObject jSONObject = new JSONObject(json);
            // 温度
            JSONObject main = jSONObject.getJSONObject("main");
            // 将开尔文转为摄氏度:开氏度 = 摄氏度+273.15。
            map.put("temp",String.format("%.2f",main.optDouble("temp")-273.15)); // 温度
            map.put("humidity",main.optString("humidity")); // 湿度

            JSONObject wind = jSONObject.getJSONObject("wind");
            map.put("speed",wind.optString("speed")); // 风速

            JSONObject sys = jSONObject.getJSONObject("sys");
            map.put("country", sys.optString("country"));

        } catch (JSONException e) {
            e.printStackTrace();
        }

        return map;
    }
}

这里我将它解析到Map集合中了。

数据处理

解析后的数据,我们希望能够将它显示到UI界面上。在onRemoteRequest()中,需要将数据写出去。

// 设置接收请求的条目。
        // int code:表示从对等端发送的服务请求代码。
        @Override
        public boolean onRemoteRequest(int code, MessageParcel data, MessageParcel reply, MessageOption option) {

            HiLog.info(LABEL_LOG, "onRemoteRequest()----->");
            String cityData = data.readString();
            city = TextUtils.convertToHanYuPinYin(cityData);
            HiLog.info(LABEL_LOG, "onRemoteRequest---->" + code + ",--->cityData:" + cityData + "---city:" + city);
            switch (code) {
                case Constant.CURRENT:
                    // 下载当前的天气
                    String weatherUrl = concatUrl(Constant.BASE_URI + Constant.WEATHER_URL, new String[]{"q=" + city, Constant.API_KEY});
                    String jsonData = NetDownLoadUtils.downLoadWeatherData(weatherUrl);
                    HiLog.info(LABEL_LOG, "json-->" + jsonData);
                    Map<String, String> map = JsonUtils.parseCurrentWeatherJsonToMap(jsonData);
                    if (map.size() == 0) {
                        reply.writeInt(Constant.FAIL);
                        return false;
                    }
                    map.put("city", cityData);
                    reply.writeInt(Constant.SUCCESS);
                    reply.writeString(ZSONObject.toZSONString(map));
                    HiLog.info(LABEL_LOG, "map-->" + map);

                    break;
                default:

            }

            return true;
        }

在MainAbilitySlice中,todoServiceJob()的完整代码:

// 这里是消息发送最关键的地方
        public void todoServiceJob(int command) {
            // 传过去的参数:城市名称
            MessageParcel message = MessageParcel.obtain();
            String city = inputCityTextField.getText();
            HiLog.info(LABEL_LOG, "textfield-->city------ " + city);
            message.writeString(city);
            // 接收回来的参数
            MessageParcel reply = MessageParcel.obtain();

            // 异步还是同步
            MessageOption option = new MessageOption(MessageOption.TF_SYNC);

            try {
                // 发送请求
                HiLog.info(LABEL_LOG, "sendRequest------ ");
                remoteObject.sendRequest(command, message, reply, option);
                // 处理结果
                int result = reply.readInt();
                if (result == Constant.FAIL) {
                    new ToastDialog(getContext()).setText("查无数据").setAlignment(LayoutAlignment.CENTER).show();
                } else if (result == Constant.SUCCESS) {
                    switch (command) {
                        case Constant.CURRENT:
                            String jsonData = reply.readString();
                            HiLog.info(LABEL_LOG, "jsonData------ " + jsonData);
                            Map<String, String> map = ZSONObject.stringToClass(jsonData, Map.class);
                            HiLog.info(LABEL_LOG, "map------ " + map);
                            // 将接收到到数据,设置到UI组件上
                            textCity.setText(map.get("city") + "," + map.get("country"));
                            String date = DateUtils.getCurrentDate();
                            textDate.setText(date + ",  " + DateUtils.getWeekOfCurrentDay());
                            textTemp.setText(map.get("temp") + "°C");
                            textHumidity.setText("空气湿度:" + map.get("humidity") + "%");
                            textSpeed.setText("风速:" + map.get("speed") + "km/hr");

                            break;

                        default:
                    }
                }

            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

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

因为我们打算显示日期的时候,显示星期几,所以在utils包下,再提供一个日期的工具类:

package com.example.hanruweather.utils;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

public class DateUtils {
    public static String getCurrentDate(){
        Date date = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        String dateStr = sdf.format(date);
        return dateStr;
    }

    /**
     * 获取今天星期几
     * @return
     */
    public static String getWeekOfCurrentDay(){
        String[] weekDays = {"星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"};
        Calendar cal = Calendar.getInstance();
        int week = cal.get(Calendar.DAY_OF_WEEK);
        return weekDays[week-1];
    }
}

到此,我们运行一下程序:

tianqiyubaoyunxing2

获取更多天气

想获预测未来4天的天气,UI上可以借助于PageSlider,用来进行切换。获取未来4天的天气的json数据和上面获取当前天气的思路原理是一样的,只是更换json接口而已。然后进行相应的解析。再将数据通过PageSliderProvider,显示到PageSlider上。完整的代码,我放在github上了。

源代码

更多内容:

1、社区:鸿蒙巴士https://www.harmonybus.net/

2、公众号:HarmonyBus

3、技术交流QQ群:714518656

4、视频课:https://www.chengxuka.com

发布者:韩茹,未经授权,禁止转载,违者必究:https://bus.chengxuka.com/archives/2491

发表评论

登录后才能评论