android产品研发(八)-->App数据统计

转载请标明出处:一片枫叶的专栏

上一篇文章中我们介绍了android社区中比较火的热修复功能,并介绍了目前的几个比较流行的热修复框架,以及各自的优缺点,同时也介绍了一下自身项目中对热修复功能的实践。目前主流的热修复原理上其实分为两种,一种是通过利用dex的加载顺序实现热修复功能,一种是通过native层实现指针替换实现热修复功能,两种各有利弊可以根据自身产品的需要选择不同的方案。

而文本将要介绍一下android产品中另一项基础功能-数据统计。App数据统计的意义在于通过统计用户的行为方式有针对性的更新展示算法,根据用户的行为习惯更新产品功能等等,具体而言当我们开发好App后就会把它发到应用市场上,但是目前有很多的应用市场(如,豌豆荚,应用宝,安卓市场等),那么问题来了,假如我们想统计我们开发的应用的下载次数,就必须使用统计吧,而且它不仅可以统计我们的应用的下载量,启动次数,还可以统计页面访问量、查看程序的bug等等,所以相对于项目而言产品由于存在着持续的迭代与用户体验,所以做好数据统计工作是一项必不可少的工作。

本文中数据统计主要介绍两种:第三方统计服务和自己实现数据统计功能。

相对而言这两种方式各有利弊,第三方统计服务简单、方便、统计范围广,但是数据保存在第三方,对于一些数据比较敏感的App可能不太喜欢这种方式,而自己实现数据统计功能主要优点就是安全可定制化,当然了缺点也是显而易见的,就是繁琐,复杂。

下面我分别就这两种数据统计方式做一下简单的介绍。

  • 第三方统计服务

一般情况下App中都是使用第三方数据统计服务的,友盟、百度统计等等,这里简单介绍一下友盟统计,其余的都是大同小异。

友盟统计官网
友盟统计中不但有统计相关功能,还提供了错误分析,社会化分享,推送,即时通讯等等,当然这里我们重点分析的是其数据统计功能。

在友盟官网中我们可以看到其对统计服务的介绍,这里我们大概的看一下:
这里写图片描述
可以发现我们提供了用户统计,渠道统计,页面访问路径统计,点击事件统计等等。
那么我们如何才能继承友盟的统计功能的,其在SDK文档介绍中已经做了详细介绍,大概的集成流程是下载友盟SDK jar包,然后引用,并在相应的Activity/Fragment的生命周期方法中调用相关的API。

  • 自己实现数据统计功能

对于一些数据敏感的App来说可能自己实现部分数据统计功能是一个比较不错的选择,当初笔者也做过类似的功能,其原理就是参考友盟的数据统计在App打开的时候执行数据上报功能。

这里简单介绍一下自己实现的数据统计功能的流程:
(1)数据统计上报主要分为两中方式,网络请求上报和文件信息上报;
(2)网络请求上报就是直接调用网络请求若请求失败,则将上报信息保存至本地文件中;
(3)本地上报文件设置阙值和间隔时间,若距离上次上报时间大于阙值或者是文件大小大于阙值则执行上报操作,若上报成功则删除本地数据文件;
(4)本地数据文件在上报之前有加密和压缩操作;
(5)在App打开的时候执行一次数据上报操作,在特定的操作下可以执行上报操作;
(6)上报操作是在Service服务中执行,避免上报操作被由于进程被杀死而中断;

最后贴上为了实现数据上报而自定义的数据统计服务:

/**
 * Created by aaron on 2016/4/8.
 * desc:UU打点service
 */
