Android通话记录备份实现代码
程序员文章站
2023-12-10 22:37:22
(一) 前言 android默认提供了联系人备份到sd卡的功能(代码在com.android.vcard包里面),我们可以把联系人导出成.vcf文件存在sd卡中;如果换手机...
(一) 前言
android默认提供了联系人备份到sd卡的功能(代码在com.android.vcard包里面),我们可以把联系人导出成.vcf文件存在sd卡中;如果换手机了,我们又可以把联系人从sd卡文件中导入进来。那么,通话记录我们也能不能做出类似的功能呢?答案是肯定的!
(二) 导出通话记录
既然是备份通话记录,那就肯定包括导出和导入的功能,这里我们先讲导出通话记录。
1. 根据通话记录导出的规范,导出的文件一般以.vcl后缀结尾,中间的内容是
begin:vcall
slot:0 //卡槽号 0:单卡手机 1: 双卡手机卡槽1 2: 双卡手机卡槽2
type:1 //电话类型 1:接入电话,2: 呼出电话 3: 未接电话
date: 2013/02/12 14:11:12 gmt //来电或者去点的时间 备份时以gmt时间记录,恢复时显示手机时区对应时间
number:+86134xxxxx //对方号码
duration:5 //持续时间,秒数
end:vcall
那么这里就是一条通话记录的存储格式了,以begin:vcall 开始 end:vcall结束。 //表示的是该字段的含义,只是为了让大家理解,不会导入到实际的文件中去。那么我们来看实际怎么导出的。
2. 查询通话记录列表
ok.. 既然是保存通话记录,那么首先要查询通话记录
android里面提供了一个calllogprovider来满足大家的这个需求,它在系统中配置的名字是“call_log”, 所以大家只要提供一个这样的uri就可以查询了,比如:
uri uri = uri.parse("context://call_log/calls");
cursor c = mcontext.getcontentresolver().query(uri, xxx, xxx );
这样就可以查询出所有的通话记录,得到游标。。
3. 从游标中剥离出想要保存的字段和数据,写入文件
既然找到了游标,那么接下来就是从游标中找到我们想要写入文件的字段数据,比如,基本如下:
protected object doinbackground(object... params) { //后台异步task,后台查询数据和写入文件,每导出一条记录,更新一次进度条
super.doinbackground(params);
string path = (string)params[0];
uri queryuri = uri.parse("content://call_log/calls");
cursor queryedcursor = mcontext.getcontentresolver().query(queryuri,
null,
null,
null,
null);
if (queryedcursor == null || queryedcursor.getcount() == 0){
return -1;
}
object[] message = new object[1];
message[0] = queryedcursor.getcount();
publishprogress(message);
stringbuilder sb = new stringbuilder();
outputstream outputstream = null;
writer writer = null;
try {
outputstream = new fileoutputstream(path);
writer = new bufferedwriter(new outputstreamwriter(outputstream));
for (queryedcursor.movetofirst(); !queryedcursor.isafterlast();
queryedcursor.movetonext()) {
if (mcancel){
break;
}
sb.setlength(0);
sb.append("begin:vcall").append("\n");
int subid = queryedcursor.getint(queryedcursor.getcolumnindex("sub_id"));
int calltype = queryedcursor.getint(
queryedcursor.getcolumnindex("type")); //incall/outcall/missed call
long date = queryedcursor.getlong(queryedcursor.getcolumnindex("date"));
string gmtdata = getgtmdatetimestring(date);
string number = queryedcursor.getstring(queryedcursor.getcolumnindex("formatted_number"));
string duration = queryedcursor.getstring(queryedcursor.getcolumnindex("duration"));
sb.append("slot:").append(subid).append("\n");
sb.append("type:").append(calltype).append("\n");
sb.append("date:").append(gmtdata).append("\n");
sb.append("number:").append(number).append("\n");
sb.append("duration:").append(duration).append("\n");
sb.append("end:vcall").append("\n");
writer.write(sb.tostring()); //写入一条记录到文件中
message[0] = -1;
publishprogress(message); //发布消息,让主线程更新进度条
}
} catch (exception e) {
log.d(tag, "", e);
return 0;
}finally{
try {
if (writer != null){
writer.close();
}
if (outputstream != null){
outputstream.close();
}
} catch (exception e2) {
log.d(tag, "", e2);
return 0;
}
}
return 1;
}
这个只是大体代码,大家如果以后有需求,可以在上面任意修改而无需知会作者。。无需版权的哈~~~
(三) 导入通话记录到数据库
1. 嗯,导入的话,首先得搜索sd卡里面以.vcl后缀结尾的文件,嗯!起个线程吧,迭代搜索。如下:
private class vclscanthread extends thread implements oncancellistener, onclicklistener { //启动线程进行搜索,同时弹出进度条给用户
private boolean mcanceled; //变量标志用户是否已经cancel这个搜索过程
private boolean mgotioexception;
private file mrootdirectory;
private static final string log_tag = "vclscanthread";
// to avoid recursive link.
private set<string> mcheckedpaths;
private class canceledexception extends exception {
}
public vclscanthread(file sdcarddirectory, string scantype) {
mcanceled = false;
mgotioexception = false;
mrootdirectory = sdcarddirectory;
mcheckedpaths = new hashset<string>();
mprogressdialogforscanvcard = new progressdialog(main.this);
mprogressdialogforscanvcard.settitle(r.string.dialog_scan_calllist_progress_title);
mprogressdialogforscanvcard.show(); //弹出搜索进度条
}
@override
public void run() {
if (mallvclfilelist == null){
mallvclfilelist = new vector<vclfile>(); //开始搜索,首先清空list,这个list用来保存找到的.vcl文件(包括文件名,文件路径,等等)
}else{
mallvclfilelist.clear();
}
try {
getvcardfilerecursively(mrootdirectory); //迭代搜索sd卡中所有的.vcl文件
} catch (canceledexception e) {
mcanceled = true;
} catch (ioexception e) {
mgotioexception = true;
}
if (mcanceled) {
mallvclfilelist = null;
}
mprogressdialogforscanvcard.dismiss();
mprogressdialogforscanvcard = null;
if (mgotioexception) {
// runonuithread(new dialogdisplayer(r.id.dialog_io_exception));
} else if (mcanceled) {
// finish();
} else {
int size = mallvclfilelist.size();
if (size == 0) {
toast.maketext(main.this, r.string.error_scan_vcl_not_found,
toast.length_short).show();
} else {
runonuithread(new runnable() {
@override
public void run() {
startvcardselectandimport(); //搜索完毕,弹出对话框让用户选择导入那些文件
}
});
}
}
}
private void getvcardfilerecursively(file directory)
throws canceledexception, ioexception {
if (mcanceled) {
throw new canceledexception();
}
// e.g. secured directory may return null toward listfiles().
final file[] files = directory.listfiles();
if (files == null) {
final string currentdirectorypath = directory.getcanonicalpath();
final string securedirectorypath =
mrootdirectory.getcanonicalpath().concat(secure_directory_name);
if (!textutils.equals(currentdirectorypath, securedirectorypath)) {
log.w(log_tag, "listfiles() returned null (directory: " + directory + ")");
}
return;
}
for (file file : directory.listfiles()) {
if (mcanceled) {
throw new canceledexception();
}
string canonicalpath = file.getcanonicalpath();
if (mcheckedpaths.contains(canonicalpath)) {
continue;
}
mcheckedpaths.add(canonicalpath);
string endfix = ".vcl";
if (file.isdirectory()) {
getvcardfilerecursively(file); //如果是目录,就继续迭代搜索
} else if (canonicalpath.tolowercase().endswith(endfix) && //如果是文件,就判断文件名是否以.vcl结尾,如果是,而且可读,则放入搜索的list里面。
file.canread()){
string filename = file.getname();
vclfile vclfile = new vclfile(
filename, canonicalpath, file.lastmodified());
mallvclfilelist.add(vclfile);
}
}
}
public void oncancel(dialoginterface dialog) {
mcanceled = true;
}
public void onclick(dialoginterface dialog, int which) {
if (which == dialoginterface.button_negative) {
mcanceled = true;
}
}
}
2. 选择好要导入的文件之后,就是解析该文件,解析完一个begin:vcall和end:vcall之后,就存入数据库(你也可以解析多条之后一次性存入数据库)
private void parseiteminter(string name, string value) throws exception{
if ("slot".equalsignorecase(name)){
mvalues.put("sub_id", value);
}else if ("type".equalsignorecase(name)){
mvalues.put("type", value);
}else if ("date".equalsignorecase(name)){
mvalues.put("date", getgtmdatetime(value));
}else if ("number".equalsignorecase(name)){
mvalues.put("formatted_number", value);
mvalues.put("number", value);
}else if ("duration".equalsignorecase(name)){
mvalues.put("duration", value);
}else{
throw new exception("unknown type, name: " + name + " value: " + value);
}
}
//提交一次通话记录信息到数据库
uri uri = uri.parse("content://call_log");
mcontext.getcontentresolver().insert(uri, mvalues);
大体就是这个意思了,只是具体细节,还要控制。比如文件非法啦,不是以begin:vcall开头啦,之类的。还需要大家控制。
大体就这么多了,希望能对大家以后做这块的时候稍微有所参考。。。
android默认提供了联系人备份到sd卡的功能(代码在com.android.vcard包里面),我们可以把联系人导出成.vcf文件存在sd卡中;如果换手机了,我们又可以把联系人从sd卡文件中导入进来。那么,通话记录我们也能不能做出类似的功能呢?答案是肯定的!
(二) 导出通话记录
既然是备份通话记录,那就肯定包括导出和导入的功能,这里我们先讲导出通话记录。
1. 根据通话记录导出的规范,导出的文件一般以.vcl后缀结尾,中间的内容是
复制代码 代码如下:
begin:vcall
slot:0 //卡槽号 0:单卡手机 1: 双卡手机卡槽1 2: 双卡手机卡槽2
type:1 //电话类型 1:接入电话,2: 呼出电话 3: 未接电话
date: 2013/02/12 14:11:12 gmt //来电或者去点的时间 备份时以gmt时间记录,恢复时显示手机时区对应时间
number:+86134xxxxx //对方号码
duration:5 //持续时间,秒数
end:vcall
那么这里就是一条通话记录的存储格式了,以begin:vcall 开始 end:vcall结束。 //表示的是该字段的含义,只是为了让大家理解,不会导入到实际的文件中去。那么我们来看实际怎么导出的。
2. 查询通话记录列表
ok.. 既然是保存通话记录,那么首先要查询通话记录
android里面提供了一个calllogprovider来满足大家的这个需求,它在系统中配置的名字是“call_log”, 所以大家只要提供一个这样的uri就可以查询了,比如:
复制代码 代码如下:
uri uri = uri.parse("context://call_log/calls");
cursor c = mcontext.getcontentresolver().query(uri, xxx, xxx );
这样就可以查询出所有的通话记录,得到游标。。
3. 从游标中剥离出想要保存的字段和数据,写入文件
既然找到了游标,那么接下来就是从游标中找到我们想要写入文件的字段数据,比如,基本如下:
复制代码 代码如下:
protected object doinbackground(object... params) { //后台异步task,后台查询数据和写入文件,每导出一条记录,更新一次进度条
super.doinbackground(params);
string path = (string)params[0];
uri queryuri = uri.parse("content://call_log/calls");
cursor queryedcursor = mcontext.getcontentresolver().query(queryuri,
null,
null,
null,
null);
if (queryedcursor == null || queryedcursor.getcount() == 0){
return -1;
}
object[] message = new object[1];
message[0] = queryedcursor.getcount();
publishprogress(message);
stringbuilder sb = new stringbuilder();
outputstream outputstream = null;
writer writer = null;
try {
outputstream = new fileoutputstream(path);
writer = new bufferedwriter(new outputstreamwriter(outputstream));
for (queryedcursor.movetofirst(); !queryedcursor.isafterlast();
queryedcursor.movetonext()) {
if (mcancel){
break;
}
sb.setlength(0);
sb.append("begin:vcall").append("\n");
int subid = queryedcursor.getint(queryedcursor.getcolumnindex("sub_id"));
int calltype = queryedcursor.getint(
queryedcursor.getcolumnindex("type")); //incall/outcall/missed call
long date = queryedcursor.getlong(queryedcursor.getcolumnindex("date"));
string gmtdata = getgtmdatetimestring(date);
string number = queryedcursor.getstring(queryedcursor.getcolumnindex("formatted_number"));
string duration = queryedcursor.getstring(queryedcursor.getcolumnindex("duration"));
sb.append("slot:").append(subid).append("\n");
sb.append("type:").append(calltype).append("\n");
sb.append("date:").append(gmtdata).append("\n");
sb.append("number:").append(number).append("\n");
sb.append("duration:").append(duration).append("\n");
sb.append("end:vcall").append("\n");
writer.write(sb.tostring()); //写入一条记录到文件中
message[0] = -1;
publishprogress(message); //发布消息,让主线程更新进度条
}
} catch (exception e) {
log.d(tag, "", e);
return 0;
}finally{
try {
if (writer != null){
writer.close();
}
if (outputstream != null){
outputstream.close();
}
} catch (exception e2) {
log.d(tag, "", e2);
return 0;
}
}
return 1;
}
这个只是大体代码,大家如果以后有需求,可以在上面任意修改而无需知会作者。。无需版权的哈~~~
(三) 导入通话记录到数据库
1. 嗯,导入的话,首先得搜索sd卡里面以.vcl后缀结尾的文件,嗯!起个线程吧,迭代搜索。如下:
复制代码 代码如下:
private class vclscanthread extends thread implements oncancellistener, onclicklistener { //启动线程进行搜索,同时弹出进度条给用户
private boolean mcanceled; //变量标志用户是否已经cancel这个搜索过程
private boolean mgotioexception;
private file mrootdirectory;
private static final string log_tag = "vclscanthread";
// to avoid recursive link.
private set<string> mcheckedpaths;
private class canceledexception extends exception {
}
public vclscanthread(file sdcarddirectory, string scantype) {
mcanceled = false;
mgotioexception = false;
mrootdirectory = sdcarddirectory;
mcheckedpaths = new hashset<string>();
mprogressdialogforscanvcard = new progressdialog(main.this);
mprogressdialogforscanvcard.settitle(r.string.dialog_scan_calllist_progress_title);
mprogressdialogforscanvcard.show(); //弹出搜索进度条
}
@override
public void run() {
if (mallvclfilelist == null){
mallvclfilelist = new vector<vclfile>(); //开始搜索,首先清空list,这个list用来保存找到的.vcl文件(包括文件名,文件路径,等等)
}else{
mallvclfilelist.clear();
}
try {
getvcardfilerecursively(mrootdirectory); //迭代搜索sd卡中所有的.vcl文件
} catch (canceledexception e) {
mcanceled = true;
} catch (ioexception e) {
mgotioexception = true;
}
if (mcanceled) {
mallvclfilelist = null;
}
mprogressdialogforscanvcard.dismiss();
mprogressdialogforscanvcard = null;
if (mgotioexception) {
// runonuithread(new dialogdisplayer(r.id.dialog_io_exception));
} else if (mcanceled) {
// finish();
} else {
int size = mallvclfilelist.size();
if (size == 0) {
toast.maketext(main.this, r.string.error_scan_vcl_not_found,
toast.length_short).show();
} else {
runonuithread(new runnable() {
@override
public void run() {
startvcardselectandimport(); //搜索完毕,弹出对话框让用户选择导入那些文件
}
});
}
}
}
private void getvcardfilerecursively(file directory)
throws canceledexception, ioexception {
if (mcanceled) {
throw new canceledexception();
}
// e.g. secured directory may return null toward listfiles().
final file[] files = directory.listfiles();
if (files == null) {
final string currentdirectorypath = directory.getcanonicalpath();
final string securedirectorypath =
mrootdirectory.getcanonicalpath().concat(secure_directory_name);
if (!textutils.equals(currentdirectorypath, securedirectorypath)) {
log.w(log_tag, "listfiles() returned null (directory: " + directory + ")");
}
return;
}
for (file file : directory.listfiles()) {
if (mcanceled) {
throw new canceledexception();
}
string canonicalpath = file.getcanonicalpath();
if (mcheckedpaths.contains(canonicalpath)) {
continue;
}
mcheckedpaths.add(canonicalpath);
string endfix = ".vcl";
if (file.isdirectory()) {
getvcardfilerecursively(file); //如果是目录,就继续迭代搜索
} else if (canonicalpath.tolowercase().endswith(endfix) && //如果是文件,就判断文件名是否以.vcl结尾,如果是,而且可读,则放入搜索的list里面。
file.canread()){
string filename = file.getname();
vclfile vclfile = new vclfile(
filename, canonicalpath, file.lastmodified());
mallvclfilelist.add(vclfile);
}
}
}
public void oncancel(dialoginterface dialog) {
mcanceled = true;
}
public void onclick(dialoginterface dialog, int which) {
if (which == dialoginterface.button_negative) {
mcanceled = true;
}
}
}
2. 选择好要导入的文件之后,就是解析该文件,解析完一个begin:vcall和end:vcall之后,就存入数据库(你也可以解析多条之后一次性存入数据库)
复制代码 代码如下:
private void parseiteminter(string name, string value) throws exception{
if ("slot".equalsignorecase(name)){
mvalues.put("sub_id", value);
}else if ("type".equalsignorecase(name)){
mvalues.put("type", value);
}else if ("date".equalsignorecase(name)){
mvalues.put("date", getgtmdatetime(value));
}else if ("number".equalsignorecase(name)){
mvalues.put("formatted_number", value);
mvalues.put("number", value);
}else if ("duration".equalsignorecase(name)){
mvalues.put("duration", value);
}else{
throw new exception("unknown type, name: " + name + " value: " + value);
}
}
//提交一次通话记录信息到数据库
uri uri = uri.parse("content://call_log");
mcontext.getcontentresolver().insert(uri, mvalues);
大体就是这个意思了,只是具体细节,还要控制。比如文件非法啦,不是以begin:vcall开头啦,之类的。还需要大家控制。
大体就这么多了,希望能对大家以后做这块的时候稍微有所参考。。。