part 7 App网络优化

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zsjlovesm521/article/details/89085441

Part 7 App网络优化

一 网络优化从哪些纬度开展

1、网络优化介绍

正确认识

1、网络优化需要从多个纬度展开
2、仅仅重视流量不够
3、网络流量消耗量:精准
4、整体均值掩盖单点问题(如:用户反馈app费流量,只统计流量消耗,不统计使用时长不好断定,还有前后台消耗流量的区分)
5、网络相关监控要全面
6、粗粒度监控不能帮助我们发现、解决深层次问题

2、网络优化纬度

流量消耗

一段时间流量消耗的精准度量,网络类型、前后台
监控相关:用户流量消耗均值、异常率(消耗多、次数多)
完整链路全部监控(Request、Response记录下来),主动上报(超过阈值)

网络请求质量

用户体验:请求速度、成功率
监控相关:请求时长、业务成功率、失败率、top失败接口

其他

公司成本:带宽、服务器数、CDN
耗电(网络请求密集)

3、网络优化误区

只关注流量消耗,忽视其他纬度
只关注平均值、整体,忽略个体

二 网络优化工具选择

1、NetWork Profiler

显示实时网络活动:发送、接收数据及连接数
需要启用高级分析
只支持HttpURLConnection和Okhttp网络库

启动高级分析功能
在这里插入图片描述

使用
在这里插入图片描述

2、抓包工具

工具的使用自己可以查看相关文档进行学习使用方法

Charles
Fiddler
wireshark
TcpDump

3、Stetho

应用调试桥,连接Android和Chrome
网络监控、视图查看、数据库查看、命令行扩展等

使用(相对与Charles来讲,功能不是很强大)

    implementation 'com.facebook.stetho:stetho:1.5.0'
    implementation 'com.facebook.stetho:stetho-okhttp3:1.5.0'
    //初始化
    Stetho.initializeWithDefaults(mContext);
    
    public class RetrofitNewsUtils {
    private static final APIService API_SERVICE;

    public static APIService getApiService() {
        return API_SERVICE;
    }

    public static final String HTTP_SPORTSNBA_QQ_COM = "http://sportsnba.qq.com/";

    static {
        OkHttpClient.Builder client = new OkHttpClient.Builder();
        HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
        logging.setLevel(HttpLoggingInterceptor.Level.BODY);
        Cache cache = new Cache(PerformanceApp.getApplication().getCacheDir(),10*1024*1024);
        //添加拦截器
        client.addNetworkInterceptor(new StethoInterceptor())
        final Retrofit RETROFIT = new Retrofit.Builder()
                .baseUrl(HTTP_SPORTSNBA_QQ_COM)
                .addConverterFactory(FastJsonConverterFactory.create())
                .client(client.build())
                .build();
        API_SERVICE = RETROFIT.create(APIService.class);
    }


}
    
    Chrome浏览器:chrome://inspect

在这里插入图片描述

三 精准获取流量消耗

问题思考:如何判断app流量消耗偏高

绝对值看不出高低
对比竞品,相同Case对比流量消耗
异常监控,超过正常指标

测试方案

设置——流量管理
抓包工具:只允许本App联网
可以解决大多数问题,但是线上场景线下可能遇不到

1、线上线下流量获取

线上流量获取方案

TrafficStats:API8以上重启以来的流量数据统计
getUidRxVytes(int uid)指定Uid的接收流量
getTotalTxBytes()总发送流量
总结:无法获取某个时间段内的流量消耗

NetworkStatsManager:API23之后流量统计
可获取指定时间间隔内的流量信息
可获取不同网络类型下的消耗

