HarmonyOS Sample 之 NetworkManagement 网络管理功能 原创 精华

Buty9147
发布于 2021-9-22 20:12
浏览
7收藏

@toc

HarmonyOS Sample 之 NetworkManagement 网络管理功能

1.介绍

本示例演示了如何使用网络管理模块相关接口,演示了以下功能:
功能1:使用默认网络,打开连接,发送HTTP请求。
功能2:统计指定UID的上行/下行流量。
功能3:使用Socket方式实现不同设备间通信。
此功能需要打开WIFI,并且通信的设备连接相同的WIFI 组成局域网。
操作上,先启动服务端,再启动客户端,然后从客户端发送消息,查看服务端是否收到消息。
功能4:HTTP缓存的使用,创建缓存,供下一次请求使用,减少数据流量和加载时间。

注意,需要以下权限:
ohos.permission.GET_NETWORK_INFO 获取网络连接信息。
ohos.permission.SET_NETWORK_INFO 修改网络连接状态。
ohos.permission.INTERNET 允许程序打开网络套接字,进行网络连接。

详情见官方文档 网络管理开发概述

2.搭建环境

安装DevEco Studio,详情请参考DevEco Studio下载
设置DevEco Studio开发环境,DevEco Studio开发环境需要依赖于网络环境,需要连接上网络才能确保工具的正常使用,可以根据如下两种情况来配置开发环境:

如果可以直接访问Internet,只需进行下载HarmonyOS SDK操作。
如果网络不能直接访问Internet,需要通过代理服务器才可以访问,请参考配置开发环境
下载源码后,使用DevEco Studio 打开项目,模拟器运行即可。
真机运行需要将config.json中的bundleName修改为自己的,如果没有请到AGC上进行配置,参见 使用模拟器进行调试

3.代码结构

3.1 代码结构

HarmonyOS Sample 之 NetworkManagement 网络管理功能-鸿蒙开发者社区

3.2 相关文件介绍

核心类:
HttpURLConnection.java //支持 HTTP 特定功能的 URLConnection
URLConnection.java //URL连接
URL.java //指向万维网上“资源”的指针
NetStatusCallback.java //网络状态的回调类,出现可用网络触发onAvailable函数
DataFlowStatistics.java //该类提供查询指定蜂窝网络、应用和网卡的整体流量统计和流量统计的接口。

DatagramSocket.java //此类表示用于发送和接收数据报包的套接字。
DatagramPacket.java //数据报包
WifiDevice.java //该类提供Wi-Fi管理接口

NetManager.java //提供接口来管理和使用数据网络。
NetHandle.java // 数据网络
InetAddress.java //网络IP地址
HttpResponseCache.java //该类缓存 HTTP 和 HTTPS 响应以供重用

自定义的类:
ThreadPoolUtil.java //线程池工具类
MainAbilitySlice.java //主页面

NetRequestSlice.java //网络请求&流量统计 功能页
SocketClientSlice.java //Socket客户端
SocketServerSlice.java //Socket服务端
HttpCacheSlice.java //HTTP缓存功能页

页面布局:
http_cache_slice.xml //HTTP缓存示例页
net_request.slice.xml //HTTP请求页面
socket_client_slice.xml //Socket通信客户端页
socket_server_slice.xml //Socket通信服务端页
main_ability_slice.xml //主页面

4.实例讲解

4.1.界面布局

主页面 HTTP请求页面 缓存示例页
HarmonyOS Sample 之 NetworkManagement 网络管理功能-鸿蒙开发者社区 HarmonyOS Sample 之 NetworkManagement 网络管理功能-鸿蒙开发者社区 HarmonyOS Sample 之 NetworkManagement 网络管理功能-鸿蒙开发者社区

HarmonyOS Sample 之 NetworkManagement 网络管理功能-鸿蒙开发者社区

HarmonyOS Sample 之 NetworkManagement 网络管理功能-鸿蒙开发者社区

