【HarmonyOS鸿蒙开发线程专题NO.6】糗事百科案例

内容纲要

作者:韩茹

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

鸿蒙巴士专栏作家

本案例用到了ListContainer,BaseItemProvider,网络下载,线程之间的通信等等。。

项目展示

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

WX20210630-142734@2x

运行效果:

qiushibaikeyunxing1

布局文件

现在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:padding="10vp"
    ohos:orientation="vertical">

    <Button
        ohos:id="$+id:btn_download"
        ohos:height="match_content"
        ohos:width="match_content"
        ohos:text="点击下载糗百数据"
        ohos:text_size="25fp"
        ohos:background_element="#EEEEEE"
        ohos:padding="5vp"
        />
    <RoundProgressBar
        ohos:id="$+id:round_progress_bar1"
        ohos:height="200vp"
        ohos:width="200vp"
        ohos:progress_width="10vp"
        ohos:top_margin="150vp"
        ohos:progress_hint_text="加载ing。。"
        ohos:progress_hint_text_color="#C71585"
        ohos:layout_alignment="center"
        ohos:visibility="hide"
        ohos:progress_color="#C71585"/>

    <ListContainer
        ohos:id="$+id:list_container"
        ohos:height="match_parent"
        ohos:width="match_parent"
        ohos:rebound_effect="true"
        />

</DirectionalLayout>

新建一个xml文件,表示list中的每一个item,list_item.xml:

<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:height="match_content"
    ohos:width="match_parent"
    ohos:background_element="$graphic:list_item_bg"
    ohos:bottom_margin="10vp"
    ohos:orientation="vertical">

    <Text
        ohos:id="$+id:list_item_id"
        ohos:height="match_content"
        ohos:width="match_content"
        ohos:text="ID"
        ohos:text_size="20fp"
        ohos:text_color="#0000AA"
        ohos:padding="5vp"
        />
    <Text
        ohos:id="$+id:list_item_user"
        ohos:height="match_content"
        ohos:width="match_content"
        ohos:text="User"
        ohos:text_size="20fp"
        ohos:text_color="#AA0000"
        ohos:padding="5vp"
        ohos:multiple_lines="true"
        />
    <Text
        ohos:id="$+id:list_item_content"
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:multiple_lines="true"
        ohos:text_alignment="left|top"
        ohos:text="Content"
        ohos:text_size="20fp"
        ohos:text_color="#000000"
        ohos:padding="5vp"
        />

</DirectionalLayout>

网络请求

首先新建一个包constant,用于存储程序中使用到的网址常量:

package com.example.hanruqiushibaike.constant;

/**
 * 常量
 */
public class Constant {
    public static final String QIUSHIBAIKE_URL="https://m2.qiushibaike.com/article/list/suggest?page=1";
}

再创建utils,提供一个网络下载的工具类:

package com.example.hanruqiushibaike.utils;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;

public class HttpUtils {
    /**
     * 根据网络接口,获取指定的InputStream
     * @param baseUrl
     * @return
     */

