关于作者:CSDN内容合伙人、技术专家, 从零开始做日活千万级APP。
专注于分享各领域原创系列文章 ,擅长java后端、移动开发、商业变现、人工智能等,希望大家多多支持。
目录
- 一、导读
- 二、概览
- 三、相关工具
- 3.1 network profiler/ Inspector
- 3.2 抓包工具
- 3.3 三方库 stetho
- 3.4 通过 TrafficStats 来统计流量
- 3.5 通过 NetworkStatsManager 来统计流量
- 四、优化
- 五、 推荐阅读
一、导读
我们继续总结学习基础知识,温故知新。
本文主要讲述网络优化相关的概念及思路。
HTTP即超文本传输协议(HyperText Transfer Protocol) 是一个简单的请求-响应协议,它通常运行在TCP之上。它指定了客户端可能发送给服务器什么样的消息以及得到什么样的响应(百度百科)。
二、概览
针对网络优化的维度比较多,可以从多方面入手,
比如流量使用情况(单位时间内流量消耗,用户网络类型,是否前后台等),
添加流量监控,最好从请求开始到结束的完整流程。
再比如网络请求的成功率、请求速度等等。
网络的测试,细节会比较多,我们要充分的进行线下测试,并进行多维度的模拟,如弱网、无网、网络切换等。
为了更精准的测试我们app的流量,可以在设置里面将其他app联网权限关闭。
对于线上用户监控,在客户端,也要多维度监控,尽可能多的获取信息,以便排查问题,要获取到接口的每一步详细信息,
如某个接口的dns解析时间,连接时间、请求时间,请求状态,网络包大小,网络状态等等,再配合用户日志系统,进行统一收集展示。
三、相关工具
3.1 network profiler/ Inspector
目前,网络性能剖析器仅支持 HttpURLConnection 和 OkHttp 网络连接库。
在新包的android studio profiler上已经移除,在新的菜单中。
打开方式如下: View > Tool Windows > App Inspection
如下图所示:
这个工具会实时显示网络活动,包括发送、接受的数据及连接数、状态等。
使用方式大家自行学习吧,比较简单。
官网链接
3.2 抓包工具
要熟悉一些抓包工具,如
charles
fidder
工具的使用请自行学习。
3.3 三方库 stetho
stetho是facebook开发的一个开源库,Android应用通过引入stetho,可以在Chrome/Chromium浏览器监控查看网络请求、数据库、SharedPreferences、UI布局层级等.
chrome://inspect/
Stetho库的地址:
工具的使用请自行学习。
3.4 通过 TrafficStats 来统计流量
我们使用 android.net.TrafficStats 类 来统计流量,
这个类只在api 18 以上才有,用来统计手机重启以来的流量使用情况。
其实本身TrafficStats类也是读取Linux提供的文件对象系统类型的文本进行解析。
android.net.TrafficStats类中,提供了多种静态方法,可以直接调用获取,返回类型均为 long型,如果返回等于-1代表 UNSUPPORTED 当前设备不支持统计。
static long getMobileRxBytes() //获取通过Mobile连接收到的字节总数,不包含WiFi
static long getMobileRxPackets() //获取Mobile连接收到的数据包总数
static long getMobileTxBytes() //Mobile发送的总字节数
static long getMobileTxPackets() //Mobile发送的总数据包数
static long getTotalRxBytes() //获取总的接受字节数,包含Mobile和WiFi等
static long getTotalRxPackets() //总的接受数据包数,包含Mobile和WiFi等
static long getTotalTxBytes() //总的发送字节数,包含Mobile和WiFi等
static long getTotalTxPackets() //发送的总数据包数,包含Mobile和WiFi等
static long getUidRxBytes(int uid) //获取某个网络UID的 接受 字节数
static long getUidTxBytes(int uid) //获取某个网络UID的 发送字节数
我们可以间隔一段时间读取一下使用的流量,
public void start() {
uid = Process.myUid();
thread = new HandlerThread("123");
thread.start();
handler = new Handler(thread.getLooper()) {
@Override
public void handleMessage(@NonNull Message msg) {
if (msg.what == 1) {
// TODO: 比如计算一下差值
handler.sendEmptyMessageDelayed(1, 5 * 60 * 1000);
}
}
};
handler.sendEmptyMessageDelayed(1, 5 * 60 * 1000);
}
(1)无法获取应用的数据流量消耗
从文档中仅能获取到指定uid的流量,但无法区分不同网络类型下的消耗
(2)无法获取某个时间段内的流量消耗
3.5 通过 NetworkStatsManager 来统计流量
精准统计流量消耗的方法
可以获取指定时间间隔内的流量消耗。
NetworkCapabilities.TRANSPORT_WIFI
NetworkStatsManager networkStatsManager = (NetworkStatsManager) getSystemService(NETWORK_STATS_SERVICE);
//查询 指定网络类型 在 某时间间隔内 的总的流量统计信息
NetworkStats.Bucket querySummaryForDevice(int networkType, String subscriberId, long startTime, long endTime)
//查询某uid在 指定网络类型 和 时间间隔内 的流量统计信息
NetworkStats queryDetailsForUid(int networkType, String subscriberId, long startTime, long endTime, int uid)
//查询 指定网络类型 在 某时间间隔内 的详细的流量统计信息(包括每个uid)
NetworkStats queryDetails(int networkType, String subscriberId, long startTime, long endTime)
需要添加权限
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" tools:ignore="ProtectedPermissions"/>
使用
@RequiresApi(api = Build.VERSION_CODES.M)
public static long getNetStats(@NonNull Context context, long startTime, long endTime, int netType) {
// 接收的流量
long netDataReceive = 0;
// 发送的流量
long netDataSend = 0;
String subId = null;
// 通过 service获取
NetworkStatsManager manager = (NetworkStatsManager) context.getApplicationContext().
getSystemService(Context.NETWORK_STATS_SERVICE);
if (manager == null) {
return 0;
}
NetworkStats networkStats = null;
NetworkStats.Bucket bucket = new NetworkStats.Bucket();
try {
// //查询 指定网络类型, 在 某时间间隔内 的总的流量统计信息
networkStats = manager.querySummary(netType, subId, startTime, endTime);
} catch (Exception e) {
e.printStackTrace();
}
//查询某应用(uid)的数据流量统计信息
while (networkStats != null && networkStats.hasNextBucket()) {
networkStats.getNextBucket(bucket);
int uid = bucket.getUid();
if (getAppUid(context) == uid) {
// 接收的流量
netDataReceive += bucket.getRxBytes();
// 发送的流量
netDataSend += bucket.getTxBytes();
}
}
// 返回总流量, 这个量跟在设置里面看应该是差不多的
return (netDataReceive + netDataSend);
}
这个方式在线上排查问题还是有用的,比如我们可以获取用户昨天的流量消耗情况,这些信息一起写入用户行为日志中,进行辅助排查。
这个时候最好带上用户所处的状态,比如是否在前后台等等。
前后台流量消耗统计
当然,我们也可启用定时任务,如TrafficStats获取流量的方式,隔一段时间记录一次,并且区分前后台,然后统一上报。
网络请求流量优化
四、优化
在应用内使用网络的情况非常多,比如各种功能接口api,图片,日志等,那么我们要优化的话,可以从下面几个方面入手。
- 数据缓存,一些不常用的数据进行网络缓存,设定过期时间。
- 增量更新,根据版本进行更新。
- 数据压缩,如gzip。
- 图片处理,进行压缩
页面打不开、打开慢、图片加载不出来等等大部分都是由于网络加载慢,请求成功率低导致。
我们先来看下http请求的流程,
图片来源网络
dns被劫持,或者dns解析速度慢,都是导致网络慢,所以要防劫持,可以使用httpdns,绕过运营商域名解析,我们稍微解释下 httpdns,
HTTPDNS是面向多端应用(移动端APP,PC客户端应用)的域名解析服务,具有域名防劫持、精准调度、实时解析生效的特性。
其工作流程是这样的:图片来源于 阿里dns文章
使用httpdns可以降低平均访问时长,提高链接成功率,防止劫持。
尽量使用最新版的http协议,里面有很多的网络优化。
另外我们自己需要统计一下接口请求耗时,成功率,错误码等等,通过okhttp 的eventlistener就可以实现,如果是其他的网络链接,可自行调研。
public class OkHttpEventListener extends EventListener {
@Override
public void callStart(Call call) {
super.callStart(call);
}
@Override
public void dnsStart(Call call, String domainName) {
super.dnsStart(call, domainName);
}
@Override
public void dnsEnd(Call call, String domainName, List<InetAddress> inetAddressList) {
super.dnsEnd(call, domainName, inetAddressList);
}
@Override
public void responseBodyStart(Call call) {
super.responseBodyStart(call);
}
@Override
public void responseBodyEnd(Call call, long byteCount) {
super.responseBodyEnd(call, byteCount);
}
@Override
public void callEnd(Call call) {
super.callEnd(call);
}
@Override
public void callFailed(Call call, IOException ioe) {
super.callFailed(call, ioe);
}
...
}
我们可以看到,里面有很多的回调,在对应的方法里面计算时间差,就可以算出请求时间。
而对于图片监控来说, 如果使用的是 Fresco ,则直接可以使用sdk提供的方法来做监控,在内部有一个
com.facebook.imagepipeline.listener.RequestListener,类 ,可以监控到具体的每一步。
我们只需要在fresco初始化时,设置一个监听即可
ImagePipelineConfig config = ImagePipelineConfig
.newBuilder(mContext)
.setRequestListeners(listenerset) // 设置监听
.setRequestListener2s() // 设置监听
.build();
Fresco.initialize(mContext,config);
通过接口,可以拿到成功失败及失败原因,
public interface RequestListener2 extends ProducerListener2 {
void onRequestStart(ProducerContext producerContext);
void onRequestSuccess(ProducerContext producerContext);
void onRequestFailure(ProducerContext producerContext, Throwable throwable);
void onRequestCancellation(ProducerContext producerContext);
}
如果是其他的图片加载库,可自行调研。
最后,针对服务端,需要做好网络容灾方案。
五、 推荐阅读
Java 专栏
SQL 专栏
数据结构与算法
Android学习专栏