Android 微信聊天记录、联系人备份并导出为表格

(github代码会及时更新,更完整的代码请参考末文的 github 链接)

最近公司要求做一个项目,实现备份和导出虚拟代表和医生的微信聊天记录的功能,于是想了一下可从以下两个方面入手,并分析了一下他们的优劣势

  • 解密微信数据库,直接用 Sql 语句查询导表上传

    • 直接操作数据库,联系人和聊天记录完整,不会有遗漏
    • 相比自动化更加省时不止一点点...10秒钟与十分钟的差别
    • 失败率很低.并能控制只上传某个时间段的聊天记录,直接定位某句话的时间
    • 但是手机需要 root
  • 通过AccessibilityService在微信界面自动化操作实现获取联系人和聊天记录
    • 不需要 root ,仅需要在设置里打开辅助功能就能实现自动化操作
    • AccessibilityService会自动滚动聊天列表和聊天详情捕捉元素获取联系人和聊天记录,所以缺点也很多:
      • 耗时较长,聊天对象和聊天记录越多,需要滚动的次数越多,越耗时
      • 受外界影响很大,来电话可能就直接失败了.....
      • 具体聊天记录的时间不好捕捉
      • 可能会有重复的联系人和聊天对话(当前屏幕只显示一半时,滚动到下一屏时仍会捕捉显示,当然这个是可以通过程序优化的)
    • 受微信版本的影响很大,可能更新微信版本整个程序得重新适配或者没法用了

利用AccessibilityService虽然业务上实现起来不太靠谱,但是可玩性还是很高的,自动化的操作看起来很牛逼很高大上,有灵感的同学可以自己再写一些功能,比如自动回复,自动拉人,自动抢红包等....,下次更博贴AccessibilityService的实现源码

https://blog.csdn.net/zk94_Android/article/details/84652992


所以在业务上还是选择直接操作数据库的方法更加靠谱,下面开始实操

* 微信数据库的加密方式:1.获取手机的 IME 码2.获取当前登录微信账号的uin,位置在/data/data/com.tencent.mm/shared_prefs/auth_info_key_prefs.xml 3.拼接IMEI和uin,并进行 MD5 加密4.取 MD5 加密的前七位(全小写)就是数据库的打开密码

贴代码:

本章代码已经是很老的一个版本的,需要最新源码的可以移步最底部的 github 获取

添加依赖:

    implementation 'dom4j:dom4j:1.6.1'implementation 'net.zetetic:android-database-sqlcipher:3.5.4@aar'implementation 'com.github.threekilogram:ObjectBus:2.1.3'implementation 'com.wang.avi:library:1.0.0'implementation 'com.nineoldandroids:library:2.4.0'

csv 的依赖需要去官网下载 jar 包再 build path,下载地址:

http://commons.apache.org/proper/commons-csv/download_csv.cgi

需要用到的一些成员变量

 String WXPackageName = "com.tencent.mm";private static final ObjectBus task = com.threekilogram.objectbus.bus.ObjectBus.newList();//微信数据库路径public final String WX_ROOT_PATH = "/data/data/com.tencent.mm/";private final String WX_DB_DIR_PATH = WX_ROOT_PATH + "MicroMsg";private final String WX_DB_FILE_NAME = "EnMicroMsg.db";//拷贝到sd 卡的路径private String mCcopyPath = Environment.getExternalStorageDirectory().getPath() + "/";private final String COPY_WX_DATA_DB = "wx_data.db";String copyFilePath = mCcopyPath + COPY_WX_DATA_DB;private SharedPreferences preferences;private static CSVPrinter contactCsvPrinter;private static CSVPrinter messageCsvPrinter;

