PhoneStateListener的使用及其造成的内存泄漏问题分析(转自http://blog.csdn.net/firedancer0089/article/details/60121128)

概述

PhoneStateListener是给三方app监听通信状态变化的方法,基本使用如下:

  1. TelephonyManager  mTelephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);  
  2.   
  3. PhoneStateListener mPhoneStateListener = new PhoneStateListener(mSubId) {  
  4.             @Override  
  5.             public void onCallStateChanged(int state, String incomingNumber) {  
  6.                ...  
  7.             }  
  8. };  
首先要创建一个新的PhoneStateListener对象和获取TelephonyManager服务
  1. mTelephonyManager.listen(  
  2.         mPhoneStateListener, PhoneStateListener.LISTEN_NONE);  
  3.   
  4. mTelephonyManager.listen(  
  5.         mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);  
然后使用TelephonyManager注册或者反注册PhoneStateListener,注意注册或者反注册用的是同一个方法,LISTEN_NONE是反注册,其余参数例如LISTEN_CALL_STATE是注册相应事件监听,如需监听多个事件可以用‘|’,例如:
  1. mTelephonyManager.listenmPhoneStateListener   
  2.                        PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR  
  3.                        | PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR);  

监听的事件有多个,监听要实现的回调方法也相应的有多个,例如 PhoneStateListener.LISTEN_CALL_STATE对应 onCallStateChanged(int state, String incomingNumber) 。具体可参见Android sdk。

多卡情况

Android5.0之前是单SIM卡,5.0之后加入了多SIM卡机制,PhoneStateListener相应的也可以单独监听某个Phone对象了

  1. /** 
  2.   * Create a PhoneStateListener for the Phone using the specified subscription. 
  3.   * This class requires Looper.myLooper() not return null. To supply your 
  4.   * own non-null Looper use PhoneStateListener(int subId, Looper looper) below. 
  5.   * @hide 
  6.   */  
  7.  public PhoneStateListener(int subId) {  
  8.      this(subId, Looper.myLooper());  
  9.  }  
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

  1. private void run() {  
  2.     ...  
  3.     startOtherServices();  
  4.     ...  
  5. }  
调用startOtherServices
  1. private void startOtherServices() {  
  2.         ...  
  3.         telephonyRegistry = new TelephonyRegistry(context);  
  4.         ServiceManager.addService("telephony.registry", telephonyRegistry);  
  5.         ...  
  6. }  
建立了名称为telephony.registry的服务

frameworks/base/services/core/java/com/android/server/TelephonyRegistry.java

  1.    TelephonyRegistry(Context context) {  
  2.         CellLocation  location = CellLocation.getEmpty();  
  3.   
  4.         mContext = context;  
  5.         mBatteryStats = BatteryStatsService.getService();  
  6. //        mConnectedApns = new ArrayList<String>();  
  7.   
  8.         int numPhones = TelephonyManager.getDefault().getPhoneCount();  
  9.   
  10.         if (DBG) log("TelephonyRegistor: ctor numPhones=" + numPhones);  
  11.         mNumPhones = numPhones;  
  12.         mConnectedApns = (ArrayList<String>[]) new ArrayList[numPhones];  
  13.         mCallState = new int[numPhones];  
  14.         ...  
  15.         mPreciseDataConnectionState = new PreciseDataConnectionState[numPhones];  
  16.   
  17.         for (int i = 0; i < numPhones; i++) {  
  18.             mConnectedApns[i] = new ArrayList<String>();  
  19.             mCallState[i] =  TelephonyManager.CALL_STATE_IDLE;  
  20.             ...  
  21.             mPreciseDataConnectionState[i] =  new PreciseDataConnectionState();  
  22.         }  
  23.         ...  
  24.     }  

TelephonyRegistry构造函数中先获取当前手机的phone对象数量,通俗的说也就是获取当前手机支持SIM卡的数量。依据numPhones建立相应的成员数组,例如mCallState就是用于通话状态,创建完毕后赋予初始值。

2.TelephonyManager获取注册服务

frameworks/base/telephony/java/android/telephony/TelephonyManager.java

  1. private static ITelephonyRegistry sRegistry;  
