短信备份的原理,是用内容提供者读取短信,然后保存。
android源码中FramWork和Package是java的代码
短信存储在com.android.providers.telephoney的database中,里面有2个文件mmsms.db和一个telephoney.db中,前者是
mmsms.db表的内容如下:
type:1表示接受,2表示发生送
body表示内容
address:电话号码
date:时间
需要权限
<uses-permission android:name="android.permission.READ_SMS"/>
<uses-permission android:name="android.permission.WRITE_SMS" />
布局文件
activity_advancetools.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView
style="@style/TitleStyle"
android:text="高级工具"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/button"
android:text="电话归属地查询"
android:textColor="@color/black"
android:drawableLeft="@android:drawable/ic_menu_camera"
android:gravity="center_vertical"
android:textSize="20sp"
android:drawablePadding="5dp"
android:padding="10dp"
android:clickable="true"
android:onClick="numberAddressQuery"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/button"
android:text="短信备份"
android:textColor="@color/black"
android:drawableLeft="@android:drawable/ic_menu_camera"
android:gravity="center_vertical"
android:textSize="20sp"
android:drawablePadding="5dp"
android:padding="10dp"
android:clickable="true"
android:onClick="backUpsms"
/>
</LinearLayout>
Toast的封装UIUtils.java
package com.ldw.safe.utils;
import android.app.Activity;
import android.widget.Toast;
public class UIUtils {
public static void showToast(final Activity context,final String msg){
if("main".equals(Thread.currentThread().getName())){
Toast.makeText(context, msg, 1).show();
}else{
context.runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(context, msg, 1).show();
}
});
}
}
}
布局的逻辑文件AdvancedToolsActivity.java
package com.ldw.safe.Activity;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Looper;
import android.view.View;
import android.widget.Toast;
import com.ldw.safe.R;
import com.ldw.safe.utils.SmsUtils;
/*
* 高级工具,包含归属地和短信备份
*/
public class AdvancedToolsActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_advancedtools);
}
/*
* 归属地查询
*/
public void numberAddressQuery(View v){
startActivity(new Intent(this, AddressActivity.class));
}
/*
* 短信备份的功能
*/
public void backUpsms(View v){
new Thread(){
public void run() {
boolean result = SmsUtils.backUp(AdvancedToolsActivity.this);
if(result){
//子线程不能刷新UI,这种方法子线程弹出Toast
Looper.prepare();
Toast.makeText(AdvancedToolsActivity.this, "备份成功", Toast.LENGTH_SHORT).show();
Looper.loop();
}else{
Looper.prepare();
Toast.makeText(AdvancedToolsActivity.this, "备份失败", Toast.LENGTH_SHORT).show();
Looper.loop();
}
}
}.start();
}
}
生成短信备份的工具文件,获取到内容观察者,读取短信,然后以xml文件的形式输出
SmsUtils.java
package com.ldw.safe.utils;
import java.io.File;
import java.io.FileOutputStream;
import org.xmlpull.v1.XmlSerializer;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Environment;
import android.util.Xml;
/*
* 短信的工具类,用于短信备份
*/
public class SmsUtils {
public static boolean backUp(Context context){
/*
* 1.先判断是否有sd卡,
* 2.权限是---因此必须使用内容观察者读取
* 3.写短信
*/
//判断sd卡的状态
if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
//sd卡存在,获取到内容观察者
ContentResolver resolver = context.getContentResolver();
// 获取短信的路径
Uri uri = Uri.parse("content://sms/");
//通过内容观察着,读取短信内容,type:1接受短信,2发送短信
Cursor cursor = resolver.query(uri, new String[]{"address", "date", "type", "body"}, null, null, null);
//短信的数量
int count = cursor.getCount();
//写文件
try {
//创建保存的短信备份的文件的名字
File file = new File(Environment.getExternalStorageDirectory(), "smsbackup.xml");
FileOutputStream os = new FileOutputStream(file);
//xml序列化器,xml解析是pull解析
XmlSerializer serializer = Xml.newSerializer();
//短信序列化到sd卡,设置编码格式
serializer.setOutput(os, "utf-8");
// standalone表示当前的xml是否是独立文件 ture表示文件独立。yes
serializer.startDocument("utf-8", true);
// 设置开始的节点 第一个参数是命名空间。第二个参数是节点的名字
serializer.startTag(null, "smss");
//设置smss节点上面的属性值 第二个参数是名字。第三个参数是值
serializer.attribute(null, "size", String.valueOf(count));
//读取短信数据
while (cursor.moveToNext()) {
System.err.println("----------------------------");
System.out.println("address = " + cursor.getString(0));
System.out.println("date = " + cursor.getString(1));
System.out.println("type = " + cursor.getString(2));
System.out.println("body = " + cursor.getString(3));
serializer.startTag(null, "sms");
serializer.startTag(null, "address");
// 设置文本的内容
serializer.text(cursor.getString(0));
serializer.endTag(null, "address");
serializer.startTag(null, "date");
serializer.text(cursor.getString(1));
serializer.endTag(null, "date");
serializer.startTag(null, "type");
serializer.text(cursor.getString(2));
serializer.endTag(null, "type");
serializer.startTag(null, "body");
serializer.endTag(null, "body");
//读取短信的内容
/**
* 加密:第一个参数表示加密种子(密钥)
* 第二个参数表示加密的内容
*/
serializer.text(Crypto.encrypt("123", cursor.getString(3)));
serializer.endTag(null, "sms");
}
cursor.close();
serializer.endTag(null, "smss");
serializer.endDocument();
os.flush();
os.close();
return true;
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return false;
}
}
中间需要调用一个Crypto.java的文件(可以用来加密,github)
package com.ldw.safe.utils;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import android.util.Base64;
/**
*
* ━━━━━━神兽出没━━━━━━
* ┏┓ ┏┓
* ┏┛┻━━━┛┻┓
* ┃ ┃
* ┃ ━ ┃
* ┃ ┳┛ ┗┳ ┃
* ┃ ┃
* ┃ ┻ ┃
* ┃ ┃
* ┗━┓ ┏━┛Code is far away from bug with the animal protecting
* ┃ ┃ 神兽保佑,代码无bug
* ┃ ┃
* ┃ ┗━━━┓
* ┃ ┣┓
* ┃ ┏┛
* ┗┓┓┏━┳┓┏┛
* ┃┫┫ ┃┫┫
* ┗┻┛ ┗┻┛
*
* ━━━━━━感觉萌萌哒━━━━━━
*/
public class Crypto {
/**
* Encrypt plain string and encode to Base64
*
* @param seed
* @param plain
* @return
* @throws Exception
*/
public static String encrypt(String seed, String plain) throws Exception {
byte[] rawKey = getRawKey(seed.getBytes());
byte[] encrypted = encrypt(rawKey, plain.getBytes());
return Base64.encodeToString(encrypted, Base64.DEFAULT);
}
/**
* Decrypt Base64 encoded encrypted string
*
* @param seed
* @param encrypted
* @return
* @throws Exception
*/
public static String decrypt(String seed, String encrypted)
throws Exception {
byte[] rawKey = getRawKey(seed.getBytes());
byte[] enc = Base64.decode(encrypted.getBytes(), Base64.DEFAULT);
byte[] result = decrypt(rawKey, enc);
return new String(result);
}
private static byte[] getRawKey(byte[] seed) throws Exception {
KeyGenerator keygen = KeyGenerator.getInstance("AES");
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
random.setSeed(seed);
keygen.init(128, random); // 192 and 256 bits may not be available
SecretKey key = keygen.generateKey();
byte[] raw = key.getEncoded();
return raw;
}
private static byte[] encrypt(byte[] raw, byte[] plain) throws Exception {
SecretKeySpec keySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
byte[] encrypted = cipher.doFinal(plain);
return encrypted;
}
private static byte[] decrypt(byte[] raw, byte[] encrypted)
throws Exception {
SecretKeySpec keySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, keySpec);
byte[] decrypted = cipher.doFinal(encrypted);
return decrypted;
}
}