public class UUPointService extends Service {
    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        /**
         * desc:该service为APP打点service
         * 主要实现两个后台服务:
         * (1)内存数据上报;
         * (2)文件数据上报;
         */
        if (intent != null && intent.getStringExtra(UUPoint.LOADTYPE) != null && intent.getStringExtra(UUPoint.LOADTYPE).trim().equals(UUPoint.LOADDATA)) {
            if (UUPoint.writeMap.size() > 0) {
                //(1)转换数据
                final List<String> strList = new ArrayList<String>();
                Iterator<Map.Entry<String, Integer>> iterator = UUPoint.writeMap.entrySet().iterator();
                while (iterator.hasNext()) {
                    StringBuffer sb = new StringBuffer();
                    Map.Entry<String, Integer> entry = iterator.next();
                    String key = entry.getKey();
                    Integer count = entry.getValue();
                    sb.append(key).append(",").append(count);
                    strList.add(sb.toString());
                }
                if (strList.size() > 0) {
                    // 上传服务器数据上传服务器数据
                    SystemCommon.ReportLogBatch.Builder builder = SystemCommon.ReportLogBatch.newBuilder();
                    builder.addAllLogLine(strList);
                    SystemInterface.ReportLogInfoRequest.Builder request = SystemInterface.ReportLogInfoRequest.newBuilder();
                    request.setLogData(ByteString.copyFrom(GZipUtils.compress(builder.build().toByteArray())));// 压缩数据包
                    NetworkTask task = new NetworkTask(CmdCodeDef.CmdCode.ReportLogInfo_VALUE);
                    task.setBusiData(request.build().toByteArray());
                    NetworkUtils.executeNetwork(task, new HttpResponse.NetWorkResponse<UUResponseData>() {
                        @Override
                        public void onSuccessResponse(UUResponseData responseData) {
                            if (responseData.getRet() == 0) {
                                try {
                                    SystemInterface.ReportLogInfoResponse response = SystemInterface.ReportLogInfoResponse.parseFrom(responseData.getBusiData());
                                    if (response.getRet() == 0) {
                                        /*MLog.i("tab", "########################");
                                        for (String str : strList) {
                                            MLog.i("tab", str);
                                        }*/
                                        UUPoint.writeMap.clear();
                                        UUPointUtils.setLastPointTime(UUPointService.this);
                                        /*MLog.i("tab", "上报数据完成################");*/
                                    } else {
                                        UUPoint.saveWriteToIO(UUPointService.this);// 将写map中的数据持久化到IO
                                    }
                                } catch (Exception e) {
                                    UUPoint.saveWriteToIO(UUPointService.this);// 将写map中的数据持久化到IO
                                }
                            } else {
                                UUPoint.saveWriteToIO(UUPointService.this);// 将写map中的数据持久化到IO
                            }
                        }

                        @Override
                        public void onError(VolleyError errorResponse) {
                            MLog.i("tab", "保存到本地文件");
                            UUPoint.saveWriteToIO(UUPointService.this);
                        }

                        @Override
                        public void networkFinish() {
                        }
                    });
                }
            }
        }
        // 上传文件
        else if (intent != null && intent.getStringExtra(UUPoint.LOADTYPE) != null && intent.getStringExtra(UUPoint.LOADTYPE).trim().equals(UUPoint.LOADFILE)) {
            File file = new File(Config.CountFile);
            List<File> fileList = Arrays.asList(file.listFiles());
            if (fileList != null && fileList.size() > 0) {
                //按文件名称排序
                Collections.sort(fileList, new Comparator<File>() {
                    @Override
                    public int compare(File lhs, File rhs) {
                        return rhs.getName().compareTo(lhs.getName());
                    }
                });
                // 上传文件
                for (final File files : fileList) {
                    // 先解密,在使用票据加密
                    FileEncrypter.decrypt(files, UUPoint.secretKey);// 解密
                    if (Config.isNetworkConnected(UUPointService.this)) {
                        NetworkTask networkTask = new NetworkTask(CmdCodeDef.CmdCode.ReportLogFile_VALUE);
                        networkTask.setUploadDataFile(true);// 上传大点数据文件
                        networkTask.setBusiData(UUPointUtils.getBytesFromFile(files));
                        NetworkUtils.executeNetwork(networkTask, new HttpResponse.NetWorkResponse<UUResponseData>() {
                            @Override
                            public void onSuccessResponse(UUResponseData responseData) {
                                if (responseData.getRet() == 0) {
                                    try {
                                        SystemInterface.ReportLogInfoResponse response = SystemInterface.ReportLogInfoResponse.parseFrom(responseData.getBusiData());
                                        if (response.getRet() == 0) {
                                            // 上传成功, 删除加密文件
                                            files.delete();
                                            MLog.i("tab", "上传文件完成" + files.getName() + "######################");
                                        } else {
                                            FileEncrypter.encrypt(files, UUPoint.secretKey);// 上传失败, 重新加密文件
                                        }
                                    } catch (Exception e) {
                                        e.printStackTrace();
                                        FileEncrypter.encrypt(files, UUPoint.secretKey);// 上传失败, 重新加密文件
                                    }
                                } else {
                                    FileEncrypter.encrypt(files, UUPoint.secretKey);// 上传失败, 重新加密文件
                                }
                            }

                            @Override
                            public void onError(VolleyError errorResponse) {
                                MLog.i("tab", errorResponse.toString());
                                FileEncrypter.encrypt(files, UUPoint.secretKey);// 上传失败, 重新加密文件
                            }

                            @Override
                            public void networkFinish() {
                            }
                        });
                    }
                }
            }
        }

        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}

总结:
一般而言直接使用第三方统计服务就已经可以满足绝大部分的业务需求了,当然了如果数据比较敏感也可以自己实现数据上报统计的功能。

另外对产品研发技术,技巧,实践方面感兴趣的同学可以参考我的:
android产品研发(一)–>实用开发规范
android产品研发(二)–>启动页优化
android产品研发(三)–>基类Activity
android产品研发(四)–>减小Apk大小
android产品研发(五)–>多渠道打包
android产品研发(六)–>Apk混淆
android产品研发(七)–>Apk热修复


本文以同步至github中:https://github.com/yipianfengye/androidProject,欢迎star和follow


本页内容版权归属为原作者,如有侵犯您的权益,请通知我们删除。

游戏中的AI算法总结与改进 - 2016-06-08 14:06:59

