PhoneStateListener

概述

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内存的现象。









  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Android Studio中设计界面显示信号强度的代码,可以通过以下步骤实现: 1. 在XML布局文件中添加一个TextView控件用于显示信号强度: ```xml <TextView android:id="@+id/signalStrengthTextView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Signal Strength: " android:textSize="16sp" /> ``` 2. 在Java代码中获取并更新信号强度的值,并将其显示在TextView上: ```java import android.telephony.PhoneStateListener; import android.telephony.SignalStrength; import android.telephony.TelephonyManager; import android.widget.TextView; public class MainActivity extends AppCompatActivity { private TextView signalStrengthTextView; private TelephonyManager telephonyManager; private PhoneStateListener phoneStateListener; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); signalStrengthTextView = findViewById(R.id.signalStrengthTextView); telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); phoneStateListener = new PhoneStateListener() { @Override public void onSignalStrengthsChanged(SignalStrength signalStrength) { super.onSignalStrengthsChanged(signalStrength); int signalDbm = signalStrength.getDbm(); signalStrengthTextView.setText("Signal Strength: " + signalDbm + " dBm"); } }; telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_SIGNAL_STRENGTHS); } @Override protected void onDestroy() { super.onDestroy(); telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE); } } ``` 3. 确保在AndroidManifest.xml文件中添加以下权限: ```xml <uses-permission android:name="android.permission.READ_PHONE_STATE" /> ``` 以上代码通过使用TelephonyManager和PhoneStateListener来监听信号强度的变化,并将其更新到TextView上显示。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值