1.获取 root 权限并拷贝,打开数据库

            //获取root权限passwordUtiles.execRootCmd("chmod 777 -R " + WX_ROOT_PATH);//获取root权限passwordUtiles.execRootCmd("chmod 777 -R " + copyFilePath);String password = passwordUtiles.initDbPassword(mActivity);String uid = passwordUtiles.initCurrWxUin();try {String path = WX_DB_DIR_PATH + "/" + Md5Utils.md5Encode("mm" + uid) + "/" + WX_DB_FILE_NAME;Log.e("path", copyFilePath);Log.e("path", path);Log.e("path", password);//微信原始数据库的地址File wxDataDir = new File(path);//将微信数据库拷贝出来,因为直接连接微信的db,会导致微信崩溃copyFile(wxDataDir.getAbsolutePath(), copyFilePath);//将微信数据库导出到sd卡操作sd卡上数据库openWxDb(new File(copyFilePath), mActivity, password);} catch (Exception e) {Log.e("path", e.getMessage());e.printStackTrace();}

2.passwordUtiles

package com.naxions.www.wechathelper;import android.content.Context;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.OutputStream;
import java.util.List;public class passwordUtiles {public static final String WX_ROOT_PATH = "/data/data/com.tencent.mm/";private static final String WX_SP_UIN_PATH = WX_ROOT_PATH + "shared_prefs/auth_info_key_prefs.xml";/*** 根据imei和uin生成的md5码获取数据库的密码** @return*/public static String initDbPassword(Context mContext) {String imei = initPhoneIMEI(mContext);String uin = initCurrWxUin();Log.e("initDbPassword","imei==="+imei);Log.e("initDbPassword","uin==="+uin);try {if (TextUtils.isEmpty(imei) || TextUtils.isEmpty(uin)) {Log.e("initDbPassword","初始化数据库密码失败:imei或uid为空");return "";}String md5 = Md5Utils.md5Encode(imei + uin);String password = md5.substring(0, 7).toLowerCase();Log.e("initDbPassword",password);return password;}catch (Exception e){Log.e("initDbPassword",e.getMessage());}return "";}/***  execRootCmd("chmod 777 -R " + WX_ROOT_PATH);** 执行linux指令*/public static   void execRootCmd(String paramString) {try {Process localProcess = Runtime.getRuntime().exec("su");Object localObject = localProcess.getOutputStream();DataOutputStream localDataOutputStream = new DataOutputStream((OutputStream) localObject);String str = String.valueOf(paramString);localObject = str + "\n";localDataOutputStream.writeBytes((String) localObject);localDataOutputStream.flush();localDataOutputStream.writeBytes("exit\n");localDataOutputStream.flush();localProcess.waitFor();localObject = localProcess.exitValue();} catch (Exception localException) {localException.printStackTrace();}}/*** 获取手机的imei码** @return*/private static String initPhoneIMEI(Context mContext) {TelephonyManager telephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);return telephonyManager.getDeviceId();}/*** 获取微信的uid* 微信的uid存储在SharedPreferences里面*/public static String initCurrWxUin() {String   mCurrWxUin = null;//存储位置为\data\data\com.tencent.mm\shared_prefs\auth_info_key_prefs.xmlFile file = new File(WX_SP_UIN_PATH);try {FileInputStream in = new FileInputStream(file);SAXReader saxReader = new SAXReader();Document document = saxReader.read(in);Element root = document.getRootElement();List<Element> elements = root.elements();for (Element element : elements) {if ("_auth_uin".equals(element.attributeValue("name"))) {mCurrWxUin = element.attributeValue("value");}}return mCurrWxUin;} catch (Exception e) {e.printStackTrace();Log.e("initCurrWxUin","获取微信uid失败,请检查auth_info_key_prefs文件权限");}return "";}}

3.Md5Utils

package com.naxions.www.wechathelper;import android.text.TextUtils;import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;public class Md5Utils {/**** MD5加密 生成32位md5码* @param* @return 返回32位md5码*/public static String md5Encode(String inStr) throws Exception {MessageDigest md5 = null;try {md5 = MessageDigest.getInstance("MD5");} catch (Exception e) {System.out.println(e.toString());e.printStackTrace();return "";}byte[] byteArray = inStr.getBytes("UTF-8");byte[] md5Bytes = md5.digest(byteArray);StringBuffer hexValue = new StringBuffer();for (int i = 0; i < md5Bytes.length; i++) {int val = ((int) md5Bytes[i]) & 0xff;if (val < 16) {hexValue.append("0");}hexValue.append(Integer.toHexString(val));}return hexValue.toString();}public static String md5(String string) {if (TextUtils.isEmpty(string)) {return "";}MessageDigest md5 = null;try {md5 = MessageDigest.getInstance("MD5");byte[] bytes = md5.digest(string.getBytes());String result = "";for (byte b : bytes) {String temp = Integer.toHexString(b & 0xff);if (temp.length() == 1) {temp = "0" + temp;}result += temp;}return result;} catch (NoSuchAlgorithmException e) {e.printStackTrace();}return "";}
}

一些具体方法

 /*** 复制单个文件** @param oldPath String 原文件路径 如:c:/fqf.txt* @param newPath String 复制后路径 如:f:/fqf.txt* @return boolean*/public  void copyFile(String oldPath, String newPath) {try {int byteRead = 0;File oldFile = new File(oldPath);if (oldFile.exists()) { //文件存在时InputStream inStream = new FileInputStream(oldPath); //读入原文件FileOutputStream fs = new FileOutputStream(newPath);byte[] buffer = new byte[1444];while ((byteRead = inStream.read(buffer)) != -1) {fs.write(buffer, 0, byteRead);}inStream.close();}} catch (Exception e) {Log.e("copyFile", "复制单个文件操作出错");e.printStackTrace();}}/*** 连接数据库*/public  void openWxDb(File dbFile, final Context mContext, String mDbPassword) {SQLiteDatabase.loadLibs(mContext);SQLiteDatabaseHook hook = new SQLiteDatabaseHook() {@Overridepublic void preKey(SQLiteDatabase database) {}@Overridepublic void postKey(SQLiteDatabase database) {database.rawExecSQL("PRAGMA cipher_migrate;");}};//打开数据库连接final SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(dbFile, mDbPassword, null, hook);runRecontact(mContext, db);}/*** 微信好友信息** @param mContext* @param db*/private  void runRecontact(final Context mContext, final SQLiteDatabase db) {task.toPool(new Runnable() {@Overridepublic void run() {getRecontactDate(db,mContext);}}).toMain(new Runnable() {@Overridepublic void run() {Toast.makeText(mContext, "文件导出完毕完毕", Toast.LENGTH_LONG).show();}}).run();}/*** 获取当前用户的微信所有联系人*/private  void getRecontactDate(SQLiteDatabase db, Context mContext) {Cursor c1 = null;Cursor c2 = null;try {//新建文件保存联系人信息File file1 = new File(Environment.getExternalStorageDirectory().getPath() + "/"+userName+"contact_file" + ".csv");BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file1), "UTF-8"));contactCsvPrinter = new CSVPrinter(writer, CSVFormat.DEFAULT.withHeader("userName", "nickName", "alias", "conRemark","type"));//新建文件保存聊天记录File file2= new File(Environment.getExternalStorageDirectory().getPath() + "/"+userName+"message_file" + ".csv");BufferedWriter writer2 = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file2), "UTF-8"));  // 防止出现乱码messageCsvPrinter = new CSVPrinter(writer2, CSVFormat.DEFAULT.withHeader("talker", "content", "createTime", "isSend"));} catch (IOException e) {e.printStackTrace();}try {// 查询所有联系人verifyFlag!=0:公众号等类型,群里面非好友的类型为4,未知类型2)c1 = db.rawQuery("select * from rcontact where verifyFlag = 0 and type != 4 and type != 2 and type != 0 and type != 33 and nickname != ''",null);while (c1.moveToNext()) {String userName = c1.getString(c1.getColumnIndex("username"));String nickName = c1.getString(c1.getColumnIndex("nickname"));String alias = c1.getString(c1.getColumnIndex("alias"));String conRemark = c1.getString(c1.getColumnIndex("conRemark"));String type = c1.getString(c1.getColumnIndex("type"));Log.e("contact", "userName=" + userName + "nickName=" + nickName + "alias=" + alias + "conRemark=" + conRemark + "type=" + type);//将联系人信息写入 csv 文件contactCsvPrinter.printRecord(userName,nickName,alias,conRemark,type);}contactCsvPrinter.printRecord();contactCsvPrinter.flush();//查询聊天记录c2 = db.rawQuery("select * from message where type = 1 and createTime > 1543207160000 ",null);while (c2.moveToNext()) {String content = c2.getString(c2.getColumnIndex("content"));String talker = c2.getString(c2.getColumnIndex("talker"));String createTime = c2.getString(c2.getColumnIndex("createTime"));int isSend = c2.getInt(c2.getColumnIndex("isSend"));Log.e("chatInfo", "talker=" + talker + "content=" + content+ "isSend=" + isSend);//将聊天记录写入 csv 文件messageCsvPrinter.printRecord(talker,content,createTime,isSend);}messageCsvPrinter.printRecord();messageCsvPrinter.flush();c1.close();c2.close();db.close();} catch (Exception e) {c1.close();c2.close();db.close();Log.e("openWxDb", "读取数据库信息失败" + e.toString());}}

到此就完成了....,导出的效果图:

奉上数据库主要表的具体字段说明:具体需要啥根据说明修改 sql 语句就好

message表

  • msgid: 自增的,每段聊天记录的唯一标识
  • type: 消息类型

    • 47 表情消息
    • 43 视频消息
    • 49 分享的网页消息
    • 50 语音视频通话
    • 1 文字消息
    • 3 图片消息
    • 34 语音消息
    • 1000 撤回消息的通知
  • status: 消息阅读状态

    • 2 对方已阅读
    • 3 自己通过pc 端阅读该条消息
    • 4 自己在手机端阅读该条消息
  • isSend:

    • 1 自己发送
    • 0 对方发送
  • createtime: 本条消息的时间戳

  • talker: 消息发送人

  • content: 消息具体内容

  • imgPath: 图片 语音 视频消息的路径

rcontact表

  • username: 用户标识,有两种类型

    • 微信号
    • 微信定义的唯一标识 gh_385a194e4ef1, wxid_f4eiifed3fjx21
  • alias: 微信号 没有设置微信号的用户为空

  • nikname: 联系人昵称

  • conRemark: 联系人备注

  • quanPin: 昵称全拼

还有一些需要注意的事情 :

1.运行软件提示".....not a dataBase" 可以尝试用备份好聊天记录之后卸载安装微信重试

2.重装无效再考虑将数据库拷贝到电脑用sqlcipher.exe(网上很多下载地址)查看是否能打开

3.手机拷贝数据库到电脑的路径:

/data/data/com.tencent.mm/MicroMsg/5d2d988ba1131f31a6c2481156b96331/EnMicroMsg.db

其中的5d2d988ba1131f31a6c2481156b96331文件夹的生成规则是:

字符串"mm"+用户的uin 再 MD5,参考代码:

String path = WX_DB_DIR_PATH + "/" + Md5Utils.md5Encode("mm" + uid) + "/" + WX_DB_FILE_NAME;

到此就结束啦....有时间在上传源码,,,

源码地址:https://github.com/KeZengOo/-wechatHelper

如果对你有帮助...就赞我一下吧

Android 微信聊天记录、联系人备份并导出为表格相关推荐

  1. android微信聊天记录导出到电脑【微信安卓版技巧】

    android微信聊天记录导出到电脑[微信安卓版技巧] 微信,对它又爱又恨!爱的是微信能替代很多手机通话短信,恨的是有些较早前的手机不能友好支持,比如ytkah之前用的i8000,挺上手的,就是没办法 ...

  2. 企业微信聊天记录如何备份?企业微信如何导出聊天记录?

    现在有很多的企业都是使用企业微信跟客户沟通,聊天中产生的聊天记录具有重要价值,因为包含了很多重要信息.如果这时候某位员工的电脑或者手机出现了问题,需要更换,那么这些聊天记录会不会也丢失了呢?如果还在, ...

  3. 微信聊天记录怎么备份到电脑(已解决)

    我们在办公的时候经常需要把微信登录在电脑上,有时候下班了,微信就断开连接了,万一这个时候有重要文件或者重要聊天记录,第二天回到公司还要重新上传,这不就很麻烦?如果能够把聊天记录一键备份,那就可以省掉很 ...

  4. 微信聊天记录怎么备份?

    微信的出现更是为社交带来巨大的方便,现实中说不出的话都能够在微信里面畅谈,工作也能在微信里面交接,活动也能在微信里组织策划等.微信聊天记录怎么备份?聊天记录是非常重要的东西,我们不想丢失的话就应该把它 ...

  5. 微信聊天记录没有备份可以恢复吗?吐血整理分享

    "你平时手机上的东西会备份吗?"问一百个人估计有九十九个人都会说"不会",但是如果平时有做备份的习惯的话,面对微信聊天记录删除了这种情况的话,就可以轻松将删除的 ...

  6. 技能Get·PC及手机微信聊天记录的备份与还原

    阅文时长 | 0.3分钟 字数统计 | 481.6字符 主要内容 | 1.引言&背景 2.备份还原过程 3.网络不一致问题 4.声明与参考资料 『技能Get·PC及手机微信聊天记录的备份与还原 ...

  7. 手机小技巧:安卓微信聊天记录怎么备份?

    安卓微信聊天记录怎么备份?现在的社会,上至老人家,下至小学生都在使用微信进行社交,不但可以打字,而且还有语音交流.语音通话.视频通话等等,甚至表情包的加入让整个聊天过程更加有活跃的氛围.聊天记录是与他 ...

  8. 用Android微信聊天记录 制作一个词云(python)

    在看完了如何做词云的教程,试用微信聊天记录做一个demo练手.关于词云,网上有大量的教程,可随意参考,注意python的版本问题即可. 微信聊天记录导出 用到的工具是一款微信聊天记录查看软件,分享如下 ...

  9. Android 通过AccessibilityService实现微信聊天记录导出

    接上Android 微信聊天记录.联系人备份并导出为表格继续讲 不太了解AccessibilityService可以看看这篇文章 基本原理: 首先打开 DDMS 捕捉界面元素 拿到resourceid ...

  10. 微信聊天记录导出(iOS) [2019.7.24]

    博客地址:https://www.busby.com.cn/2019/07/24/微信聊天记录导出(iOS)[2019.7.24]/ 最前 前不久很久,我的小傻瓜女票误删了我们俩的微信聊天记录,也没有 ...

最新文章

  1. Blender未来科幻武器全流程制作视频教程
  2. spring AOP编程
  3. MySQL的一级索引和二级索引介绍,HBase中提到的二级索引【笔记自用】
  4. SQL Server 创建用户及权限管理
  5. 隐马尔科夫模型——简介
  6. python多次输入数据_如何用python3输出重复的数据?
  7. 无线模块在工业控制上的应用
  8. tensorflow tfrecoder read write
  9. 使用Q查询设计搜索框
  10. TokenInsight:反映区块链行业整体表现的TI指数较昨日同期下跌1.33%
  11. JavaScript(DOM编程一)
  12. 详解YUV系列(二)--YUV422
  13. python调用海康威视的摄像头_Python调用海康威视网络相机之——python读取相机rtsp码流显示画面...
  14. snownlp情感分析
  15. 数据结构与算法之迷宫回溯
  16. 串口调试助手fx2n_PLC串口调试助手详细讲解(结合实操)
  17. 学习笔记-安全-MAC地址攻击
  18. Word插入目录页面编辑Tips
  19. h5让图片转圈的动效,让页面动起来
  20. 第九天 杭电OJ 1087、1203、1003

热门文章

  1. Math.round(-1.5) 等于多少
  2. Word如何在表格里面插入行或者列
  3. 带阵列的服务器如何重装系统,带阵列服务器系统重装
  4. 实战攻防比赛中作为防守方需要做哪些工作
  5. VirtualBox主机与虚拟机互相ping通的方法
  6. SI24R1可以替代NRF24L01P软件硬件DIY兼容成功
  7. Android图标下载网站
  8. 国外问卷调查赚钱网站
  9. 旅游出行 APP 哪家强?
  10. 使用linux提交pbs任务:运行matlab或perl程序