参考文章: http://games.sina.com.cn/zl/duanpian/2014-03-11/105973.shtml http://www.oschina.net/translate/understanding-steering-behaviors-collision-avoidance?cmp http://blog.csdn.net/ityuany/article/details/5509750 一. 人工智能的定义         人工智能(AI, Artificial Intell

安卓2048小游戏源码 - 2016-06-08 14:06:42

http://git.oschina.net/scimence/sci_2048/wikis/home package com.example.sci_2048;import java.util.Random;import android.app.Activity;import android.app.AlertDialog;import android.content.DialogInterface;import android.graphics.Color;import android.os.Bund
Android permission 基础知识 Android是一个多进程系统,在这个系统中,应用程序(或者系统的部分)会在自己的进程中运行。系统和应用之间的安全性是通过Linux的facilities(工具,功能)在进程级别来强制实现的,比如会给应用程序分配user ID和Group ID。更细化的安全特性是通过"Permission"机制对特定的进程的特定的操作进行限制,而"per-URI permissions"可以对获取特定数据的access专门权限进行限制。 安全架构 Android安全架构中一
为延长设备的电池寿命、降低内存使用率以及提升应用性能,Android N 对系统行为做出了一些变更。这些变更可能会影响系统资源和系统通知对应用的可用性。您应仔细检查这些变更并评估应用可能需要做何调整以适应这些变更。 打盹         Android 6.0(API 级别 23)引入了打盹模式,当用户设备未插接电源、处于静止状态且屏幕关闭时,该模式会推迟 CPU 和网络活动,从而延长电池寿命。而 Android N 则通过在设备未插接电源且屏幕关闭状态下、但不一定要处于静止状态(例如用户外出时把手持式设
在Android应用开发中,我们经常需要跟图片打交道,而图片一个很麻烦的问题是占用内存非常大,经常导致OOM,了解Bitmap相关信息,不同sdk版本中Android图片处理的变化,以及一些优化处理的方式对我们平时开发中对图片的会非常有帮助。 这篇先介绍Bitmap基础内容,关于像素,存储信息,以及加载。 像素 Bitmap的存储可以说包括两个部分,像素以及长,宽,颜色等描述信息。像素是Bitmap最占用内存的地方,长宽和像素位数是用来描述图片的,可以通过这些信息计算出图片的像素占用的内存大小。具体到Bi

玩转Android之Drawable的使用 - 2016-06-08 14:06:35

Drawable天天用,可你是否对Drawable家族有一个完整的认知?今天我们就来系统的学习一下Drawable的使用。 1.概述 用过Drawable的筒子都知道Drawable有很多种,有的时候Drawable是一张图片,有的时候Drawable是我们通过颜色构造出来的某种图形。最常见的自己构造的Drawable图形莫过于ShapeDrawable,我们在开发中可能经常需要自己绘制一个矩形、圆形、椭圆等等各种各样的图形。一般来说,Drawable并没大小的概念(虽然可以通过getIntrinsicH
Alt+回车 导入包,自动修正 Shift+F6  重构-重命名 Ctrl+Shift+先上键 Ctrl+Y 删除行 ( ctrl+x不是删除行,是剪切。如果不选中,则为剪切当行。ths for 貌似掉线 ) Ctrl+D 复制行 Ctrl+/ 或 Ctrl+Shift+/  注释(// 或者/*...*/ ) Ctrl+J  自动代码 Ctrl+E 最近打开的文件 Ctrl+H 显示类结构图 Ctrl+Q 显示注释文档 Alt+F1 查找代码所在位置 Alt+1 快速打开或隐藏工程面板 Ctrl+Alt
写在前面: Reference本身是一个接口,表示一个引用,不能直接使用,有四个它的派生类供我们使用,它们分别是:SoftReference,WeakReference,PhantomReference,FinalizerReference .其中SoftReference,WeakReference和 PhantomReference的区别与使用Google一下已经有大把的介绍资料,因此本文对此只简单说明带过,主要给大家介绍你不知道的Reference. 一. SoftReference SoftRef
出现这个原因我个人感觉有两个。一、5037端口被别的程序或者进程占用;二、adb占用的不是5037端口。很多人仅仅知道第一种二忽视了第二种。 不管哪个原因,解决问题的办法都一样。这里做一个整理如下: 1. 错误信息: C:\Users\lizyadb devices adb devicesadb server is out of date.  killing... ADB server didn't ACK* failed to start daemon *error: unknown host serv

ViewDragHelper详解(侧滑栏) - 2016-06-08 14:06:25

乐观是一首激昂优美的进行曲,时刻鼓舞着你向事业的大路勇猛前进。——大仲马 一、概述 Drag 拖拽; ViewDrag 拖拽视图,拖拽控件; ViewDragHelper 拖拽视图助手,拖拽操作类。利用 ViewDragHelper 类可以实现很多绚丽的效果,比如:拖拽删除,拖拽排序,侧滑栏等。本篇主要讲解简易侧滑栏的实现。 注意 : ViewDragHelper 是作用在一个 ViewGroup 上,也就是说他不能直接作用到被拖拽的控件 view 上, 因为控件的位置是由父控件决定的。 最终效果效果图: