一个阶段又一个阶段,人生也不过如此吧……
本来想稍微歇歇的,但萌萌“突发奇想”,为软件自身加一个流量查看的模块,由于软件本身就要经常的上传一些文字和图片(当然了,这是它的主要功能),还有一些坐标信息等,同时还要从服务端接收一些通知等,本身就是个耗油的家伙,那么用户就很关心这个流量的使用状况。
话又说话来,现在市场上有很多主流的流量统计软件,比如360的,部分版本系统也加入了流量统计的功能如ICS4.0和小米的系统,这不是重复制造轮子嘛!!
但随后一想,如果加上了这一功能,也许会增加软件的实用性,在同类软件中增加一些竞争力也说不准,不就是添加一个菜单,然后显示一个Activity嘛,好简单哦!2天搞定嘛,但事实并非如此。
如何统计流量
首先,看一下这个问题,如何统计自身软件使用的流量?
TrafficStats,听说过吧,没听说过的百度去,直接调用它的静态方法,上传,下载流量,一一搞定,但是,只支持2.2以上,看到这个提示后,直接否定使用。
看来流量统计要靠自己动手了,其实在这款软件中流量统计并没有什么技术难度,一切都是byte!只需在访问服务端的方法的过程中加上一个方法,就是将请求的实体转换成byte[],求出它的length,long length = byte[].length; 并将这个长度记录下来(数据库),就是你上传数据的大小,当你的请求成功发送后,势必会返回响应,那么响应的实体的长度,就是你下载数据的大小,把他们统统记录下来。如果访问服务端超时,将不记录请求实体的大小。
其实不想上代码,因为每个人都有自己实现的方式,因需求而定,因人而定,这才是开发嘛,但没代码是否有点干吧呢,见代码!
1 HttpClient client = new DefaultHttpClient(httpParameters);
2 MultipartEntity multipartEntity = new MultipartEntity(null, null,
3 Charset.forName("UTF-8"));
4 HttpPost post = new HttpPost(url);
5 post.setHeader("charset", "UTF-8");
6 StringBody jsonBody = new StringBody(jsonList.get(i),
7 Charset.forName("UTF-8"));
8 multipartEntity.addPart("uplinkStr", jsonBody);
9 //上传流量大小入库
10 db.insertOrUpdateTraffic(CurrentParameter.UPLENGTH, jsonList.get(i).getBytes("utf-8").length, CurrentUtils.showTime("yyyy-MM-dd"));
这是部分代码,看代码10行,中 insertOrUpdateTraffic方法中的
第一个参数是指定这是上传数据。
第二个参数是上传实体的大小(我这里只是一个json字符串,还没涉及到文件,下面会提到)。
第三个参数是产生这条流量的日期(系统时间)。
1 for(int ii = 0;ii < cpicPath;ii++){
2 isb = prepareStreamBody(context, alpicPath.get(ii),
3 alpicName.get(ii), i);
4 if(isb != null) {
5 //添加实体
6 multipartEntity.addPart("file" + i, isb);
7 //获取流的大小,也就是待上传文件的大小,并记录到数据库中
8 InputStream is = isb.getInputStream();
9 upLength = countUpLength((ByteArrayInputStream) is);
10 db.insertOrUpdateTraffic(CurrentParameter.UPLENGTH, upLength, CurrentUtils.showTime("yyyy-MM-dd"));
11 Log.i(TAG, "everyPath&name:josnNum" + i + "--" + alpicPath.get(ii) + "--" + alpicName.get(ii));
12 }
13 }
14 post.setEntity(multipartEntity);
15 HttpResponse response = client.execute(post);
再看一下上面的代码片段,在上传的过程中计算文件大小,外层是一个for循环,因为图片有点时候不是一张,而是多张上传,所以用for循环不断的向实体类中添加文件流,见第6行代码。
但这不是重点,重点在第8行开始,从实体包装类中获取到流的真正实体,通过第9行代码中的countUpLength代码计算流的大小,这就是你要上传文件的大小通过第10行代码进行数据库操作,保存起来。
问题来啦!!!
当我计算完流的大小后,执行15行代码后,服务端没有解析出文件!!!流,你到底肿么了?为什么解析不出来了呢?
那么,看一下我是怎么计算流的大小的,countUpLength代码如下:
1 /**
2 * 计算文件上传长度
3 * @param in
4 * @return
5 */
6 private static long countUpLength(ByteArrayInputStream in) throws IOException{
7 ByteArrayOutputStream baos = new ByteArrayOutputStream();
8 int b=0;
9 while( (b = in.read()) != -1){
10 baos.write(b);
11 }
12 upLength = baos.toByteArray().length;
13 return upLength;
14 }
通过以上的方法,我只是将输入流的内容通过read方法读出来,通过输出流的write方法写在内存中,然后将输出流转换成byte[],求出其属性length的值,无可厚非的方法,真确的拿到了输入流的大小,但是问题来了,可以想象一下,流的内部有一个指针,每次read后,指针就会指向当前读取的位置,下次读取流的时候,指针会从上一次读取的地方继续读取,当我为了求出流的大小,将流从头到尾读取了一遍后,它的指针就指到了流底,当我将这个流传到服务端后,服务端也会去读取流中的内容保存成文件,可是却什么也读不出来,我说过了,此时的流的指针指向了流底,在读也不会读出什么了。
其实这是一个很小的疏忽,解决也能解决,很简单,在求出流的长度后,再将指针恢复到流顶就可以了,有API提供。
改良后的countUpLength方法:
获取输入流的大小
1 /**
2 * 计算文件上传长度
3 * @param in
4 * @return
5 */
6 private static long countUpLength(ByteArrayInputStream in) throws IOException{
7 in.mark(0);
8 ByteArrayOutputStream baos = new ByteArrayOutputStream();
9 int b=0;
10 while( (b = in.read()) != -1){
11 baos.write(b);
12 }
13 upLength = baos.toByteArray().length;
14 in.reset();
15 return upLength;
16 }
首先用mark()方法,记录下你想恢复的位置,见代码7行。
求出长度后,通过reset()方法代码14行,会将指针恢复到你当时mark下的位置,也就是流顶!这样在下一次读取流的时候,就会从头读取了,不会丢下任何数据。你可以理解为,从哪里拿的就放到哪去,小时候妈妈经常这样教育我,这样在下一次用的时候就不会找不到了。
如何保存上传和下载的数据
上面已经解决了如何将下载数据和上传的数据获取到,下面就是如何保存辛辛苦苦得到的数据了,弄丢了就玩蛋去吧。
上面简单的介绍过insertOrUpdateTraffic方法,看一下这个方法:
1 /**
2 *
3 * @param i 类型,上传CurrentParameter.UPLENGTH或下载CurrentParameter.DOWNLENGTH
4 * @param byteLength 数据长度,单位byte
5 * @param date 系统当前时间‘yyyy-MM-dd’
6 */
7 public void insertOrUpdateTraffic(int type, long byteLength, String date) {
8 // TODO Auto-generated method stub
9 datahelper.insertOrUpdateTraffic(type, byteLength, date);
10 }
1 public void insertOrUpdateTraffic(int type, long byteLength, String date) {
2 // TODO Auto-generated method stub
3 SQLiteDatabase db = getWritableDatabase();
4 long upLength = byteLength;
5 long downLength = byteLength;
6 Cursor cursor = db.rawQuery("select upLength,downLength from sosgps_traffic_tb where createDate=?",
7 new String[]{date});
8 if(cursor.moveToFirst()){
9 upLength = cursor.getLong(0);
10 downLength = cursor.getLong(1);
11 if(type == CurrentParameter.UPLENGTH){
12 upLength = byteLength + upLength;
13 }else{
14 downLength = byteLength + downLength;
15 }
16
17 }
18 String sql = "REPLACE INTO sosgps_traffic_tb" +
19 "(createDate,upLength,downLength) values" + "(?,?,?)";
20 db.execSQL(sql, new Object[]{date,upLength,downLength});
21 }
第一个段代码是对第二段代码的封装,重点看第二段胆码。
首先,代码6行,通过日期做条件查询数据库表sosgps_traffic_tb,返回cursor对象,很简单的数据库操作,
代码8行,用cursor.moveToFirst,一来将cursor的角标移动到第一条数据上,二来判断cursor中是否有数据存在。为什么没有用到wile循环或for呢?因为我很确定通过日期查询sosgps_traffic_tb表(以下简称traffic表),cursor只会存在一条记录。因为我在存的时候相同日期的数据会被更新,一个日期只有对应的一条数据,因为是流量统计,最小统计周期为day,没必要操作一次网络就记录一条信息,这样数据库文件会越来越大,造成负担,我这样一年才365条数据。
代码9行,求出当期日期,数据库中所对应的上传数据的大小。
代码10行,求出当期日期,数据库中所对应的下载数据的大小。
代码11行,判断属于什么操作,是记录上传数据,还是下载数据,如果是上传,将数据库中的上传长度 + 当前方法传过来的上传长度 = 待记录上传长度,如果是下载,执行else后的代码。
代码18行,执行replace into 语句,以日期为唯一约束,只要当前插入日期数据和数据库中已有的日期相同,就不会创建新的条目,并将原有的数据替换成新的数据,反之创建新的条目,这是因为在创建traffic表的时候,为日期添加了唯一约束。
如何通过代码,为traffic表的日期添加唯一约束?
1 String sql4TrafficUnique = "CREATE UNIQUE INDEX [unique] ON [sosgps_traffic_tb] ([createDate])";
到此,数据保存完毕。
显示
关于显示这个话题,我想多说一些,关于软件开发,你想开发一个简单的可以,想开发一个大的也可以,而且大的无上限,复杂也会无上限的,就好比上面的3个关键数据,上传流量值、下载流量值、日期。就这么3个数据,你想玩成什么样?你可以展开你想象的翅膀玩出各种花样,按年统计,按月统计,按天统计,以年统计平均数,按月统计平均数,按星期统计平均数,求同比增长等等等,但真的有必要搞的这么“全面”吗?你只是一个统计流量的模块,搞的太 复杂了反而会降低软件的可用度,最坏的直接影响到用户的使用,还是那句话,仁者见仁,智者见智,不是绝对统一的硬性规定,这是鄙人的一些拙见,之所以引出这样的话题,是因为在开发的过程中和萌萌发生了一些分歧,他执意要显示按星期统计的信息,而我只统计了月和当天的数据,原因很简单,用户所关注的是当天这款软件使用了多少流量,和当月每天使用流量的情况,和当月一共使用了多少流量,为什么是月?不是星期和年?那是因为大多数人的流量套餐是按月来结算的,说到这里大家都应该明白了吧。
坚持就是胜利,哪怕那是条错误的路,也会走成胜利的路。
关于AChartEngine,没听说的百oogle去。
给大家个链接http://code.google.com/p/achartengine/
这个东西使用起来非常简单,登陆到上面的地址,可以下载插件,和演示Demo,通过查看API文档和Demo,就可以实现出自己的曲线图,关于如何使用,我就不说了,就是些API方法的调用,如果英文不好的话,可以直接copyDemo中的代码,将Demo工程导入到ecilpse中发布到真机或者虚拟机上,就可以看到各种图表的demo了,找到自己感兴趣的图表后,从Demo文件中直接copy到自己的工程里(别忘了为自己的工程引入jar包),提供数据,修改外观,就会得到自己想要的效果。下面就是我的流量统计曲线图截图,做的不太好,是因为美工生病了,NND。
声明:由于美工生病,UI进展缓慢,造成的“侵权”,在这里不任何责任!!!!