TelephonyManager有一个sRegistry静态成员,它的类型是ITelephonyRegistry,是由aidl编译而成。用到aidl就表示进程间通信,TelephonyRegistry是在system进程,ITelephonyRegistry为了给TelephonyManager对象所属进程使用。

frameworks/base/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl

  1. if (sRegistry == null) {  
  2.      sRegistry = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService(  
  3.              "telephony.registry"));  
  4.  }  
sRegistry在TelephonyManager构造方法中初始化,也即通过进程间通信获取到TelephonyRegistry服务。

3.TelephonyManager Listen方法分析

  1. public void listen(PhoneStateListener listener, int events) {  
  2.     if (mContext == nullreturn;  
  3.     try {  
  4.         Boolean notifyNow = (getITelephony() != null);  
  5.         sRegistry.listenForSubscriber(listener.mSubId, getOpPackageName(),  
  6.                 listener.callback, events, notifyNow);  
  7.     } catch (RemoteException ex) {  
  8.         // system process dead  
  9.     } catch (NullPointerException ex) {  
  10.         // system process dead  
  11.     }  
  12. }  

getITelephony是判断phone服务是否存在的,phone服务是在com.android.phone进程中的,phone服务实现代码在packages/services/Telephony/src/com/android/phone/PhoneInterfaceManager.java。该服务没有建立的话证明通信系统没有初始化完毕或者有问题,所以notifyNow参数设置为true是没有意义的。注意这里传递的listener.callback定义在PhoneStateListener中

  1. IPhoneStateListener callback = new IPhoneStateListener.Stub() {...}  

IPhoneStateListener是一个aidl文件,TelephonyRegistry服务通过它才能实现对客户端回调方法的调用。

调用listenForSubscriber,这个进程间调用带代码又会跑到system进程中的TelephonyRegistry服务。

  1. public void listenForSubscriber(int subId, String pkgForDebug, IPhoneStateListener callback,  
  2.            int events, boolean notifyNow) {  
  3.        listen(pkgForDebug, callback, events, notifyNow, subId);  
  4.    }  
转到listen方法:
  1. private void listen(String callingPackage, IPhoneStateListener callback, int events,  
  2.         boolean notifyNow, int subId) {  
  3.   ...  
  4.   if (events != PhoneStateListener.LISTEN_NONE) {  
  5.         /* Checks permission and throws Security exception */  
  6.         checkListenerPermission(events);  
  7.   
  8.         if ((events & ENFORCE_PHONE_STATE_PERMISSION_MASK) != 0) {  
  9.             try {  
  10.                 mContext.enforceCallingOrSelfPermission(  
  11.                         android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, null);  
  12.                 // SKIP checking for run-time permission since caller or self has PRIVILEGED  
  13.                 // permission  
  14.             } catch (SecurityException e) {  
  15.                 if (mAppOps.noteOp(AppOpsManager.OP_READ_PHONE_STATE, Binder.getCallingUid(),  
  16.                         callingPackage) != AppOpsManager.MODE_ALLOWED) {  
  17.                     return;  
  18.                 }  
  19.             }  
  20.         }  
  21.   
  22.         synchronized (mRecords) {  
  23.             // register  
  24.             Record r;  
  25.             find_and_add: {  
  26.                 IBinder b = callback.asBinder();  
  27.                 final int N = mRecords.size();  
  28.                 for (int i = 0; i < N; i++) {  
  29.                     r = mRecords.get(i);  
  30.                     if (b == r.binder) {  
  31.                         break find_and_add;  
  32.                     }  
  33.                 }  
  34.                 r = new Record();  
  35.                 r.binder = b;  
  36.                 mRecords.add(r);  
  37.                 if (DBG) log("listen: add new record");  
  38.             }  
  39.   
  40.             r.callback = callback;  
  41.             r.callingPackage = callingPackage;  
  42.             r.callerUserId = callerUserId;  
  43.             boolean isPhoneStateEvent = (events & (CHECK_PHONE_STATE_PERMISSION_MASK  
  44.                     | ENFORCE_PHONE_STATE_PERMISSION_MASK)) != 0;  
  45.             r.canReadPhoneState = isPhoneStateEvent && canReadPhoneState(callingPackage);  
  46.             // Legacy applications pass SubscriptionManager.DEFAULT_SUB_ID,  
  47.             // force all illegal subId to SubscriptionManager.DEFAULT_SUB_ID  
  48.             if (!SubscriptionManager.isValidSubscriptionId(subId)) {  
  49.                 r.subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;  
  50.              } else {//APP specify subID  
  51.                 r.subId = subId;  
  52.             }  
  53.             r.phoneId = SubscriptionManager.getPhoneId(r.subId);  
  54.   
  55.             int phoneId = r.phoneId;  
  56.             r.events = events;  
  57.             if (DBG) {  
  58.                 log("listen:  Register r=" + r + " r.subId=" + r.subId + " phoneId=" + phoneId);  
  59.             }  
  60.             if (VDBG) toStringLogSSC("listen");  
  61.             if (notifyNow && validatePhoneId(phoneId)) {  
  62.                 if ((events & PhoneStateListener.LISTEN_SERVICE_STATE) != 0) {  
  63.                     try {  
  64.                         if (VDBG) log("listen: call onSSC state=" + mServiceState[phoneId]);  
  65.                         r.callback.onServiceStateChanged(  
  66.                                 new ServiceState(mServiceState[phoneId]));  
  67.                     } catch (RemoteException ex) {  
  68.                         remove(r.binder);  
  69.                     }  
  70.                 }  
  71.                 ...  
  72.             }  
  73.         }  
  74.     } else {  
  75.         if(DBG) log("listen: Unregister");  
  76.         remove(callback.asBinder());  
  77.     }  
  78. }  
注册流程:首先检查权限,如无权限就退出;然后检测该对象是否已经注册,没有注册的话通过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

  1. case EVENT_POLL_STATE_REGISTRATION:  
  2.           case EVENT_POLL_STATE_GPRS:  
  3.           case EVENT_POLL_STATE_OPERATOR:  
  4.           case EVENT_POLL_STATE_NETWORK_SELECTION_MODE:  
  5.               ...  
  6.               ar = (AsyncResult) msg.obj;  
  7.               handlePollStateResult(msg.what, ar);  
  1. protected void handlePollStateResult (int what, AsyncResult ar) {  
  2.      ...  
  3.      pollStateDone();  
  4.      ...  
  5. }  
  1. private void pollStateDone() {  
  2.      ...  
  3.      mPhone.notifyServiceStateChanged(mSS);  
  4.      ...  
  5.  }  
GsmServiceStateTracker收到ril上报的消息后一路调用到phone对象的notifyServiceStateChanged方法

 frameworks/opt/telephony/src/java/com/android/internal/telephony/gsm/GsmPhone.java

  1. /*package*/ void  
  2. notifyServiceStateChanged(ServiceState ss) {  
  3.     super.notifyServiceStateChangedP(ss);  
  4. }     
调用基类方法

frameworks/opt/telephony/src/java/com/android/internal/telephony/PhoneBase.java

  1. protected void notifyServiceStateChangedP(ServiceState ss) {  
  2.      ...  
  3.      mNotifier.notifyServiceState(this);  
  4.      ...  
  5.  }  

mNotifier类型是PhoneNotifier,该类是个interface,实现类是DefaultPhoneNotifier。

frameworks/opt/telephony/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java
  1. public void notifyServiceState(Phone sender) {  
  2.        ...  
  3.        mRegistry.notifyServiceStateForPhoneId(phoneId, subId, ss);  
  4.        ...  
  5. }  
mRegistry和TelephonyManager中的 sRegistry完全一样,就是TelephonyRegistry服务。

frameworks/base/services/core/java/com/android/server/TelephonyRegistry.java

  1. public void notifyServiceStateForPhoneId(int phoneId, int subId, ServiceState state) {  
  2.     ...  
  3.     r.callback.onServiceStateChanged(new ServiceState(state));  
  4.     ...  
  5. }  
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内存的现象。

或者采用监听广播的方式来实现:

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();
        }
    }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值