目前市面上,Android上的防騷擾類應用非常多,比如騰訊手機管家、360手機衛士、金山手機衛士等。由於受Android OS設計框架,他們的來電攔截實現,都是通過接受com.android.phone進程所發送的廣播而實現。具體的實現方案,網上有很多教程,在此不再敘述。
採用上述的方案實現來電攔截,存在一個先天性的技術缺陷——每當有來電時,都會跳到系統來電UI,而且還伴有短暫的鈴聲,之後纔會被掛斷。究其原因,主要是因爲Android RIL的JAVA的響應邏輯,在com.android.phone實現的,也就是說,當有來電時,會先經由com.android.phone處理,之後纔對外發送廣播。因此,無論技術處理上如何快速,前面所說的技術缺陷也是無法解決的。當然,如果在有root的情況下,就有完善的實現方案,也就是接下來準備介紹的方案——基於Android
RIL層的來電攔截方案。
前提
本次的技術可行性分析,是建立在已經成功實現在Android上進行so和dex注入的基礎上。
原理
通過分析來電在RIL層的消息傳遞流程, 並尋流程中的所有“連接點”。然後選擇最適合的“連接點”進行代碼注入。所謂“連接點”有很多表現形式,總結起來,主要有兩種:一種是方法重寫(類與類之間的關係);一種包裝(實體與實體的關係)。當然這兩種方式,都是代碼級別的,如果跳出代碼級別,那在JAVA每一個函數的調用,都可以是一個“連接點“(比如通過修改JNI的methodID的結構體中native方法的指針地址等)。
而本次技術方案的實現,則同時使用了上面所介紹的兩種方式,下面有詳細介紹。
分析
RIL$RILReceiver.run
RIL.readRilMessage
RIL.processResponse
RIL.processUnsolicited
Registrant.notifyRegistrant
Registrant.internalNotifyRegistrant
Handler.sendMessage
Handler.handleMessage
PhoneBase.handleMessage
PhoneBase.notifyIncmoingRing
RegistrantList.notifyRegistrants
RegistrantList.internalNotifyRegistrants
Registrant.internalNotifyRegistrant
Handler.sendMessage
CallManager.handlerMessage
RegistrantList.notifyRegistrants
RegistrantList.internalNotifyRegistrants
Registrant.internalNotifyRegistrant
CallNotifier.handleMessage
Ringer.ring(響鈴)
...
通過源碼分析,其中紅色部分的調用堆棧,是framework層的;
而藍色部分,則package/phone層的;
其中加粗加下劃線部分,是各層次消息傳遞的關鍵“連接點”邏輯,主要是通過嵌套使用Handler的實現。
因此,要實現來電攔截,在這三個環節的任意一個,插入判斷邏輯都可以。但還需要結合框架本身的設計出發考慮。由於PhoneBase是各種phone(GSMPhone, CDMAPhone等類)的基類,因此在第一個環節進行“注入”,並不是好的控制點,因爲需要實現多處“注入”,加大了風險和不確定性。而在第二個和第三個都只需要注入一次即可,但第三個更接近應用層,因此API更加穩定,而且兼容性更好。最後,我們選擇第三個點進行注入,即CallNotifier這個Handler。
下面是具體實現的代碼片斷:
class ProxyHandler extends Handler {
private Handler mInnter;
public ProxyHandler(Handler h){
mInnter = h;
}
@Override
public void handleMessage(Message msg) {
// ......
// ......
mInnter.handlerMessage(msg);
}
}
CallManager instance = CallManager.getInstance();
//通過反射,拿到其字段mIncomingRingRegistrants
RegistrantList mIncomingRingRegistrants = instance.mIncomingRingRegistrants;
for(int i=0; i<mIncomingRingRegistrants.size(); i++){
Registrant item = mIncomingRingRegistrants.get(i);
Handler handler = item.getHandler();
//通過反射,拿到其字段refH
item.refH = new WeakReference(new ProxyHandler(handler)); //完成注入
}
需要注意
如果採用了本方案的來電攔截方式,屬於是“事前處理”,跟原來的方式完全不一樣了(原來是無序廣播的方式,各個監聽都可以接收,屬於“事後處理”)。所以,攔截的流程上會有諸多變化,也有各種細節需要考慮。
後話
本方案是在java層實現的,因此還可以非常方便的使用Android SDK所提供的各種API。另外,對於IPC通訊也非常方便(因爲com.android.phohe已經有完整的環境)。而除了在java層上實現之外,還可以通過本地庫實現。原理很簡單,就是在com.android.phone與 rild之間,加入“中間人”,做中轉代理。這個方法,之前也有人實現過,並做出了DEMO。但由於更偏向於低層,很多協調的解釋需要看源碼,不如在framework層,可以直接拿到解釋後的數據。由於我們的目的是攔截來電時不希望跳轉到來電界面以及響一聲,更偏向於App層面的邏輯,所以我更偏向於在framework上實現。