private void getNetStatus(long startTime, long endTime) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            return;
        }
        long netDataRx = 0;//接收
        long netDataTx = 0;//发送

        TelephonyManager telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
        String subId = telephonyManager.getSubscriberId();
        @SuppressLint("WrongConstant")
        NetworkStatsManager manager = (NetworkStatsManager) getSystemService(Context.NETWORK_STATS_SERVICE);
        NetworkStats networkStats = null;
        NetworkStats.Bucket bucket = new NetworkStats.Bucket();
        try {
            networkStats = manager.querySummary(NetworkCapabilities.TRANSPORT_WIFI, subId, startTime, endTime);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        while (networkStats != null && networkStats.hasNextBucket()) {
            networkStats.getNextBucket(bucket);
            int uid = bucket.getUid();
            if (getUidByPackageName() == uid) {
                netDataRx += bucket.getRxBytes();
                netDataTx += bucket.getTxBytes();
            }
        }

    }


    public int getUidByPackageName() {
        int uid = -1;
        PackageManager packageManager = MainActivity.this.getPackageManager();
        try {
            PackageInfo packageInfo = packageManager.getPackageInfo(MainActivity.this.getPackageName(), 0);
            uid = packageInfo.applicationInfo.uid;
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        return uid;
    }

2、前台后台流量获取

难题:线上反馈App后台跑流量,只获取一个时间段的值不够全面
方案:
在这里插入图片描述

getApplication().registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() {
            @Override
            public void onActivityCreated(Activity activity, Bundle savedInstanceState) {

            }

            @Override
            public void onActivityStarted(Activity activity) {

            }

            @Override
            public void onActivityResumed(Activity activity) {
                //认为进入前台
                appIsFront = true;
            }

            @Override
            public void onActivityPaused(Activity activity) {
                //认为进入后台
                appIsFront = false;
            }

            @Override
            public void onActivityStopped(Activity activity) {

            }

            @Override
            public void onActivitySaveInstanceState(Activity activity, Bundle outState) {

            }

            @Override
            public void onActivityDestroyed(Activity activity) {

            }
        });

        Executors.newScheduledThreadPool(1).schedule(new Runnable() {
            @Override
            public void run() {
                long netUse = getNetStatus(System.currentTimeMillis() - 30 * 1000, System.currentTimeMillis());//开始时间:当前时间-30秒,结束时间:就是当前时间
                //前台还是后台
                if (appIsFront) {
                    //前台
                } else {
                    //后台
                }
            }
        }, 30, TimeUnit.SECONDS);
        
        
    private boolean appIsFront = false;

    private long getNetStatus(long startTime, long endTime) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            return 0;
        }
        long netDataRx = 0;//接收
        long netDataTx = 0;//发送

        TelephonyManager telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
        String subId = telephonyManager.getSubscriberId();
        @SuppressLint("WrongConstant")
        NetworkStatsManager manager = (NetworkStatsManager) getSystemService(Context.NETWORK_STATS_SERVICE);
        NetworkStats networkStats = null;
        NetworkStats.Bucket bucket = new NetworkStats.Bucket();
        try {
            networkStats = manager.querySummary(NetworkCapabilities.TRANSPORT_WIFI, subId, startTime, endTime);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        while (networkStats != null && networkStats.hasNextBucket()) {
            networkStats.getNextBucket(bucket);
            int uid = bucket.getUid();
            if (getUidByPackageName() == uid) {
                netDataRx += bucket.getRxBytes();
                netDataTx += bucket.getTxBytes();
            }
        }
        return netDataRx + netDataTx;
    }

总结:
有一定的误差,在可接收范围内
结合精细化的流量异常报警针对性的解决后台跑流量

四 网络请求流量优化

1、使用网络的场景概述

数据:Api、资源包(升级包、H5、RN)、配置信息
图片:上传、下载
监控:APM相关、单点问题相关

2、数据缓存

服务端返回加上过期时间,避免每次重新获取
节约流量且大幅提高数据访问速度,更好的用户体验
Okhttp、Volley都有较好的实践

使用:

//创建拦截器
public class NoNetInterceptor implements Interceptor {

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        Request.Builder builder = request.newBuilder();
        //无网络的情况下使用缓存
        if(!Utils.isNetworkConnected(PerformanceApp.getApplication())){
            builder.cacheControl(CacheControl.FORCE_CACHE);
        }
        return chain.proceed(builder.build());
    }
}



public class RetrofitNewsUtils {
    private static final APIService API_SERVICE;

    public static APIService getApiService() {
        return API_SERVICE;
    }

    public static final String HTTP_SPORTSNBA_QQ_COM = "http://sportsnba.qq.com/";

    static {
        OkHttpClient.Builder client = new OkHttpClient.Builder();
        HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
        logging.setLevel(HttpLoggingInterceptor.Level.BODY);
        //设置缓存目录
        Cache cache = new Cache(PerformanceApp.getApplication().getCacheDir(),10*1024*1024);
        //设置拦截器
        client.
                eventListenerFactory(OkHttpEventListener.FACTORY).
                addInterceptor(new NoNetInterceptor()).
                addInterceptor(logging);

        final Retrofit RETROFIT = new Retrofit.Builder()
                .baseUrl(HTTP_SPORTSNBA_QQ_COM)
                .addConverterFactory(FastJsonConverterFactory.create())
                .client(client.build())
                .build();
        API_SERVICE = RETROFIT.create(APIService.class);
    }


}

3、数据增量更新

加上版本的概念,只传输有变化的数据
配置信息、省市区等更新

4、数据压缩

