概述
PhoneStateListener是给三方app监听通信状态变化的方法,基本使用如下:
TelephonyManager mTelephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
PhoneStateListener mPhoneStateListener = new PhoneStateListener(mSubId) {
@Override
public void onCallStateChanged(int state, String incomingNumber) {
...
}
};
首先要创建一个新的PhoneStateListener对象和获取TelephonyManager服务
mTelephonyManager.listen(
mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
mTelephonyManager.listen(
mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
然后使用TelephonyManager注册或者反注册PhoneStateListener,注意注册或者反注册用的是同一个方法,LISTEN_NONE是反注册,其余参数例如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());
}
5.0后多了几个构造函数,参数多了个subId,注意是subId,不是slotId(两者区别可以见我之前写过的文章
subId和slotid)。使用subid就可以区别要监听哪个phone了。
吐槽下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();
...
}
调用startOtherServices
private void startOtherServices() {
...
telephonyRegistry = new TelephonyRegistry(context);
ServiceManager.addService("telephony.registry", telephonyRegistry);
...
}
建立了名称为telephony.registry的服务
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;
TelephonyManager有一个sRegistry静态成员,它的类型是ITelephonyRegistry,是由aidl编译而成。用到aidl就表示进程间通信,TelephonyRegistry是在system进程,ITelephonyRegistry为了给TelephonyManager对象所属进程使用。
frameworks/base/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
if (sRegistry == null) {
sRegistry = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService(
"telephony.registry"));
}
sRegistry在TelephonyManager构造方法中初始化,也即通过进程间通信获取到TelephonyRegistry服务。
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);
}
转到listen方法:
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());
}
}
注册流程:首先检查权限,如无权限就退出;然后检测该对象是否已经注册,没有注册的话通过callback参数建立Record对象r,然后将r加入到mRecords中;最后notifyNow为true且subid有效的话的话,回调注册进程对应的方法如onServiceStateChanged等,所以正常情况下第一次注册注册端的回调方法就会马上会走一次。
反注册流程:就是从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);
...
}
GsmServiceStateTracker收到ril上报的消息后一路调用到phone对象的notifyServiceStateChanged方法
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);
...
}
mRegistry和TelephonyManager中的 sRegistry完全一样,就是TelephonyRegistry服务。
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));
...
}
TelephonyRegistry最终跨进程调用注册端实现的回调方法。
整个代码运行跨越了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内存的现象。