    public static InputStream getInputStream(String baseUrl){
        try {
            URL url = new URL(baseUrl);
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setRequestProperty("User-Agent",
                    "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko");
            if(connection.getResponseCode()==HttpURLConnection.HTTP_OK){
                return connection.getInputStream();
            }
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
    /**
     * 根据网络接口,获取数据,byte数组
     * @param baseUrl
     * @return
     */
    public static byte[] getDataFromNet(String baseUrl){
        InputStream inputStream = getInputStream(baseUrl);
        if(inputStream!=null){
            byte[]bs = new byte[1024];
            int len=0;
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            try {
                while((len = inputStream.read(bs))!=-1){
                    baos.write(bs, 0, len);
                }
                return baos.toByteArray();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }
    /**
     * 根据网络接口,获取json数据
     * @param baseUrl
     * @return
     */
    public static String getJsonFromNet(String baseUrl){
        byte[] data = getDataFromNet(baseUrl);
        if(data!=null){
            return new String(data);
        }
        return null;
    }
}

然后我们可以测试一下,点击开下面的test包,在ExampleTest类中再添加一个单元测试方法:

 @Test
    public void testHttpUtils(){
        String json = HttpUtils.getJsonFromNet(Constant.QIUSHIBAIKE_URL);
        System.out.println("json--"+json);
    }

然后点击方法前面的小绿揪揪,选择Run这个方法:

WX20210630-152024@2x

我们就可以看到下面的运行结果了:

WX20210630-152223@2x

到此网络下载json数据是没有问题的。

json解析

这里下载到的糗百数据是json格式的,我们需要将它进行解析,存储到List集合里。浏览器上打开糗百的网址,然后将json数据复制,随便搜索一个在线格式化的工具进行格式化后,这里我们只要3个字段:id,login,和content:

WX20210630-153015@2x

我们在utils包下新建一个json解析的工具类:JsonParseUtils.java

package com.example.hanruqiushibaike.utils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

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

/**
 * 解析糗百的json数据
 */
public class JsonParseUtils {
    public static List<Map<String, String>> parseJsonToList(String json){
        List<Map<String, String>> list = new ArrayList<Map<String,String>>();
        try {
            JSONArray jsonArray = new JSONObject(json).getJSONArray("items");
            for(int i=0;i<jsonArray.length();i++){
                JSONObject jsonObject = jsonArray.getJSONObject(i);
                Map<String, String> map = new HashMap<String, String>();
                int id = jsonObject.optInt("id");
                map.put("id", id+"");
                map.put("content", jsonObject.optString("content"));
                JSONObject jsonObject2 = jsonObject.optJSONObject("user");
                if(jsonObject2!=null){
                    map.put("login", jsonObject2.optString("login"));
                }
                list.add(map);
            }
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return list;
    }
}

我们测试一下,同样还是在ExampleTest下添加一个单元测试方法:

@Test
    public void testJson(){
        List<Map<String, String>> list = JsonParseUtils.parseJsonToList(HttpUtils.getJsonFromNet(Constant.QIUSHIBAIKE_URL));
        System.out.println(list.size());
        for(int i=0;i<list.size();i++){
            System.out.println(list.get(i));
        }
    }

然后运行后观察结果:

WX20210630-153451@2x

到此我们已经解析了json数据。

ListItemProvider

然后我们就可以创建ListItemProvider类来进行填充ListContainer了。我们先创建一个包provider,然后新建一个provider文件:ListItemProvider.java

package com.example.hanruqiushibaike.provider;

import com.example.hanruqiushibaike.ResourceTable;
import ohos.aafwk.ability.AbilitySlice;
import ohos.agp.components.*;

import java.util.List;
import java.util.Map;

public class ListItemProvider  extends  BaseItemProvider{

    private List<Map<String, String>> list;
    private AbilitySlice  slice;

    public ListItemProvider(List<Map<String, String>> list, AbilitySlice slice) {
        this.list = list;
        this.slice = slice;
    }

    @Override
    public int getCount() {
        return list == null?0: list.size();//一般返回数据源的长度
    }

    @Override
    public Object getItem(int position) {
        if(list!= null && position >= 0 && position < list.size()){
            return list.get(position);
        }
        return null;
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public Component getComponent(int position, Component convertComponent, ComponentContainer componentContainer) {
        final Component cpt;
        // 如果还没有convertComponent对象,那么将xml布局文件转为一个Component对象。
        if(convertComponent == null){
            //从当前的AbilitySlice对应的xml布局中,
            cpt = LayoutScatter.getInstance(slice).parse(ResourceTable.Layout_list_item,null,false);
        }else{
            cpt = convertComponent;
        }
        Map<String,String> map =list.get(position);//获取数据
        Text textId = (Text) cpt.findComponentById(ResourceTable.Id_list_item_id);
        Text textUser = (Text) cpt.findComponentById(ResourceTable.Id_list_item_user);
        Text textContent = (Text) cpt.findComponentById(ResourceTable.Id_list_item_content);

        textId.setText("用户ID:"+map.get("id"));
        textUser.setText("用户名:"+map.get("login"));
        textContent.setText(map.get("content"));
        String content = map.get("content");

        return cpt;
    }
}

投递InnerEvent

我们现在MainAbilitySlice中,初始化组件,首先声明Button,RoundProgressBar和ListContainer对象,并在onStart()方法中调用该初始化方法。

        // 获取UI组件
    private void initComponent(){
        btnDownload= (Button) findComponentById(ResourceTable.Id_btn_download);
        roundProgressBar = (RoundProgressBar)findComponentById(ResourceTable.Id_round_progress_bar1);
        listContainer = (ListContainer) findComponentById(ResourceTable.Id_list_container);

    }

然后声明EventHandler和EventRunner对象,再实例化:

        // 实例化EventHandler和EventRunner对象,并在onStart()方法中调用该初始化方法。
    private void initHandler() {
        eventRunner = EventRunner.create("TestRunner");
        handler = new DownLoadImageEventHandler(eventRunner,this);
    }

接下来给按钮添加点击事件,当点击下载按钮的时候,我们要进行任务投递,完整的MainAbilitySlice代码如下:

package com.example.hanruqiushibaike.slice;

import com.example.hanruqiushibaike.ResourceTable;
import com.example.hanruqiushibaike.constant.Constant;
import com.example.hanruqiushibaike.handler.DownLoadImageEventHandler;
import com.example.hanruqiushibaike.utils.HttpUtils;
import ohos.aafwk.ability.AbilitySlice;
import ohos.aafwk.content.Intent;
import ohos.agp.components.*;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.eventhandler.InnerEvent;

public class MainAbilitySlice extends AbilitySlice {
    private Button btnDownload;
    private RoundProgressBar roundProgressBar;
    private ListContainer listContainer;

    private EventRunner eventRunner;
    private EventHandler handler;

    // 先定义全局变量
    public static final int EVENT_MESSAGE_NORMAL = 1;

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

        initComponent();
        initHandler();

        // 按钮的点击事件
        btnDownload.setClickedListener(component -> {
            // 点击按钮,下载糗事百科的json数据
            long param = 0;
            InnerEvent innerEvent = InnerEvent.get(EVENT_MESSAGE_NORMAL, param, EventRunner.current());
            handler.sendEvent(innerEvent, EventHandler.Priority.IMMEDIATE);
            System.out.println("UI线程-->InnerEvent已发送。。");
            roundProgressBar.setVisibility(Component.VISIBLE);
        });

    }
    public ListContainer getListContainer(){
        return listContainer;
    }

    public RoundProgressBar getRoundProgressBar(){
        return roundProgressBar;
    }

    // 获取UI组件
    private void initComponent(){
        btnDownload= (Button) findComponentById(ResourceTable.Id_btn_download);
        roundProgressBar = (RoundProgressBar)findComponentById(ResourceTable.Id_round_progress_bar1);
        listContainer = (ListContainer) findComponentById(ResourceTable.Id_list_container);

    }
    //step2:实例化EventHandler和EventRunner对象,并在onStart()方法中调用该初始化方法。
    private void initHandler() {
        eventRunner = EventRunner.create("TestRunner");
        handler = new DownLoadImageEventHandler(eventRunner,this);
    }

}

EventHandler处理

新建一个包handler,然后新建一个java文件,DownLoadImageEventHandler.java:

这里主要的思路是,

1.先根据网络请求获取到json数据。
2.然后解析json数据得到数据源list。
3.将list数据传回到原来的UI线程,并进行设置listContainer。

示例代码:

package com.example.hanruqiushibaike.handler;

import com.example.hanruqiushibaike.constant.Constant;
import com.example.hanruqiushibaike.provider.ListItemProvider;
import com.example.hanruqiushibaike.slice.MainAbilitySlice;
import com.example.hanruqiushibaike.utils.HttpUtils;
import com.example.hanruqiushibaike.utils.JsonParseUtils;
import ohos.agp.components.Component;
import ohos.agp.components.ListContainer;
import ohos.agp.components.RoundProgressBar;
import ohos.agp.window.dialog.ToastDialog;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.eventhandler.InnerEvent;

import java.util.List;
import java.util.Map;

//1.创建自定义的EventHandler子类
public class DownLoadImageEventHandler extends EventHandler {
    private MainAbilitySlice slice;

    //2.添加构造方法
    public DownLoadImageEventHandler(EventRunner runner, MainAbilitySlice slice) {
        super(runner);
        this.slice = slice;
    }

    //3.重写processEvent()方法
    @Override
    protected void processEvent(InnerEvent event) {
        super.processEvent(event);
        if (event == null) {
            return;
        }
        switch (event.eventId) {
            case MainAbilitySlice.EVENT_MESSAGE_NORMAL:
                System.out.println("----开始网络下载---");

                // 下载完毕后,解析json数据
                String json = HttpUtils.getJsonFromNet(Constant.QIUSHIBAIKE_URL);
                System.out.println("===json:" + json);
                if (json != null) {
                    List<Map<String, String>> listData = JsonParseUtils.parseJsonToList(json);
                    // 在UI线程上设置ListContainer
                    EventRunner runner = (EventRunner) event.object;
                    EventHandler eventHandler = new EventHandler(runner) {
                        @Override
                        protected void processEvent(InnerEvent event) {
                            // 进度条消失
                            RoundProgressBar roundProgressBar = slice.getRoundProgressBar();
                            roundProgressBar.setVisibility(Component.HIDE);
                            // 设置数据到ListContainer上
                            if (listData.size() == 0) {
                                new ToastDialog(slice.getContext()).setText("没有数据显示").show();
                            } else {
                                // 1.初始化Provider对象
                                ListItemProvider listItemProvider = new ListItemProvider(listData, slice);
                                // 2.获取listContainer对象
                                ListContainer listContainer = slice.getListContainer();
                                // 3.适配要展示的内容数据
                                listContainer.setItemProvider(listItemProvider);
                            }
                        }
                    };
                    int testEventId = 1;
                    long testParam = 0;
                    Object testObject = listData;
                    InnerEvent innerEvent = InnerEvent.get(testEventId, testParam, testObject);
                    eventHandler.sendEvent(innerEvent);

                }

        }

    }
}

到此结束。

附带源码

更多内容:

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

2、公众号:HarmonyBus

3、技术交流QQ群:714518656

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

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

发表评论

登录后才能评论