4.2.后台代码

4.2.1 NetRequestSlice.java 网络请求&流量统计 功能

a.初始化网络管理对象NetManager

//初始化网络管理对象
netManager = NetManager.getInstance(null);

b.通过线程池获取一个新线程处理进行连接请求
获取默认数据网络的时候需要ohos.permission.GET_NETWORK_INFO权限。

//用线程池的取一个新线程处理
ThreadPoolUtil.submit(() -> {
    HiLog.debug(LABEL_LOG, "%{public}s", "ThreadPoolUtil submit");
    //获取默认的数据网络,wifi和流量都关闭时,netId==0,其它时 netId=390/391, must have the ohos.permission.GET_NETWORK_INFO permission
    NetHandle netHandle = netManager.getDefaultNet();
    //接收默认数据网络的状态更改的回调
    netManager.addDefaultNetStatusCallback(callback);

    //netManager.setAppNet(netHandle);

    //支持 HTTP 特定功能的 URLConnection。
    HttpURLConnection connection = null;
    //输出流
    try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
        //请求的URL
        String urlString = inputText.getText();

        URL url = new URL(urlString);
        //使用 netHandle打开URL连接,不使用代理
        URLConnection urlConnection = netHandle.openConnection(url, java.net.Proxy.NO_PROXY);
        HiLog.debug(LABEL_LOG, "%{public}s", "netHandle openConnection");
        //强转换类型
        if (urlConnection instanceof HttpURLConnection) {
            connection = (HttpURLConnection) urlConnection;
        }
        //请求类型
        connection.setRequestMethod("GET");
        //连接
        connection.connect();
        //流量统计
        trafficDataStatistics(false);

        try (InputStream inputStream = urlConnection.getInputStream()) {
            byte[] cache = new byte[2 * 1024];
            int len = inputStream.read(cache);
            while (len != -1) {
                //
                outputStream.write(cache, 0, len);
                len = inputStream.read(cache);
            }
        } catch (IOException e) {
            HiLog.error(LABEL_LOG, "%{public}s", "netRequest inner IOException");
        }
        //返回结果
        String result = new String(outputStream.toByteArray());

        //UI显示
        getUITaskDispatcher().asyncDispatch(() -> outText.setText(result));
        //统计完毕
        trafficDataStatistics(true);

    } catch (IOException e) {
        HiLog.error(LABEL_LOG, "%{public}s", "netRequest IOException");
    }
});

c.按照应用ID,进行数据流量统计
在发送请求前获取一次,在请求完成后获取一次,
gitee代码中这个地方有一处笔误,tx代表上行流量,rx代表下行流量才对。详情见官方文档说明 流量统计

/**
   * 按照应用ID,进行数据流量统计
   *
   * @param isStart
   */
  private void trafficDataStatistics(boolean isStart) {
      int uid = 0;
      try {
          //根据给定的包名称和用户 ID 获取应用程序 UID。
          uid = getBundleManager().getUidByBundleName(getBundleName(), 0);
      } catch (RemoteException e) {
          HiLog.error(LABEL_LOG, "%{public}s", "trafficDataStatistics RemoteException");
      }
      if (isStart) {
          //获取指定UID的下行流量。
          rx = DataFlowStatistics.getUidRxBytes(uid);
          //获取指定UID的上行流量。
          tx = DataFlowStatistics.getUidTxBytes(uid);
      } else {
          rx = DataFlowStatistics.getUidRxBytes(uid) - rx;
          tx = DataFlowStatistics.getUidTxBytes(uid) - tx;

          //设置UI显示
          getUITaskDispatcher().asyncDispatch(() -> statisticsText.setText(
                  "TrafficDataStatistics:" + System.lineSeparator()
                          + "Receive traffic:" + rx + System.lineSeparator()
                          + "Sent traffic:" + tx));
      }
  }
}


