概述
PhoneStateListener是给三方app监听通信状态变化的方法,基本使用如下:
- TelephonyManager mTelephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
- PhoneStateListener mPhoneStateListener = new PhoneStateListener(mSubId) {
- @Override
- public void onCallStateChanged(int state, String incomingNumber) {
- ...
- }
- };
- mTelephonyManager.listen(
- mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
- mTelephonyManager.listen(
- mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
- mTelephonyManager.listenmPhoneStateListener
- PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR
- | PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR);
监听的事件有多个,监听要实现的回调方法也相应的有多个,例如 PhoneStateListener.LISTEN_CALL_STATE对应 onCallStateChanged(int state, String incomingNumber) 。具体可参见Android sdk。
多卡情况
Android5.0之前是单SIM卡,5.0之后加入了多SIM卡机制,PhoneStateListener相应的也可以单独监听某个Phone对象了
- /**
- * Create a PhoneStateListener for the Phone using the specified subscription.
- * This class requires Looper.myLooper() not return null. To supply your
- * own non-null Looper use PhoneStateListener(int subId, Looper looper) below.
- * @hide
- */
- public PhoneStateListener(int subId) {
- this(subId, Looper.myLooper());
- }
吐槽下Android源码中使用subId,因为比较难以理解,5.0之前无论是mtk还是高通的方案中,基本都是使用slotid(要监听卡1或卡2是很常见的,但是要监听iccid为xxxx的sim卡就比较怪了,用户会记得住SIM卡的id或者iccid吗?id是数据库中的递增主键,iccid十几位的数字,用户一般也就记得住卡的号码、卡的运营商和自己把卡插在卡槽1或者卡槽2 这三个信息;还有种情况,如果不插SIM卡,subId和slotid是无法转换的,岂不是卡槽1或者卡槽2分别监听就无法实现了,这种情况下slotid才有意义,subid已经无意义了)。
系统注册代码流程
1.注册服务建立
系统服务一般都是起源于SystemServer
/frameworks/base/services/java/com/android/server/SystemServer.java
- private void run() {
- ...
- startOtherServices();
- ...
- }
- private void startOtherServices() {
- ...
- telephonyRegistry = new TelephonyRegistry(context);
- ServiceManager.addService("telephony.registry", telephonyRegistry);
- ...
- }
frameworks/base/services/core/java/com/android/server/TelephonyRegistry.java
- TelephonyRegistry(Context context) {
- CellLocation location = CellLocation.getEmpty();
- mContext = context;
- mBatteryStats = BatteryStatsService.getService();
- // mConnectedApns = new ArrayList<String>();
- int numPhones = TelephonyManager.getDefault().getPhoneCount();
- if (DBG) log("TelephonyRegistor: ctor numPhones=" + numPhones);
- mNumPhones = numPhones;
- mConnectedApns = (ArrayList<String>[]) new ArrayList[numPhones];
- mCallState = new int[numPhones];
- ...
- mPreciseDataConnectionState = new PreciseDataConnectionState[numPhones];
- for (int i = 0; i < numPhones; i++) {
- mConnectedApns[i] = new ArrayList<String>();
- mCallState[i] = TelephonyManager.CALL_STATE_IDLE;
- ...
- mPreciseDataConnectionState[i] = new PreciseDataConnectionState();
- }
- ...
- }
TelephonyRegistry构造函数中先获取当前手机的phone对象数量,通俗的说也就是获取当前手机支持SIM卡的数量。依据numPhones建立相应的成员数组,例如mCallState就是用于通话状态,创建完毕后赋予初始值。
2.TelephonyManager获取注册服务
frameworks/base/telephony/java/android/telephony/TelephonyManager.java
- private static ITelephonyRegistry sRegistry;
frameworks/base/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
- if (sRegistry == null) {
- sRegistry = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService(
- "telephony.registry"));
- }
3.TelephonyManager Listen方法分析
- public void listen(PhoneStateListener listener, int events) {
- if (mContext == null) return;
- try {
- Boolean notifyNow = (getITelephony() != null);
- sRegistry.listenForSubscriber(listener.mSubId, getOpPackageName(),
- listener.callback, events, notifyNow);
- } catch (RemoteException ex) {
- // system process dead
- } catch (NullPointerException ex) {
- // system process dead
- }
- }
getITelephony是判断phone服务是否存在的,phone服务是在com.android.phone进程中的,phone服务实现代码在packages/services/Telephony/src/com/android/phone/PhoneInterfaceManager.java。该服务没有建立的话证明通信系统没有初始化完毕或者有问题,所以notifyNow参数设置为true是没有意义的。注意这里传递的listener.callback定义在PhoneStateListener中
- IPhoneStateListener callback = new IPhoneStateListener.Stub() {...}
IPhoneStateListener是一个aidl文件,TelephonyRegistry服务通过它才能实现对客户端回调方法的调用。
调用listenForSubscriber,这个进程间调用带代码又会跑到system进程中的TelephonyRegistry服务。
- public void listenForSubscriber(int subId, String pkgForDebug, IPhoneStateListener callback,
- int events, boolean notifyNow) {
- listen(pkgForDebug, callback, events, notifyNow, subId);
- }
- private void listen(String callingPackage, IPhoneStateListener callback, int events,
- boolean notifyNow, int subId) {
- ...
- if (events != PhoneStateListener.LISTEN_NONE) {
- /* Checks permission and throws Security exception */
- checkListenerPermission(events);
- if ((events & ENFORCE_PHONE_STATE_PERMISSION_MASK) != 0) {
- try {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, null);
- // SKIP checking for run-time permission since caller or self has PRIVILEGED
- // permission
- } catch (SecurityException e) {
- if (mAppOps.noteOp(AppOpsManager.OP_READ_PHONE_STATE, Binder.getCallingUid(),
- callingPackage) != AppOpsManager.MODE_ALLOWED) {
- return;
- }
- }
- }
- synchronized (mRecords) {
- // register
- Record r;
- find_and_add: {
- IBinder b = callback.asBinder();
- final int N = mRecords.size();
- for (int i = 0; i < N; i++) {
- r = mRecords.get(i);
- if (b == r.binder) {
- break find_and_add;
- }
- }
- r = new Record();
- r.binder = b;
- mRecords.add(r);
- if (DBG) log("listen: add new record");
- }
- r.callback = callback;
- r.callingPackage = callingPackage;
- r.callerUserId = callerUserId;
- boolean isPhoneStateEvent = (events & (CHECK_PHONE_STATE_PERMISSION_MASK
- | ENFORCE_PHONE_STATE_PERMISSION_MASK)) != 0;
- r.canReadPhoneState = isPhoneStateEvent && canReadPhoneState(callingPackage);
- // Legacy applications pass SubscriptionManager.DEFAULT_SUB_ID,
- // force all illegal subId to SubscriptionManager.DEFAULT_SUB_ID
- if (!SubscriptionManager.isValidSubscriptionId(subId)) {
- r.subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
- } else {//APP specify subID
- r.subId = subId;
- }
- r.phoneId = SubscriptionManager.getPhoneId(r.subId);
- int phoneId = r.phoneId;
- r.events = events;
- if (DBG) {
- log("listen: Register r=" + r + " r.subId=" + r.subId + " phoneId=" + phoneId);
- }
- if (VDBG) toStringLogSSC("listen");
- if (notifyNow && validatePhoneId(phoneId)) {
- if ((events & PhoneStateListener.LISTEN_SERVICE_STATE) != 0) {
- try {
- if (VDBG) log("listen: call onSSC state=" + mServiceState[phoneId]);
- r.callback.onServiceStateChanged(
- new ServiceState(mServiceState[phoneId]));
- } catch (RemoteException ex) {
- remove(r.binder);
- }
- }
- ...
- }
- }
- } else {
- if(DBG) log("listen: Unregister");
- remove(callback.asBinder());
- }
- }
反注册流程:就是从mRecords中删除掉对应record,添加或者删除都是依据binder对象的
4.客户端回调方法执行流程
第一次回调其实在注册时就执行了,后续的回调都源于phone进程中ril上报的事件,代码都在telephony framework中,接下来拿onServiceStateChanged的回调流程举例。 frameworks/opt/telephony/src/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java
- case EVENT_POLL_STATE_REGISTRATION:
- case EVENT_POLL_STATE_GPRS:
- case EVENT_POLL_STATE_OPERATOR:
- case EVENT_POLL_STATE_NETWORK_SELECTION_MODE:
- ...
- ar = (AsyncResult) msg.obj;
- handlePollStateResult(msg.what, ar);
- protected void handlePollStateResult (int what, AsyncResult ar) {
- ...
- pollStateDone();
- ...
- }
- private void pollStateDone() {
- ...
- mPhone.notifyServiceStateChanged(mSS);
- ...
- }
frameworks/opt/telephony/src/java/com/android/internal/telephony/gsm/GsmPhone.java
- /*package*/ void
- notifyServiceStateChanged(ServiceState ss) {
- super.notifyServiceStateChangedP(ss);
- }
frameworks/opt/telephony/src/java/com/android/internal/telephony/PhoneBase.java
- protected void notifyServiceStateChangedP(ServiceState ss) {
- ...
- mNotifier.notifyServiceState(this);
- ...
- }
mNotifier类型是PhoneNotifier,该类是个interface,实现类是DefaultPhoneNotifier。
frameworks/opt/telephony/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java- public void notifyServiceState(Phone sender) {
- ...
- mRegistry.notifyServiceStateForPhoneId(phoneId, subId, ss);
- ...
- }
frameworks/base/services/core/java/com/android/server/TelephonyRegistry.java
- public void notifyServiceStateForPhoneId(int phoneId, int subId, ServiceState state) {
- ...
- r.callback.onServiceStateChanged(new ServiceState(state));
- ...
- }
整个代码运行跨越了3个进程:phone进程、system进程和注册端进程。
注册和回调代码流程分析完毕。
诡异的内存泄漏
Android中可以用strictmode或者leakcanary检测app内存泄漏:strictmode可以一般设置成activity在有内存问题的时候闪退,能大概确认是哪个activity有问题;leakcanary可以在虚拟机内存出问题的时候dump当前的内存(一般来说这时手机会卡一下,内存越大卡的时间越长),然后dump文件就可在pc端用工具(一般就是 Memory Analyzer ,简称MAT)做具体分析。使用PhoneStateListener的Activity碰到过闪退的问题,即有内存泄漏,而且是第二次启动同样Activity必现。PhoneStateListener的反注册代码是肯定写了的,那么为啥会闪退?写了个非常简单的demo问题照样是可以重现的,那么证明app端代码有问题的可能性是不大的。
想了几个小时后忽然发现原因了,注册PhoneStateListener的binder对象是保有在system进程的,虽然注册端进程反注册代码已经走了,system端的代码也走了。但是不同进程虚拟机是各不相干的。java垃圾回收gc是虚拟机控制的,虽然引用已经置空,但是在system进程垃圾回收代码运行前内存中的引用其实还是在的,只是在等待下次gc时才能回收掉。两个进程的不同步造成了system端还是有注册端的引用的,所以会有内存泄漏。
但是其实这种泄漏是假性的,不是真正的内存泄漏。在第二次启动相同activity之前,通过ddms手动强制触发system进程的gc,再次进入就不会有闪退或者是dump内存的现象。
或者采用监听广播的方式来实现:
IntentFilter intentFilterPhone = new IntentFilter(); intentFilterPhone.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED); if (mPhoneStateReceiver == null) { mPhoneStateReceiver = new PhoneStateReceiver(); } mPhoneStateReceiver.setOnCallStateChangeListener(new OnCallStateChangeListener() { @Override public void onCallStateChange() { } }); mContext.registerReceiver(mPhoneStateReceiver, intentFilterPhone);
public static class PhoneStateReceiver extends BroadcastReceiver { private OnCallStateChangeListener mOnCallStateChangeListener; public void setOnCallStateChangeListener(OnCallStateChangeListener listener) { mOnCallStateChangeListener = listener; } @Override public void onReceive(Context context, Intent intent) { if (mOnCallStateChangeListener != null) { mOnCallStateChangeListener.onCallStateChange(); } } }