做者:咕咚移動技術團隊-喬瑟琳java
最近在需求中須要實現監聽安卓手機通知欄信息的功能,好比實時獲取qq、微信、短信消息。一開始評估是件挺簡單的事兒,實現 NotificationListenerService
,直接上代碼。實現步驟以下:
android
<intent-filter>
:<service android:name="com.example.yuanting.msgpushandcall.service.NotifyService"
android:label="@string/app_name"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService" />
</intent-filter>
</service>
複製代碼
try {
Intent intent;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP_MR1) {
intent = new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS);
} else {
intent = new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS");
}
startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
}
複製代碼
onNotificationPosted
方法內獲取 ,以前在網上查了一些文章有的經過判斷API是否大於18來採起不一樣的辦法,大體是=18
則利用反射獲取 Notification
的內容,>18
則經過Notification.extras
來獲取通知內容,而經測試在部分安卓手機上即便API>18
Notification.extras
是等於null的。所以不能經過此方法獲取通知欄信息默認開啓了NotificationListenerService
將收到系統全部開啓了推送開關的應用的推送消息,若是想要收到指定應用消息,則需過濾該應用的包名:git
String packageName = sbn.getPackageName();
if (!packageName.contains(ComeMessage.MMS) && !packageName.contains(ComeMessage.QQ) && !packageName.contains(ComeMessage.WX)) {
return;
}
複製代碼
短信、QQ、微信對應的包名則爲:github
public static final String QQ="com.tencent.mobileqq";
public static final String WX="com.tencent.mm";
public static final String MMS="com.android.mms";
複製代碼
String content = null;
if (sbn.getNotification().tickerText != null) {
content = sbn.getNotification().tickerText.toString();
}
複製代碼
在onNotificationPosted
方法內經過上面的方法便可獲取部分手機的通知欄消息,可是可是重點來了,在部分手機上,好比華爲榮耀某系列sbn.getNotification().tickerText == null
,經調試發現僅在StatusBarNotification
對象內部的一個view的成員變量上有推送消息內容,所以不得不用上了反射去獲取view上的內容
api
private Map<String, Object> getNotiInfo(Notification notification) {
int key = 0;
if (notification == null)
return null;
RemoteViews views = notification.contentView;
if (views == null)
return null;
Class secretClass = views.getClass();
try {
Map<String, Object> text = new HashMap<>();
Field outerFields[] = secretClass.getDeclaredFields();
for (int i = 0; i < outerFields.length; i++) {
if (!outerFields[i].getName().equals("mActions"))
continue;
outerFields[i].setAccessible(true);
ArrayList<Object> actions = (ArrayList<Object>) outerFields[i].get(views);
for (Object action : actions) {
Field innerFields[] = action.getClass().getDeclaredFields();
Object value = null;
Integer type = null;
for (Field field : innerFields) {
field.setAccessible(true);
if (field.getName().equals("value")) {
value = field.get(action);
} else if (field.getName().equals("type")) {
type = field.getInt(action);
}
}
// 經驗所得 type 等於9 10爲短信title和內容,不排除其餘廠商拿不到的狀況
if (type != null && (type == 9 || type == 10)) {
if (key == 0) {
text.put("title", value != null ? value.toString() : "");
} else if (key == 1) {
text.put("text", value != null ? value.toString() : "");
} else {
text.put(Integer.toString(key), value != null ? value.toString() : null);
}
key++;
}
}
key = 0;
}
return text;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
複製代碼
那麼通過以上方法:先獲取sbn.getNotification().tickerText
,若是爲空,則嘗試使用反射獲取view上的內容,目前測試了主流機型,暫無任何兼容性問題。bash
由於 NotificationListenerService
被殺後再次啓動時,並無去 bindService
,因此致使監聽效果無效。這一現象目前我在僅有的手機上並無出現,可是一旦遇到推薦的解決辦法:利用 NotificationListenerService
先 disable
再 enable
,從新觸發系統的 rebind 操做。代碼以下:微信
private void toggleNotificationListenerService() {
PackageManager pm = getPackageManager();
pm.setComponentEnabledSetting(new ComponentName(this, com.fanwei.alipaynotification.ui.AlipayNotificationListenerService.class),
PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
pm.setComponentEnabledSetting(new ComponentName(this, com.fanwei.alipaynotification.ui.AlipayNotificationListenerService.class),
PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
}
複製代碼
整個消息推送的流程如上,重點即在解決不通手機上獲取消息的兼容性問題,不能簡單的經過api
版本去區分獲取哪一個對象,實踐得出的結論是經過判斷tiketText是否爲空,爲空則試圖使用反射獲取消息內容。app
關於安卓手機上拒接來電的功能,官方並未給出api,搜索了許多資料,花樣百出,有使用模擬mediaButton按鍵、有使用反射拿系統的endCall方法的,但經測試在目前主流的機型上都存在問題。特總結了以下的方法,親測有效:
ide
if(ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED){
ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.CALL_PHONE},1000);
}
複製代碼
這一點十分重要,這是動態申請電話相關的權限,值得注意的是無論你的targetSdk 是否高於安卓6.0,都須要動態的申請此權限,不然,咱們在後面經過反射獲取相應的API,部分手機也會crash,提示你沒有readPhoneState
等權限,雖然這與官方定義的不一致,但國內安卓手機關於權限這塊兒確實是各不相同。
oop
public class PhoneCallListener extends PhoneStateListener {
@Override
public void onCallStateChanged(int state, String incomingNumber) {
switch (state) {
case TelephonyManager.CALL_STATE_OFFHOOK: //電話通話的狀態
break;
case TelephonyManager.CALL_STATE_RINGING: //電話響鈴的狀態
PhoneCallUtil.endPhone(MainActivity.this);
break;
}
super.onCallStateChanged(state, incomingNumber);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
telephonyManager = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
callListener = new PhoneCallListener();
telephonyManager.listen(callListener, PhoneStateListener.LISTEN_CALL_STATE);
}
複製代碼
這塊兒是對電話狀態的監聽,一開始並沒有可注意的tip,但在自測期間發現了些奇怪的現象,好比你直接 telephonyManager.listen(new PhoneCallListener(), PhoneStateListener.LISTEN_CALL_STATE);
直接new一個對象傳入listene
方法,在某些手機這個電話監聽會在某些操做後失效。解決的辦法則是該將PhoneCallListener
的對象申明成成員變量,讓外面的的對象所持有,這樣在跨進程通訊時這個回調不被回收。
按照系統iTelephony.aidl文件的路徑,新建一個相同文件,其接口內方法只須要寫endCall()
,注意路徑必需要徹底相同:
package com.android.internal.telephony;
interface ITelephony {
boolean endCall();
}
複製代碼
java方法:
public static void endPhone(Context context) {
TelephonyManager telephonyManager = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);
Method method = null;
try {
method = TelephonyManager.class.getDeclaredMethod("getITelephony");
method.setAccessible(true);
ITelephony telephony = (ITelephony) method.invoke(telephonyManager);
telephony.endCall();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (RemoteException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
複製代碼
通過以上三步,可以實現掛斷電話的功能,可是通過多種機型的測試,在vivo手機上,仍是由於權限的問題不能生效,vivo手機上報出的錯誤以下:
AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.yuanting.msgpushandcall, PID: 6170
java.lang.SecurityException: MODIFY_PHONE_STATE permission required.
at android.os.Parcel.readException(Parcel.java:1684)
at android.os.Parcel.readException(Parcel.java:1637)
at com.android.internal.telephony.ITelephony$Stub$Proxy.endCall(ITelephony.java:1848)
at com.example.yuanting.msgpushandcall.utils.PhoneCallUtil.endPhone(PhoneCallUtil.java:25)
at com.example.yuanting.msgpushandcall.MainActivity$PhoneCallListener.onCallStateChanged(MainActivity.java:80)
at android.telephony.PhoneStateListener$1.handleMessage(PhoneStateListener.java:298)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6211)
at java.lang.reflect.Method.invoke(Native Method)
複製代碼
神奇的安卓手機,在源碼內,查到了僅僅是掛斷電話是不須要修改手機電話權限的,接聽電話才須要MODIFY_PHONE_STATE
,可是部分手機仍是報沒有權限,這就是安卓吧~~,所以目前該方法並無兼容vivo手機。
以上是近期對消息通知、來電拒接的一些總結,關於來電的拒接功能,部分手機還存在兼容性問題,後續有新的思路會持續更新。在文章中有不足之處或錯誤指出望予以指出,不勝感激。
git 地址 :github.com/CodoonDemo/…