4.2.2 SocketClientSlice.java/SocketServerSlice.java Socket客户端/服务端

实现Socket通信,是要客户端和服务端的,服务端在指定网卡上监听指定端口,客户端向指定IP指定端口发送数据,实现通信。

a.Socket服务端开启监听,等待接收数据

//监听端口
private static final int PORT = 8888;

/**
 * 启动socket服务监听
 *
 * @param component
 */
private void startServer(Component component) {
    HiLog.debug(LABEL_LOG, "startServer");
    
    //线程池获取新线程处理
    ThreadPoolUtil.submit(() -> {
        //在指定端口开启监听
        try (DatagramSocket socket = new DatagramSocket(PORT)) {
            //数据报包
            DatagramPacket packet = new DatagramPacket(new byte[255], 255);
            //死循环接收数据
            while (true) {
                //接收数据
                socket.receive(packet);
                
                //通过专有线程同步设置要显示接收到的数据
                getUITaskDispatcher().syncDispatch(() -> outText.setText(
                        "Receive a message from :" + packet.getAddress().getHostAddress()
                                + System.lineSeparator() + " on port " + packet.getPort()
                                + System.lineSeparator() + "message :" + new String(
                                packet.getData()).substring(0, packet.getLength())
                ));
                packet.setLength(255);

                //延迟一下,留出处理数据的时间
                Thread.sleep(1000);
            }
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
            HiLog.error(LABEL_LOG, "%{public}s", "StartServer  IOException | InterruptedException" + e);
        }
    });
}


/**
 * 获取服务器端IP地址,显示给客户端发送消息使用
 *
 * @return
 */
private String getLocationIpAddress() {
    HiLog.debug(LABEL_LOG, "getLocationIpAddress");
    //提供接口来管理 Wi-Fi。
    WifiDevice wifiDevice = WifiDevice.getInstance(this);
    HiLog.debug(LABEL_LOG, "wifiDevice:" + wifiDevice);
    //WifiLinkedInfo提供有关 Wi-Fi 连接的信息。
    Optional<WifiLinkedInfo> linkedInfo = wifiDevice.getLinkedInfo();
    HiLog.debug(LABEL_LOG, "linkedInfo:" + linkedInfo);
    //获取IP地址
    int ip = linkedInfo.get().getIpAddress();
    HiLog.debug(LABEL_LOG, "ip:" + ip);
    return (ip & 0xFF) + "." + ((ip >> 8) & 0xFF) + "." + ((ip >> 16) & 0xFF) + "." + (ip >> 24 & 0xFF);
}

b.Socket客户端发送数据,等待接收数据
初始化NetManager对象->new 一个DatagramSocket->获取当前数据网络NetHandle->获取服务端的IP地址对象InetAddress
->将DatagramSocket绑定到NetHandle -> new 一个数据报包DatagramPacket -> 发送数据


//通信端口
private static final int PORT = 8888;


/**
 * 发送网络请求
 * @param component
 */
private void netRequest(Component component) {
    HiLog.debug(LABEL_LOG, "netRequest");
    //启动新线程
    ThreadPoolUtil.submit(() -> {
        //初始化网络管理对象
        NetManager netManager = NetManager.getInstance(null);

        //检查默认数据网络是否已激活。
        if (!netManager.hasDefaultNet()) {
            return;
        }
        //new套接字
        try (DatagramSocket socket = new DatagramSocket()) {
            //获取当前数据网络
            NetHandle netHandle = netManager.getDefaultNet();

            //获取服务端的IP地址
            InetAddress address = netHandle.getByName(inputText.getText());
            //将套接字绑定到当前网络
            netHandle.bindSocket(socket);
            byte[] buffer = "I'm from Client".getBytes();
            //数据包
            DatagramPacket request = new DatagramPacket(buffer, buffer.length, address, PORT);
            //发送数据
            socket.send(request);
            HiLog.debug(LABEL_LOG, "send socket");
        } catch (IOException e) {
            e.printStackTrace();
            HiLog.error(LABEL_LOG, "%{public}s", "netRequest IOException"+e);
        }
    });
}