Post请求Body使用GZip压缩
请求头压缩(只传递一次,以后只传递请求头的MD5值,服务端从之前的请求头中取)
图片上传之前必须压缩

    //图片压缩库
    implementation 'top.zibin:Luban:1.1.8'
    
    // 以下代码是为了演示Luban这个库对图片压缩对流量方面的影响
    Luban.with(holder.imageView.getContext())
            .load(Environment.getExternalStorageDirectory()+"/Android/1.jpg")
            .setTargetDir(Environment.getExternalStorageDirectory()+"/Android")
            .launch();    
    
    //在demo中可以将1.94MB的图片压缩至446KB,且预览质量无差别

5、优化发送频率和时机

合并网络请求,减少请求次数(如埋点数据的统一上传)
性能日志上报:批量+特定场景上传(只在WiFi情况下上传)

6、图片优化

图片使用策略细化:优化缩略图
使用WebP格式图片(https://www.upyun.com/products/process#pic 里面有介绍)
在这里插入图片描述

五 网络请求质量优化

1、质量指标

网路请求成功率,网络请求速度

http请求过程

1、请求到达运营商的DNS服务器并解析成对应的IP地址
2、创建链接(TCP三次握手),根据IP地址找到相应的服务器,发送一个请求
3、服务器找到对应资源原路返回给访问的用户

DNS相关

问题:DNS被劫持,DNS解析慢
方案:使用HttpDNS,绕过运营商域名解析过程(传统DNS向DNS53端口发送,HttpDNS向80端口发送)
优势:降低平均访问时长、提高连接成功率

参考文档:
https://blog.csdn.net/z_xiaozhuT/article/details/80596469 (深入理解Http请求、DNS劫持与解析)
https://blog.csdn.net/yb223731/article/details/82858057 (HttpDNS 服务详解)

2、使用OKhttp和HttpDNS

    //依赖
    compile ('com.aliyun.ams:alicloud-android-httpdns:1.1.7@aar') {
        transitive true
    }
    
    //添加OkHttpDNS解析类
    public class OkHttpDNS implements Dns {

    private HttpDnsService dnsService;
    private static OkHttpDNS instance = null;

    private OkHttpDNS(Context context) {
        dnsService = HttpDns.getService(context, "");
    }

    public static OkHttpDNS getIns(Context context) {
        if (instance == null) {
            synchronized (OkHttpDNS.class) {
                if (instance == null) {
                    instance = new OkHttpDNS(context);
                }
            }
        }
        return instance;
    }

    @Override
    public List<InetAddress> lookup(String hostname) throws UnknownHostException {
        String ip = dnsService.getIpByHostAsync(hostname);
        //如果不为空走OkhttpDNS解析
        if(ip != null){
            List<InetAddress> inetAddresses = Arrays.asList(InetAddress.getAllByName(ip));
            return inetAddresses;
        }
        //如果为空走系统的DNS解析
        return Dns.SYSTEM.lookup(hostname);
        }
    }   


public class RetrofitNewsUtils {
    private static final APIService API_SERVICE;

    public static APIService getApiService() {
        return API_SERVICE;
    }

    public static final String HTTP_SPORTSNBA_QQ_COM = "http://sportsnba.qq.com/";

    static {
        OkHttpClient.Builder client = new OkHttpClient.Builder();
        HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
        logging.setLevel(HttpLoggingInterceptor.Level.BODY);
        Cache cache = new Cache(PerformanceApp.getApplication().getCacheDir(),10*1024*1024);
        //在OkHttpClient中添加dns
        client.
                eventListenerFactory(OkHttpEventListener.FACTORY).
                dns(OkHttpDNS.getIns(PerformanceApp.getApplication())).
                addInterceptor(new NoNetInterceptor()).
                addInterceptor(logging);

        final Retrofit RETROFIT = new Retrofit.Builder()
                .baseUrl(HTTP_SPORTSNBA_QQ_COM)
                .addConverterFactory(FastJsonConverterFactory.create())
                .client(client.build())
                .build();
        API_SERVICE = RETROFIT.create(APIService.class);
    }
}

3、协议版本升级

1.0:版本的TCP连接不复用
1.1:引入持久连接,但数据通讯按次序进行
2:多工,客户端、服务器双向实时通信

4、网络请求质量监控

接口请求耗时、成功率、错误码等
图片加载的每一步耗时

请求的监控

实现okhttp的EventListener
public class OkHttpEventListener extends EventListener {

    public static final Factory FACTORY = new Factory() {
        @Override
        public EventListener create(Call call) {
            return new OkHttpEventListener();
        }
    };

    OkHttpEvent okHttpEvent;
    public OkHttpEventListener() {
        super();
        okHttpEvent = new OkHttpEvent();
    }

    @Override
    public void callStart(Call call) {
        super.callStart(call);
        Log.i("lz","callStart");
    }

    @Override
    public void dnsStart(Call call, String domainName) {
        super.dnsStart(call, domainName);
        okHttpEvent.dnsStartTime = System.currentTimeMillis();
    }

    @Override
    public void dnsEnd(Call call, String domainName, List<InetAddress> inetAddressList) {
        super.dnsEnd(call, domainName, inetAddressList);
        okHttpEvent.dnsEndTime = System.currentTimeMillis();
    }

    @Override
    public void connectStart(Call call, InetSocketAddress inetSocketAddress, Proxy proxy) {
        super.connectStart(call, inetSocketAddress, proxy);
    }

    @Override
    public void secureConnectStart(Call call) {
        super.secureConnectStart(call);
    }

    @Override
    public void secureConnectEnd(Call call, @Nullable Handshake handshake) {
        super.secureConnectEnd(call, handshake);
    }

    @Override
    public void connectEnd(Call call, InetSocketAddress inetSocketAddress, Proxy proxy, @Nullable Protocol protocol) {
        super.connectEnd(call, inetSocketAddress, proxy, protocol);
    }

    @Override
    public void connectFailed(Call call, InetSocketAddress inetSocketAddress, Proxy proxy, @Nullable Protocol protocol, IOException ioe) {
        super.connectFailed(call, inetSocketAddress, proxy, protocol, ioe);
    }

    @Override
    public void connectionAcquired(Call call, Connection connection) {
        super.connectionAcquired(call, connection);
    }

    @Override
    public void connectionReleased(Call call, Connection connection) {
        super.connectionReleased(call, connection);
    }

    @Override
    public void requestHeadersStart(Call call) {
        super.requestHeadersStart(call);
    }

    @Override
    public void requestHeadersEnd(Call call, Request request) {
        super.requestHeadersEnd(call, request);
    }

    @Override
    public void requestBodyStart(Call call) {
        super.requestBodyStart(call);
    }

    @Override
    public void requestBodyEnd(Call call, long byteCount) {
        super.requestBodyEnd(call, byteCount);
    }

    @Override
    public void responseHeadersStart(Call call) {
        super.responseHeadersStart(call);
    }

    @Override
    public void responseHeadersEnd(Call call, Response response) {
        super.responseHeadersEnd(call, response);
    }

    @Override
    public void responseBodyStart(Call call) {
        super.responseBodyStart(call);
    }

    @Override
    public void responseBodyEnd(Call call, long byteCount) {
        super.responseBodyEnd(call, byteCount);
        okHttpEvent.responseBodySize = byteCount;
    }

    @Override
    public void callEnd(Call call) {
        super.callEnd(call);
        okHttpEvent.apiSuccess = true;
    }

    @Override
    public void callFailed(Call call, IOException ioe) {
        Log.i("lz","callFailed ");
        super.callFailed(call, ioe);
        okHttpEvent.apiSuccess = false;
        okHttpEvent.errorReason = Log.getStackTraceString(ioe);
        Log.i("lz","reason "+okHttpEvent.errorReason);
    }
}


//实体信息类
public class OkHttpEvent {
    public long dnsStartTime;
    public long dnsEndTime;
    public long responseBodySize;
    public boolean apiSuccess;
    public String errorReason;
}


//添加到OkHttpClient中
  client.
        eventListenerFactory(OkHttpEventListener.FACTORY).
        dns(OkHttpDNS.getIns(PerformanceApp.getApplication())).
        addInterceptor(new NoNetInterceptor()).
        addInterceptor(logging);

5、网络容灾机制

备用服务器分流
多次失败后一定时间内不进行请求,避免雪崩效应

6、其他

CDN加速、提高带宽、动静资源分离(更新后清理缓存)
减少传输量,注意请求时机及频率
OKhttp的请求池

六 网络体系化的方案建设

1、线下测试相关

方案:至抓单独App
侧重点:请求有误、多余、网络切换、弱网、无网测试

2、线上监控相关

服务端:

请求耗时(区分地域、时间段、版本、机型)
失败率(业务失败和请求失败)
Top失败接口、异常接口

客户端:

接口每一步详细信息(DNS、连接、请求等)
请求次数、网络包大小、失败原因
图片监控

异常监控体系

服务器防刷:超限拒绝访问
客户端大文件预警、异常兜底策略(如重连5次都失败了,延长重连时间,或者取消)
单点问题追查

猜你喜欢

转载自blog.csdn.net/zsjlovesm521/article/details/89085441
今日推荐