首页 > 系统 > Android > 正文

Android通话记录备份实现代码

2019-10-24 21:02:59
字体:
来源:转载
供稿:网友
其实就是解析文件,存入数据库;或者查询数据库,存入文件而已,特分享下,方便需要的朋友
 
(一) 前言 

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开头啦,之类的。还需要大家控制。 

大体就这么多了,希望能对大家以后做这块的时候稍微有所参考。。。

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表