最近想起了以前未实现的一个小功能:卸载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卡",并且文件也复制成功.这样就完成了开始我们所提出的小需求了.