自定义服务WatchService,在该类的onStart函数(重写此函数用以响应服务启动、创建动作,之所以不在onCreate函数中实现,是因为onCreate函数仅在创建服务的时候调用,而onStart会在创建或者启动服务的时候均调用)中,主要完成三件事情:
(1)建立新的logcat进程,并对日志信息进行监控。具体实现方法如下:


mLogcat = safeExec("logcat -c");//此句代码用于清空日志 
  
 mLogcat = safeExec("logcat -v raw ActivityManager:I *:S");//此句代码用于监控标签为ActivityManager的日志信息

完成上述步骤之后,需要对mLogcat进程输出信息进行监控,,将其重定向,然后按行提取日志信息并对日志信息分析处理,具体实现如下

is = mLogcat.getInputStream();//is是一个InputStream对象,将logcat的输出定向到is 
  
 isr = new InputStreamReader(is);//isr是一个InputStreamReader对象,用于读取is流对象的内容 
  
 reader = new BufferedReader(isr, 0x400);//BufferedReader对象用于读取缓冲区内容


紧接着可以提取流的内容,然后对提取的内容进行分析,提取出有效信息


line = reader.readLine();//String line;//按行提取流内容

(2)提取出有效信息。通过查看输出日志的方式,不难发现,启动新的进程将会向系统日志中写入标签为ActivityManager的日志信息,该日志信息的有效Message部分包含了具体行为内容(Starting activity...或者Start proc....etc),我们需要提取内容为Start proc打头的日志信息,分析该日志信息,可以很容易获得日志信息的组成:Start proc PKG_NAME for activity/service PKG_NAME/.CLS_NAME:pid=XXX uid=XXX gids={XXX,XXX...} 有了上述分析,那么要提取出新建进程的pid易如反掌,不仅可以获取pid,我们可以获取新建进程的包名、类名、pid、uid、gids信息。(上文中PKG_NAME表示包名,CLS_NAME表示类名,XXX表示数字组合。)

(3)通过前两步提取的有效信息对新建进程进行有效控制。如杀死新进程:android.os.Process.killProcess(int pid)等。



说明:在第一步对logcat进程输出流重定向,然后在第二步中按行提取流内容的操作是阻塞式的,这意味着,如果进程没有新的日志信息,服务线程将阻塞在此位置,直到有新的进程启动,并向系统日志缓冲区写入了新的内容。鉴于Android系统对进程状态的控制,如果超过5000毫秒,程序无响应则会将该进程标记为ANR(Application Not Response)并报以弹窗提示。所以,在提取内容的时候,BufferedReader有一成员函数用于检测reader是否有数据可读,此函数为ready(),返回类型为boolean,如果没有准备好数据,则可以用wait或者sleep函数将进程或线程挂起某一指定时间,然后再进行状态判定。




以下通过监控进程启动、应用软件卸载两种行为来具体说明。


实例1.通过监控日志信息,监控启动进程事件。

1)新建logcat进程,并将其输出信息重定向到InputStream对象is中,紧接着用InputStream对象创建一个InputStreamReader对象isr,最后以isr为参数创建一个BufferedReader对象reader,通过此对象提取日志内容。代码如下:


Runtime rt = Runtime.getRuntime(); 
  
 mLogcat = rt.exec("logcat -c");//先清空日志 
  
 mLogcat = rt.exec("logcat -v raw ActivityManager:I *:S");//然后查看标签为ActivityManager的日志信息 
  
 if (mLogcat != null) { 
  
 InputStream is = null; 
  
 InputStreamReader isr = null; 
  
 try { 
  
 is = mLogcat.getInputStream(); 
  
 isr = new InputStreamReader(is); 
  
 reader = new BufferedReader(isr, 0x400);//全局变量reader,方便其他函数调用 
  
 } catch (Exception e) { 
  
 e.printStackTrace(); 
  
 } 
  
 }

2)从reader中提取有效信息。在此实例中,仅需提取标签为ActivityManager(注意已经在新建进程时筛选完毕),内容以Start proc打头的日志信息。代码如下:

String line = null;//用来存放读取的日志信息 
  
 int dat_s = -1;//用来记录Start proc的位置(可以用来判断是不是需要的日志信息) 
  
 line = reader.readLine();//为阻塞方式 
  
 dat_s = -1; 
  
 dat_s = line.indexOf("Start proc");//引号里为检索特征串,用来判断当前这条日志是不是我们需要分析的日志 
  
 if (dat_s < 0)//为真表明此句不包含Start proc语句,同时也表明不是启动新进程的日志信息 
  
 continue;


有了上述分析,可以进一步提取日志中的包信息、类信息。

一旦步骤2)中的if语句条件成立,则表明此语句为我们需要分析的日志,即当前日志包含的进程信息为刚刚启动的进程,也即完成了进程启动的监控。



实例2.通过日志信息监控应用程序卸载事件。 1)按照实例1中的第一步,新建logcat进程,将输出内容重定向。

2)提取内容,捕捉有效信息。对于实例2,我们需要的有效信息应该是标签为ActivityManager,内容以“Starting activity: Intent { act=android.intent.action.DELETE”打头的日志信息,注意一定要得到act属性值,这个是卸载应用程序时首先会调用的Activity,如果没有得到此属性值为“android.intent.action.DELETE”,是无法断定系统将要卸载应用程序的。代码仅在获取的时候稍有差异:


String line = null;//用来存放读取的日志信息 
  
 int dat_s = -1;//用来记录Start proc的位置(可以用来判断是不是需要的日志信息) 
  
 line = reader.readLine();//为阻塞方式 
  
 dat_s = -1; 
  
 dat_s = line.indexOf("Starting activity: Intent { act=android.intent.action.DELETE");//这里不同 
  
 if (dat_s < 0)//为真表明此句不包含Start proc语句,同时也表明不是启动新进程的日志信息 
  
 continue;

一旦通过了上述条件验证,表明已经检测到系统即将卸载应用程序,也即检测到系统卸载应用程序事件。


注:在使用时可以根据需要以最小的特征串来判断,如上面代码中其实可以进通过“act=android.intent.action.DELETE”这一字符串即可判断当前日志信息是否是所需要的日志信息。如果不需要当然就不需要继续分析下去了。当然,这个特征串可以更短些,或者采用其他的特征字符串来判断。考虑到移动便携式设备的处理能力,这个特征字符串应该越容易检索越好。以上实例中使用到的API,若有疑问可直接查询谷歌 Android开发者网站


资料,或者查阅本地Android文档即可。


(本例在Android2.2仿真器上和真机上均测试通过,但是针对不同的Android系统,如Ophone,其日志格式可能与上述不一致,在开发之前检查日志信息即可。笔者测试机OPhone的日志格式为“the application ready to delete is PKG_NAME”。所以当检测到该信息时表示,系统即将卸载包名为PKG_NAME的应用。)