4.2.3 HttpCacheSlice.java HTTP缓存功能

应用重复打开一个相同网页时,可以优先从缓存文件里读取内容,从而减少数据流量,降低设备功耗,提升应用性能。

问:如何设置优先从缓存文件里读取内容,
答:不用额外设置,自动的 ~~

设置缓存,需要考虑 缓存位置和缓存大小。
a.初始化缓存 install

/**
 * 开启缓存
 * 开启后自动缓存数据,不需要手动处理
 */
private void initCache(Component component) {
    //默认的缓存目录
    File httpCacheDir = new File(this.getCacheDir(), "http");
    //缓存大小
    long httpCacheSize = 10 * 1024 * 1024;
    try {
        //配置缓存目录及最大缓存空间
        HttpResponseCache.install(httpCacheDir, httpCacheSize);
        HiLog.debug(LABEL_LOG, "%{public}s", "initCache,cache installed[" + httpCacheDir + "]");
    } catch (IOException e) {
        HiLog.error(LABEL_LOG, "%{public}s", "initCache IOException");
    }
}

b.保存缓存,将缓存写入文件系统 flush

/**
 * 将缓存写入文件系统,防止缓存丢失
 */
private void flushCache(Component component) {
    HiLog.debug(LABEL_LOG, "%{public}s", "flushCache");
    try {
        //获取缓存对象
        HttpResponseCache cache = HttpResponseCache.getInstalled();
        HiLog.debug(LABEL_LOG, "%{public}s", "cache:"+cache);
        if (cache != null) {
            try {
                //将缓存写入文件系统,如果cache is closed 会报错 //java.lang.IllegalStateException: cache is closed
                cache.flush();
                getUITaskDispatcher().syncDispatch(() -> {
                    //图片加载时间,测试缓存和不缓存的差别
                    duration.setText("cache flush success");
                });
                HiLog.debug(LABEL_LOG, "%{public}s", "cache flush");
            } catch (IOException e) {
                HiLog.error(LABEL_LOG, "%{public}s", "onStop IOException");
            }
        }
    } catch (IllegalStateException e) {
        e.printStackTrace();
    }
}

c.禁用缓存并删除其中的数据delete

/**
 * 禁用缓存并删除其中的数据
 */
private void deleteCache(Component component) {
    HiLog.debug(LABEL_LOG, "%{public}s", "deleteCache");
    //获取缓存对象
    HttpResponseCache cache = HttpResponseCache.getInstalled();
    HiLog.debug(LABEL_LOG, "%{public}s", "cache:"+cache);
    if (cache != null) {
        try {
            //禁用缓存并删除其中的数据。
            cache.delete();
            image.setPixelMap(null);
            HiLog.debug(LABEL_LOG, "%{public}s", "cache delete");
        } catch (IOException e) {
            HiLog.error(LABEL_LOG, "%{public}s", "onStop IOException");
        }

    }
}

/**
 * 测试请求
 *
 * @param component
 */
