最近想起了以前未实现的一个小功能:卸载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())

  时就可以在卸载删除本地文件之前操作数据文件了.

  <!-- 小米手机对我这个新手来说真是个坑,好多东西与原生系统不一样,这点也不例外,此例不兼容小米-->

<!– 允许程序读取系统日志 –><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卡",并且文件也复制成功.这样就完成了开始我们所提出的小需求了.