在卸载行为之前保存本地数据
最近想起了以前未实现的一个小功能:卸载APP时保存本地数据库文件.从网上查找了一些资料,具体的做法有以下几种:
1.监听系统卸载广播:只能监听到其他应用的卸载广播,无法监听到自己是否被卸载。
2.读取系统 log。
3.静默安装另一个程序,监听自己是否被卸载:需要 root 权限。
4.Java 线程轮询,监听/data/data/{package-name}目录是否存在:卸载 app,进程退出,线程也被销毁。
5.C 进程轮询,监听/data/data/{package-name}目录是否存在:目前业界普遍采用的方案。
第一种方法缺陷很明显不多说,第二种方法后面详解,第三种方法明显是强盗软件,不推荐,第四种十分耗费资源;第五种以前实现过类似的功能,是比较好的一种方法,但与我们的需求不符,当监听到目录删除时,文件已经被delete,无法进行数据操作.因此目前暂时只能够读取log来实现这个小功能.
实现思路是:每当弹出卸载弹出框时,则会在Log中打印一条消息:
09-13 20:20:47.480: I/ActivityManager(388): START u0 {act=android.intent.action.DELETE dat=package:com.example.uninstall
然后在服务中不断读取Log,当满足info.contains("android.intent.action.DELETE")&&info.contains(MyApp.getApp().getPackageName())
时就可以在卸载删除本地文件之前操作数据文件了.
<!-- 小米手机对我这个新手来说真是个坑,好多东西与原生系统不一样,这点也不例外,此例不兼容小米-->
需要注意的一点是:在android4.1之前,读取Log之前只需要添加<!– 允许程序读取系统日志 –><uses-permission android:name=”android.permission.READ_LOGS” />
权限,这样就可以读到所有日志,但在android4.1之后,Google认为这是一种不安全的行为操作,因此在更高版本上只能读取到本程序的Log日志,不能读取到系统日志.这点坑了我一天时间,各位请注意!!!
而我们就正好需要读取系统日志,这就只能要求设备root,并通过Runtime.getRuntime().exec来实现,网上的方法是:
String[] shellCmd = new String[] { "logcat","ActivityManager:I *:S" };
Runtime.getRuntime().exec(shellCmd);
然后在通过输入流和错误流获取,但我测试了下,仍然获取不到系统日志,不知道你们实现了没有?最后需要执行SU才可以成功获取.废话不多说了,下面上代码:
Log工具类:
1 public class ShellUtils { 2 3 private static final Logger logger = LogPcComm.getLogger(ShellUtils.class); 4 public static final String COMMAND_SU = "su"; 5 public static final String COMMAND_SH = "sh"; 6 public static final String COMMAND_EXIT = "exit\n"; 7 public static final String COMMAND_LINE_END = "\n"; 8 private static readRunnable r1; 9 private static readRunnable r2; 10 private static LogcatObserver observer; 11 12 /** 13 * check whether has root permission 14 * 15 * @return 16 */ 17 public static boolean checkRootPermission() { 18 return execCommand("echo root", true, false).result == 0; 19 } 20 21 /** 22 * execute shell command, default return result msg 23 * 24 * @param command command 25 * @param isRoot whether need to run with root 26 * @return 27 * @see ShellUtils#execCommand(String[], boolean, boolean) 28 */ 29 public static CommandResult execCommand(String command, boolean isRoot,LogcatObserver _observer) { 30 observer = _observer; 31 return execCommand(new String[] {command}, isRoot, true); 32 } 33 34 /** 35 * execute shell commands, default return result msg 36 * 37 * @param commands command list 38 * @param isRoot whether need to run with root 39 * @return 40 * @see ShellUtils#execCommand(String[], boolean, boolean) 41 */ 42 public static CommandResult execCommand(List<String> commands, boolean isRoot,LogcatObserver _observer) { 43 observer = _observer; 44 return execCommand(commands == null ? null : commands.toArray(new String[] {}), isRoot, true); 45 } 46 47 /** 48 * execute shell commands, default return result msg 49 * 50 * @param commands command array 51 * @param isRoot whether need to run with root 52 * @return 53 * @see ShellUtils#execCommand(String[], boolean, boolean) 54 */ 55 public static CommandResult execCommand(String[] commands, boolean isRoot,LogcatObserver _observer) { 56 observer = _observer; 57 return execCommand(commands, isRoot, true); 58 } 59 60 /** 61 * execute shell command 62 * 63 * @param command command 64 * @param isRoot whether need to run with root 65 * @param isNeedResultMsg whether need result msg 66 * @return 67 * @see ShellUtils#execCommand(String[], boolean, boolean) 68 */ 69 public static CommandResult execCommand(String command, boolean isRoot, boolean isNeedResultMsg) { 70 return execCommand(new String[] {command}, isRoot, isNeedResultMsg); 71 } 72 73 /** 74 * execute shell commands 75 * 76 * @param commands command list 77 * @param isRoot whether need to run with root 78 * @param isNeedResultMsg whether need result msg 79 * @return 80 * @see ShellUtils#execCommand(String[], boolean, boolean) 81 */ 82 public static CommandResult execCommand(List<String> commands, boolean isRoot, boolean isNeedResultMsg) { 83 return execCommand(commands == null ? null : commands.toArray(new String[] {}), isRoot, isNeedResultMsg); 84 } 85 86 /** 87 * execute shell commands 88 * 89 * @param commands command array 90 * @param isRoot whether need to run with root 91 * @param isNeedResultMsg whether need result msg 92 * @return <ul> 93 * <li>if isNeedResultMsg is false, {@link CommandResult#successMsg} is null and 94 * {@link CommandResult#errorMsg} is null.</li> 95 * <li>if {@link CommandResult#result} is -1, there maybe some excepiton.</li> 96 * </ul> 97 */ 98 public static CommandResult execCommand(String[] commands, boolean isRoot, boolean isNeedResultMsg) { 99 int result = -1; 100 if (commands == null || commands.length == 0) { 101 logger.info("-------------参数为空------------------"); 102 return new CommandResult(result, null, null); 103 } 104 105 Process process = null; 106 BufferedReader successResult = null; 107 BufferedReader errorResult = null; 108 StringBuilder successMsg = null; 109 StringBuilder errorMsg = null; 110 111 DataOutputStream os = null; 112 BufferedWriter out; 113 try { 114 115 out = new BufferedWriter(new FileWriter(new File(FileUtil.getSDcardPath()+"/loggg"),true)); 116 117 118 process = Runtime.getRuntime().exec(isRoot ? COMMAND_SU : COMMAND_SH); 119 os = new DataOutputStream(process.getOutputStream()); 120 for (String command : commands) { 121 if (command == null) { 122 continue; 123 } 124 // donnot use os.writeBytes(commmand), avoid chinese charset error 125 os.write(command.getBytes()); 126 os.writeBytes(COMMAND_LINE_END); 127 128 } 129 os.writeBytes(COMMAND_EXIT); 130 os.flush(); 131 132 // get command result 133 if (isNeedResultMsg) { 134 out.append("-----------------开始记录日志文件------------------"); 135 out.append("\n"); 136 successMsg = new StringBuilder(); 137 errorMsg = new StringBuilder(); 138 r1 = new readRunnable(process.getInputStream(), successMsg,out); 139 new Thread(r1).start(); 140 r2 = new readRunnable(process.getErrorStream(), errorMsg,out); 141 new Thread(r2).start(); 142 // successResult = new BufferedReader(new InputStreamReader(process.getInputStream())); 143 // errorResult = new BufferedReader(new InputStreamReader(process.getErrorStream())); 144 // String s; 145 // while ((s = successResult.readLine()) != null) { 146 // successMsg.append(s); 147 // logger.info("写入success文件"); 148 // } 149 // while ((s = errorResult.readLine()) != null) { 150 // errorMsg.append(s); 151 // logger.info("写入error文件"); 152 // } 153 } 154 logger.info("等待su执行完毕"); 155 result = process.waitFor(); 156 logger.info("su结果-----------------:"+result); 157 // while((!r1.flag)&&(!r2.flag)){ 158 // continue; 159 // } 160 } catch (Exception e) { 161 e.printStackTrace(); 162 System.out.println("写入流异常"); 163 } finally { 164 // try { 165 // if (os != null) { 166 // os.close(); 167 // } 168 //// if (successResult != null) { 169 //// successResult.close(); 170 //// } 171 //// if (errorResult != null) { 172 //// errorResult.close(); 173 //// } 174 // } catch (IOException e) { 175 // e.printStackTrace(); 176 // } 177 178 // if (process != null) { 179 // process.destroy(); 180 // } 181 } 182 return new CommandResult(result, successMsg == null ? null : successMsg.toString(), errorMsg == null ? null 183 : errorMsg.toString()); 184 } 185 186 /** 187 * result of command 188 * <ul> 189 * <li>{@link CommandResult#result} means result of command, 0 means normal, else means error, same to excute in 190 * linux shell</li> 191 * <li>{@link CommandResult#successMsg} means success message of command result</li> 192 * <li>{@link CommandResult#errorMsg} means error message of command result</li> 193 * </ul> 194 */ 195 public static class CommandResult { 196 197 /** result of command **/ 198 public int result; 199 /** success message of command result **/ 200 public String successMsg; 201 /** error message of command result **/ 202 public String errorMsg; 203 204 public CommandResult(int result) { 205 this.result = result; 206 } 207 208 public CommandResult(int result, String successMsg, String errorMsg) { 209 this.result = result; 210 this.successMsg = successMsg; 211 this.errorMsg = errorMsg; 212 } 213 214 @Override 215 public String toString() { 216 return "CommandResult [result=" + result + ", successMsg=" 217 + successMsg + ", errorMsg=" + errorMsg + "]"; 218 } 219 220 } 221 222 static class readRunnable implements Runnable{ 223 public InputStream is; 224 StringBuilder result; 225 public BufferedReader bufferedReader; 226 public boolean flag = true; 227 private BufferedWriter out; 228 public readRunnable(InputStream is,StringBuilder result,BufferedWriter out){ 229 this.is = is; 230 this.result = result; 231 this.out = out; 232 } 233 @Override 234 public void run() { 235 bufferedReader = new BufferedReader(new InputStreamReader(is)); 236 String s; 237 try { 238 out.append("----------开始读取数据------------------"); 239 out.flush(); 240 while(true){ 241 s = bufferedReader.readLine(); 242 if(s!=null && !s.contains("System.out")){ 243 out.append(s); 244 out.append("\n"); 245 out.flush(); 246 }else{ 247 try { 248 Thread.sleep(100); 249 } catch (InterruptedException e) { 250 e.printStackTrace(); 251 } 252 } 253 if(s.contains("android.intent.action.DELETE")&& 254 s.contains(MyApp.getApp().getPackageName())){ 255 observer.handleNewLine(s); 256 logger.info("该程序即将被卸载,将会保存本地的数据文件到sd卡上"); 257 // Looper.loop(); 258 // Toast.makeText(MyApp.getApp(), "程序即将卸载", Toast.LENGTH_SHORT).show(); 259 // Looper.prepare(); 260 // Looper.myLooper().quitSafely(); 261 262 //FileUtil.copyAssetFileToFiles(MyApp.getApp(), "Undone.txt", Environment.getExternalStorageDirectory()+""); 263 } 264 } 265 } catch (IOException e) { 266 logger.info(e); 267 e.printStackTrace(); 268 }finally{ 269 flag = false; 270 logger.info("----------读取数据完成------------------"); 271 // try { 272 // if(bufferedReader!=null){ 273 // bufferedReader.close(); 274 // } 275 // 276 // } catch (IOException e) { 277 // // TODO Auto-generated catch block 278 // e.printStackTrace(); 279 // } 280 } 281 } 282 283 } 284 285 286 }
使用方法:在子线程中监听log
1 public class AndroidLogcatScanner implements Runnable { 2 private Logger logger = LogPcComm.getLogger(this.getClass()); 3 private LogcatObserver observer; 4 private BufferedWriter out; 5 6 public AndroidLogcatScanner(LogcatObserver observer) { 7 this.observer = observer; 8 } 9 10 public void run() { 11 String[] cmds = { "logcat", "-c" }; 12 // String shellCmd1 = "logcat -s ActivityManager"; 13 String shellCmd1 = "logcat"; 14 List<String> list = new ArrayList<String>(); 15 list.add("logcat"); 16 // list.add("-d"); 17 // list.add("-v"); 18 // list.add("time"); 19 // list.add("-f"); 20 // list.add(">"); 21 // list.add("/sdcard/log/logcat.txt"); 22 // list.add("-s"); 23 // list.add("ActivityManager"); 24 25 String[] shellCmd = new String[] { "logcat", "ActivityManager:I *:S" }; 26 Process process = null; 27 Runtime runtime = Runtime.getRuntime(); 28 try { 29 out = new BufferedWriter(new FileWriter(new File( 30 FileUtil.getSDcardPath() + "/loggg"), true)); 31 out.write("=============================================" + "\n"); 32 // observer.handleNewLine(line); 33 int waitValue; 34 waitValue = runtime.exec(cmds).waitFor(); 35 // process = runtime.exec(list.toArray(new String[list.size()])); 36 // process = runtime.exec(shellCmd1); 37 CommandResult execCommand = ShellUtils.execCommand(shellCmd, true, 38 observer); 39 40 out.append("-----------------结束记录日志文件------------------"); 41 42 logger.info("-----------------开始记录日志文件------------------"); 43 logger.info("\n"); 44 logger.info(execCommand.toString()); 45 logger.info("\n"); 46 logger.info("-----------------结束记录日志文件------------------"); 47 48 } catch (InterruptedException e) { 49 e.printStackTrace(); 50 } catch (IOException ie) { 51 ie.printStackTrace(); 52 } finally { 53 try { 54 if (out != null) { 55 out.close(); 56 } 57 if (process != null) { 58 process.destroy(); 59 } 60 } catch (Exception e) { 61 e.printStackTrace(); 62 } 63 } 64 } 65 66 public interface LogcatObserver { 67 public void handleNewLine(String line); 68 } 69 }
在服务中启动线程:
1 public class AndroidLogcatScannerService extends Service{ 2 private Logger logger = LogPcComm.getLogger(this.getClass()); 3 private AndroidLogcatScanner scannerRunnable; 4 public static ScheduledExecutorService schedualExec; 5 @Override 6 public void onCreate() { 7 schedualExec = Executors.newScheduledThreadPool(2); 8 super.onCreate(); 9 } 10 @Override 11 public void onStart(Intent intent, int startId) { 12 if (scannerRunnable == null) { 13 scannerRunnable = new AndroidLogcatScanner(new LogcatObserver() { 14 @Override 15 public void handleNewLine(String info) { 16 //监听到程序即将卸载,处理文件操作 17 FileUtil.copyAssetFileToFiles(MyApp.getApp(), "Undone.txt", Environment.getExternalStorageDirectory()+""); 18 logger.info("文件转移成功-------------------------------"); 19 } 20 }); 21 } 22 new Thread(scannerRunnable).start(); 23 } 24 @Override 25 public IBinder onBind(Intent intent) { 26 // TODO Auto-generated method stub 27 return null; 28 } 29 30 }
当监听到卸载动作后,在loggg文本中就会输出"该程序即将被卸载,将会保存本地数据文件到sd卡",并且文件也复制成功.这样就完成了开始我们所提出的小需求了.

浙公网安备 33010602011771号