private void startRequest(Component component) {
    HiLog.debug(LABEL_LOG, "%{public}s", "startRequest");
    ThreadPoolUtil.submit(() -> {
        try {
            long startTime=System.currentTimeMillis();
            HiLog.debug(LABEL_LOG, "%{public}s", "startTime:"+startTime);
            //请求URL
            URL url = new URL(inputText.getText());
            URLConnection urlConnection = url.openConnection();
            HiLog.debug(LABEL_LOG, "%{public}s", "openConnection");
            //判断连接类型
            if (urlConnection instanceof HttpURLConnection) {
                HiLog.debug(LABEL_LOG, "%{public}s", "urlConnection");
                HttpURLConnection connection = (HttpURLConnection) urlConnection;
                HiLog.debug(LABEL_LOG, "%{public}s", "connect:"+connection);
                //连接
                connection.connect();
                HiLog.debug(LABEL_LOG, "%{public}s", "connected");
                //连接结果
                if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
                    HiLog.debug(LABEL_LOG, "%{public}s", "HTTP_OK");
                    //描述图像数据源选项,例如包括表示为 image/png 的图像格式。
                    ImageSource.SourceOptions srcOpts = new ImageSource.SourceOptions();
                    ImageSource imageSource = ImageSource.create(connection.getInputStream(), srcOpts);
                    //以像素矩阵的形式提供图像。
                    PixelMap pixelMap = imageSource.createPixelmap(null);
                    HiLog.debug(LABEL_LOG, "%{public}s", "pixelMap:"+pixelMap);
                    //专有线程同步设置图片显示
                    getUITaskDispatcher().syncDispatch(() -> {
                        image.setPixelMap(pixelMap);
                        //图片加载时间,测试缓存和不缓存的差别
                        duration.setText(String.valueOf(System.currentTimeMillis()-startTime)+" ms");
                    });
                    HiLog.debug(LABEL_LOG, "%{public}s", "setPixelMap");
                    HiLog.debug(LABEL_LOG, "%{public}s", "endTime:"+ (System.currentTimeMillis()-startTime));
                }
                HiLog.debug(LABEL_LOG, "%{public}s", "finish");
                //关闭连接
                connection.disconnect();
            }
        } catch (Exception e) {
            HiLog.error(LABEL_LOG, "%{public}s", "initCache Exception"+e);
            getUITaskDispatcher().syncDispatch(() -> {
                //图片加载时间,测试缓存和不缓存的差别
                duration.setText("cache is closed, please open cache");
            });
        }
    });
}

5.总结说明

1.两种打开网络连接的方式

URLConnection urlConnection = url.openConnection();
//使用 netHandle打开URL连接,不使用代理
URLConnection urlConnection = netHandle.openConnection(url, java.net.Proxy.NO_PROXY);

2.未开启缓存情况下,发送请求的时长在400-2000ms之间,开启后,需要点击两次发送请求,时长维持在90-200ms左右。
3.禁用并清除缓存delete/close后,在没有再次开启缓存前,无法发送请求。这个操作官方文档注释写的是 “结束时关闭缓存”。

6.完整代码

附件直接下载

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
NetworkManagement.zip 1.49M 108次下载
已于2021-9-29 09:05:42修改
7
收藏 7
回复
举报
8条回复
按时间正序
/
按时间倒序
mb609898e2cfb86
mb609898e2cfb86

各种讲解和演示楼主都展示的很详细呀,赞一个。

已于2021-9-23 10:01:05修改
回复
2021-9-23 10:00:33
丨张明亮丨
丨张明亮丨

好文学习了

回复
2021-9-24 09:21:47
Buty9147
Buty9147 回复了 丨张明亮丨
好文学习了

感谢支持!

回复
2021-9-24 10:11:50
Buty9147
Buty9147 回复了 mb609898e2cfb86
各种讲解和演示楼主都展示的很详细呀,赞一个。

感谢支持!

回复
2021-9-24 10:11:59
甜甜爱开发
甜甜爱开发

不好意思,我发现一个单词拼错了,buddleName  这个

回复
2021-9-26 09:51:11
甜甜爱开发
甜甜爱开发

各种讲解和演示楼主都展示的很详细呀,赞一个。比官网文档更适合作为官网文档,楼主666

回复
2021-9-26 09:56:42
Buty9147
Buty9147 回复了 甜甜爱开发
不好意思,我发现一个单词拼错了,buddleName 这个

哈哈,感谢,已更正。

回复
2021-9-29 09:06:21
Buty9147
Buty9147 回复了 丨张明亮丨
好文学习了

感谢支持

回复
2021-9-29 09:07:57
回复
    相关推荐