IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android Q 基站刷新接口源码分析 & 适配双卡手机基站刷新逻辑 -> 正文阅读

[移动开发]Android Q 基站刷新接口源码分析 & 适配双卡手机基站刷新逻辑

一、获取基站信息的两个关键方法

// 获取所有的基站信息列表
// 在targetSDK < Q时,主动请求基站信息刷新并返回,
// >Q 需要调用下面的方法请求刷新
public List<CellInfo> getAllCellInfo() 

// Android Q 新增,请求基站刷新
public void requestCellInfoUpdate(
            @NonNull @CallbackExecutor Executor executor, @NonNull CellInfoCallback callback) 

getAllCellInfo调用流程总结

1.通过TelephoneyManager.getAllCellInfo方法获取所有基站信息。

2.该方法会调用到TelephonyManager在系统层的对应接口类PhoneInterfaceManager的getAllCellInfo。

3.PhoneInterfaceManager.getAllCellInfo中首先判断是否有位置权限,如果没有权限则根据情况抛出异常或者返回空。

4.PhoneInterfaceManager.getAllCellInfo继续判断如果当前targetSdk > ,循环Phone列表,调用每一个Phone.getAllCellInfo(),最终调用ServiceStateTracker的成员变量mLastCellInfoList,返回缓存基站信息。否则,循环当前的Phone列表,分别调用Phone.requestCellInfoUpdate方法

5.Phone.requestCellInfoUpdate方法最终的响应类为ServiceStateTracker.requestAllCellInfo

6.ServiceStateTracker.requestAllCellInfo中首先判断当前刷新时间的间隔,如果刷新时间间隔小于一定阈值,则直接返回缓存结果,不再刷新。刷新时间间隔为(亮屏 & (未连接wifi || 充电中) = 2000ms,其它10000ms)。

7.如果符合间隔,则调用和硬件对接的接口,刷新基站信息,同时通过handler等待结果回调,并设置超时时间2000ms。

8.如果超时或者刷新接口回调刷新结果,则回调基站信息,如果超时或者刷新异常,则返回空基站信息。

9.上4中,最终所有的Phone刷新结束,组装所有Phone的基站信息结果并返回。

具体源码分析见下第三部分

requestCellInfoUpdate 流程总结

1.通过TelephoneyManager.requestCellInfoUpdate方法请求刷新基站信息并等待回调结果,TelephoneyManager实现中,调用系统层的requestCellInfoUpdate会传入当前TelephonyManager对应的subId,可以理解为对应的手机卡槽中的某一张卡。

2.该方法会调用到TelephonyManager在系统层的对应接口类PhoneInterfaceManager的requestCellInfoUpdate,并最终调用到requestCellInfoUpdateInternal,该方法首先判断是否有位置权限,如果没有权限则根据情况抛出异常或者返回空。其次,通过subId获取到Phone对象,并调用Phone.requestCellInfoUpdate。

3.其余步骤同上getAllCellInfo中5~7。

4.最终根据刷新基站信息结果,如果异常,则回调onError,成功回调onCellInfo。

具体源码分析见下第四部分

问题

根据如上分析,当targetSdk > Q时,系统不会再主动刷新基站信息,需要我们调用requestCellInfoUpdate主动刷新。

但根据源码分析,requestCellInfoUpdate刷新时和 getAllCellInfo & targetSdk < Q时,刷新逻辑存在差异。requestCellInfoUpdate刷新时,因传入了subId,只会刷新subId对应的Phone对象,而getAllCellInfo刷新时,会调用所有Phone的刷新基站信息接口。

按照该源码表现,requestCellInfoUpdate仅刷新了一个卡的基站信息,对于双卡手机,差异如下:

1)requestCellInfoUpdate回调的onCellInfo方法中,基站列表信息少于getAllCellInfo所获取基站信息。

2)requestCellInfoUpdate刷新时,仅刷新了单卡的基站信息,如果调用requestCellInfoUpdate刷新基站后,立即通过getAllCellInfo获取,会发现部分基站信息的刷新为缓存时间。

二、双卡手机适配 Android Q requestCellInfoUpdate接口

根据以上分析,核心问题是如何找到第二张卡以及对应的TelephonyManager对象,具体逻辑如下:

SubscriptionManager subscriptionManager = (SubscriptionManager) mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
// 0 对应卡槽0 所对应的系统中的subId,1 对应卡槽1
int[] subIds0 = subscriptionManager.getSubscriptionIds(0);
int[] subIds1 = subscriptionManager.getSubscriptionIds(1);
// 判断是否有效,如果有效,取返回的数组中的第一位,为subId
int subId0 = subIds0 != null && subIds0.length > 0 ? subIds0[0] : SubscriptionManager.INVALID_SUBSCRIPTION_ID;
int subId1 = subIds1 != null && subIds1.length > 0 ? subIds1[0] : SubscriptionManager.INVALID_SUBSCRIPTION_ID;

// 获取系统的TelphoneManager对象 系统的telehonyM
TelephonyManager telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);

// 卡槽0 对应的TelephonyManager
TelephonyManager tm0 = telephonyManager.createForSubscriptionId(subId0);
// 卡槽1 对应的TelephonyManager
TelephonyManager tm1 = telephonyManager.createForSubscriptionId(subId1);
//.... 调用对应的requestCellInfoUpdate即可

根据如上逻辑,即可达到刷新双卡基站信息的逻辑,通过卡槽获取subId,根据subId创建对应的TelephoneManager。

在这里存在一个疑问,通过context获取系统的TelephonyManager和哪个卡槽对应?这个是不一定的,取决于系统的设置。可以通过subscriptionManager.getDefaultSubscriptionId获取当前默认的subId,和卡槽对应的subId比对可知。

三、getAllCellInfo方法源码流程

源码总结:具体细分见下:

应用层

应用层调用getAllCellInfo接口,具体逻辑实现

  public List<CellInfo> getAllCellInfo() {
        try {
            // Binder机制,会调用到系统进程
            ITelephony telephony = getITelephony();
            if (telephony == null)
                return null;
            // 通过跨进程访问系统的Phone相关服务
            return telephony.getAllCellInfo(getOpPackageName(), getAttributionTag());
        } catch (RemoteException ex) {
        } catch (NullPointerException ex) {
        }
        return null;
    }

获取系统的基站信息,需要和系统的进程通信,那么根据跨进程相关的aidl逻辑,系统进程肯定存在一个响应的接口,该接口为packages/services/Telephony/src/com/android/phone/PhoneInterfaceManager.java

系统层

PhoneInterfaceManager.java

    @Override
    public List<CellInfo> getAllCellInfo(String callingPackage, String callingFeatureId) {
        mApp.getSystemService(AppOpsManager.class)
                .checkPackage(Binder.getCallingUid(), callingPackage);
        // 检查权限,是否允许获取基站信息,如果不允许,抛出异常或者返回空的基站信息列表
        LocationAccessPolicy.LocationPermissionResult locationResult =
                LocationAccessPolicy.checkLocationPermission(mApp,
                        new LocationAccessPolicy.LocationPermissionQuery.Builder()
                                .setCallingPackage(callingPackage)
                                .setCallingFeatureId(callingFeatureId)
                                .setCallingPid(Binder.getCallingPid())
                                .setCallingUid(Binder.getCallingUid())
                                .setMethod("getAllCellInfo")
                                .setMinSdkVersionForCoarse(Build.VERSION_CODES.BASE)
                                .setMinSdkVersionForFine(Build.VERSION_CODES.Q)
                                .build());
        switch (locationResult) {
            case DENIED_HARD:
                throw new SecurityException("Not allowed to access cell info");
            case DENIED_SOFT:
                return new ArrayList<>();
        }
        // 判断当前targetSdk版本,如果>=Q,则会获取缓存位置
        final int targetSdk = TelephonyPermissions.getTargetSdk(mApp, callingPackage);
        if (targetSdk >= android.os.Build.VERSION_CODES.Q) {
            // 返回缓存的基站信息,最终返回ServiceStateTracker.java的成员变量mLastCellInfoList;
            return getCachedCellInfo();
        }
      
        if (DBG_LOC) log("getAllCellInfo: is active user");
        // 如果当前targetSdk版本 < Q,则刷新基站信息,返回基站信息
        WorkSource workSource = getWorkSource(Binder.getCallingUid());
        final long identity = Binder.clearCallingIdentity();
        try {
            List<CellInfo> cellInfos = new ArrayList<CellInfo>();
            for (Phone phone : PhoneFactory.getPhones()) {
                final List<CellInfo> info = (List<CellInfo>) sendRequest(
                        CMD_GET_ALL_CELL_INFO, null, phone, workSource);
                if (info != null) cellInfos.addAll(info);
            }
            return cellInfos;
        } finally {
            Binder.restoreCallingIdentity(identity);
        }
    }

检查权限,如果权限不通过,则返回空

    /** Check if location permissions have been granted */
    public static LocationPermissionResult checkLocationPermission(
            Context context, LocationPermissionQuery query) {
        // Always allow the phone process, system server, and network stack to access location.
        // This avoid breaking legacy code that rely on public-facing APIs to access cell location,
        // and it doesn't create an info leak risk because the cell location is stored in the phone
        // process anyway, and the system server already has location access.
        if (query.callingUid == Process.PHONE_UID || query.callingUid == Process.SYSTEM_UID
                || query.callingUid == Process.NETWORK_STACK_UID
                || query.callingUid == Process.ROOT_UID) {
            return LocationPermissionResult.ALLOWED;
        }

        // Check the system-wide requirements. If the location main switch is off or
        // the app's profile isn't in foreground, return a soft denial.
        if (!checkSystemLocationAccess(context, query.callingUid, query.callingPid)) {
            return LocationPermissionResult.DENIED_SOFT;
        }

        // Do the check for fine, then for coarse.
        if (query.minSdkVersionForFine < Integer.MAX_VALUE) {
            LocationPermissionResult resultForFine = checkAppLocationPermissionHelper(
                    context, query, Manifest.permission.ACCESS_FINE_LOCATION);
            if (resultForFine != null) {
                return resultForFine;
            }
        }

        if (query.minSdkVersionForCoarse < Integer.MAX_VALUE) {
            LocationPermissionResult resultForCoarse = checkAppLocationPermissionHelper(
                    context, query, Manifest.permission.ACCESS_COARSE_LOCATION);
            if (resultForCoarse != null) {
                return resultForCoarse;
            }
        }

        // At this point, we're out of location checks to do. If the app bypassed all the previous
        // ones due to the SDK backwards compatibility schemes, allow it access.
        return LocationPermissionResult.ALLOWED;
    }

请求基站刷新,并获取基站结果

  private @Nullable Object sendRequest(int command, Object argument, Integer subId, Phone phone,
            WorkSource workSource, long timeoutInMs) {
        // timeoutInMs 超时时间判断
        if (Looper.myLooper() == mMainThreadHandler.getLooper()) {
            throw new RuntimeException("This method will deadlock if called from the main thread.");
        }

        MainThreadRequest request = null;
        if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID && phone != null) {
            throw new IllegalArgumentException("subId and phone cannot both be specified!");
        } else if (phone != null) {
            request = new MainThreadRequest(argument, phone, workSource);
        } else {
            request = new MainThreadRequest(argument, subId, workSource);
        }

        Message msg = mMainThreadHandler.obtainMessage(command, request);
        msg.sendToTarget();


        synchronized (request) {
            if (timeoutInMs >= 0) {
                // Wait for at least timeoutInMs before returning null request result
                long now = SystemClock.elapsedRealtime();
                long deadline = now + timeoutInMs;
                while (request.result == null && now < deadline) {
                    try {
                        request.wait(deadline - now);
                    } catch (InterruptedException e) {
                        // Do nothing, go back and check if request is completed or timeout
                    } finally {
                        now = SystemClock.elapsedRealtime();
                    }
                }
            } else {
                // Wait for the request to complete
                // 等待结果返回
                while (request.result == null) {
                    try {
                        request.wait();
                    } catch (InterruptedException e) {
                        // Do nothing, go back and wait until the request is complete
                    }
                }
            }
        }
        if (request.result == null) {
            Log.wtf(LOG_TAG,
                    "sendRequest: Blocking command timed out. Something has gone terribly wrong.");
        }
        return request.result;
    }

通过handler响应事件处理处理

case CMD_GET_ALL_CELL_INFO:
    request = (MainThreadRequest) msg.obj;
    onCompleted = obtainMessage(EVENT_GET_ALL_CELL_INFO_DONE, request);
    // 刷新基站信息
    request.phone.requestCellInfoUpdate(request.workSource, onCompleted);
    break;
case EVENT_GET_ALL_CELL_INFO_DONE:
    ar = (AsyncResult) msg.obj;
    request = (MainThreadRequest) ar.userObj;
    // 如果结果返回,则停止线程等待,并返回结果
    request.result = (ar.exception == null && ar.result != null)
            ? ar.result : new ArrayList<CellInfo>();
    synchronized (request) {
        request.notifyAll();
    }
    break;

最终响应的类:ServiceStateTracker.java

https://cs.android.com/android/platform/superproject/+/master:frameworks/opt/telephony/src/java/com/android/internal/telephony/ServiceStateTracker.java;l=1271;drc=master?hl=zh-cn

   public void requestAllCellInfo(WorkSource workSource, Message rspMsg) {
        // .....
        synchronized (mPendingCellInfoRequests) {
            // 如果当前正在请求刷新基站信息,则保存回调信息,等待刷新结束后通知
            if (mIsPendingCellInfoRequest) {
                if (rspMsg != null) mPendingCellInfoRequests.add(rspMsg);
                return;
            }
            // 判断是否可以刷新,刷新时间间隔需 >  mCellInfoMinIntervalMs
            // 该间隔时间:亮屏 & (未连接wifi || 充电中) = 2000ms,其它10000ms
            final long curTime = SystemClock.elapsedRealtime();
            if ((curTime - mLastCellInfoReqTime) < mCellInfoMinIntervalMs) {
                if (rspMsg != null) {
                    // 如果小于这个时间,回调缓存的结果
                    AsyncResult.forMessage(rspMsg, mLastCellInfoList, null);
                    rspMsg.sendToTarget();
                }
                return;
            }
            // 添加当前回调对象,等待刷新结束后通知
            if (rspMsg != null) mPendingCellInfoRequests.add(rspMsg);
            // 最后刷新的时间
            mLastCellInfoReqTime = curTime;
            // 刷新中的标记
            mIsPendingCellInfoRequest = true;
            // 请求基站刷新,刷新成功后会回调EVENT_GET_CELL_INFO_LIST
            Message msg = obtainMessage(EVENT_GET_CELL_INFO_LIST);
            mCi.getCellInfoList(msg, workSource);
            // 超时判断,如果超时,则强制返回结果,2000ms
            sendMessageDelayed(
                    obtainMessage(EVENT_GET_CELL_INFO_LIST), CELL_INFO_LIST_QUERY_TIMEOUT);
        }
    }

刷新结束后,基站信息回调:

            case EVENT_GET_CELL_INFO_LIST: // fallthrough
            case EVENT_UNSOL_CELL_INFO_LIST: {
                List<CellInfo> cellInfo = null;
                Throwable ex = null;
                if (msg.obj != null) {
                    ar = (AsyncResult) msg.obj;
                    if (ar.exception != null) {
                        log("EVENT_GET_CELL_INFO_LIST: error ret null, e=" + ar.exception);
                        // 存在异常,标记异常
                        ex = ar.exception;
                    } else if (ar.result == null) {
                        loge("Invalid CellInfo result");
                    } else {
                        // 保存最新的基站 
                        cellInfo = (List<CellInfo>) ar.result;
                        updateOperatorNameForCellInfo(cellInfo);
                        mLastCellInfoList = cellInfo;
                        mPhone.notifyCellInfo(cellInfo);
                        if (VDBG) {
                            log("CELL_INFO_LIST: size=" + cellInfo.size() + " list=" + cellInfo);
                        }
                    }
                } else {
                    synchronized (mPendingCellInfoRequests) {
                        // If we receive an empty message, it's probably a timeout; if there is no
                        // pending request, drop it.
                        if (!mIsPendingCellInfoRequest) break;
                        // 判断是否是超时
                        final long curTime = SystemClock.elapsedRealtime();
                        if ((curTime - mLastCellInfoReqTime) <  CELL_INFO_LIST_QUERY_TIMEOUT) {
                            break;
                        }
                        // We've received a legitimate timeout, so something has gone terribly
                        // wrong.
                        loge("Timeout waiting for CellInfo; (everybody panic)!");
                        // 如果是超时,则将lastCellInfo置为空
                        mLastCellInfoList = null;
                        // Since the timeout is applicable, fall through and update all synchronous
                        // callers with the failure.
                    }
                }
                synchronized (mPendingCellInfoRequests) {
                    // If we have pending requests, then service them. Note that in case of a
                    // timeout, we send null responses back to the callers.
                    if (mIsPendingCellInfoRequest) {
                        // 将最新的基站信息回调给上层业务
                        mIsPendingCellInfoRequest = false;
                        for (Message m : mPendingCellInfoRequests) {
                            AsyncResult.forMessage(m, cellInfo, ex);
                            m.sendToTarget();
                        }
                        mPendingCellInfoRequests.clear();
                    }
                }
                break;
            }

四、requestCellInfoUpdate源码流程分析

应用层:

应用层调用requestCellInfoUpdate接口,具体逻辑实现

public void requestCellInfoUpdate(
            @NonNull @CallbackExecutor Executor executor, @NonNull CellInfoCallback callback) {
        try {
            ITelephony telephony = getITelephony();
            if (telephony == null) return;
            telephony.requestCellInfoUpdate(
                    getSubId(), // 获取当前的subId
                    new ICellInfoCallback.Stub() { // 监听回调
                        @Override
                        public void onCellInfo(List<CellInfo> cellInfo) {
                            final long identity = Binder.clearCallingIdentity();
                            try {
                                executor.execute(() -> callback.onCellInfo(cellInfo));
                            } finally {
                                Binder.restoreCallingIdentity(identity);
                            }
                        }

                        @Override
                        public void onError(int errorCode, String exceptionName, String message) {
                            final long identity = Binder.clearCallingIdentity();
                            try {
                                executor.execute(() -> callback.onError(
                                        errorCode,
                                        createThrowableByClassName(exceptionName, message)));
                            } finally {
                                Binder.restoreCallingIdentity(identity);
                            }
                        }
                    }, getOpPackageName() 
                    , getAttributionTag());
        } catch (RemoteException ex) {
        }
    }

系统层

PhoneInterfaceManager.java

    private void requestCellInfoUpdateInternal(int subId, ICellInfoCallback cb,
            String callingPackage, String callingFeatureId, WorkSource workSource) {
        mApp.getSystemService(AppOpsManager.class)
                .checkPackage(Binder.getCallingUid(), callingPackage);
        // 位置权限判断
        LocationAccessPolicy.LocationPermissionResult locationResult =
                LocationAccessPolicy.checkLocationPermission(mApp,
                        new LocationAccessPolicy.LocationPermissionQuery.Builder()
                                .setCallingPackage(callingPackage)
                                .setCallingFeatureId(callingFeatureId)
                                .setCallingPid(Binder.getCallingPid())
                                .setCallingUid(Binder.getCallingUid())
                                .setMethod("requestCellInfoUpdate")
                                .setMinSdkVersionForCoarse(Build.VERSION_CODES.BASE)
                                .setMinSdkVersionForFine(Build.VERSION_CODES.BASE)
                                .build());
        switch (locationResult) {
            case DENIED_HARD:
                if (TelephonyPermissions
                        .getTargetSdk(mApp, callingPackage) < Build.VERSION_CODES.Q) {
                    // Safetynet logging for b/154934934
                    EventLog.writeEvent(0x534e4554, "154934934", Binder.getCallingUid());
                }
                throw new SecurityException("Not allowed to access cell info");
            case DENIED_SOFT:
                if (TelephonyPermissions
                        .getTargetSdk(mApp, callingPackage) < Build.VERSION_CODES.Q) {
                    // Safetynet logging for b/154934934
                    EventLog.writeEvent(0x534e4554, "154934934", Binder.getCallingUid());
                }
                try {
                    cb.onCellInfo(new ArrayList<CellInfo>());
                } catch (RemoteException re) {
                    // Drop without consequences
                }
                return;
        }

        // 获取subId对应的Phone对象
        final Phone phone = getPhoneFromSubId(subId);
        if (phone == null) throw new IllegalArgumentException("Invalid Subscription Id: " + subId);
        // 发送message,请求基站刷新
        sendRequestAsync(CMD_REQUEST_CELL_INFO_UPDATE, cb, phone, workSource);
    }

Handler处理逻辑为

case CMD_REQUEST_CELL_INFO_UPDATE:
    request = (MainThreadRequest) msg.obj;
    // 请求基站刷新
    request.phone.requestCellInfoUpdate(request.workSource,
            obtainMessage(EVENT_REQUEST_CELL_INFO_UPDATE_DONE, request));
    break;
case EVENT_REQUEST_CELL_INFO_UPDATE_DONE:
    // 回调刷新结果
    ar = (AsyncResult) msg.obj;
    request = (MainThreadRequest) ar.userObj;
    ICellInfoCallback cb = (ICellInfoCallback) request.argument;
    try {
        if (ar.exception != null) {
            Log.e(LOG_TAG, "Exception retrieving CellInfo=" + ar.exception);
            cb.onError(
                    TelephonyManager.CellInfoCallback.ERROR_MODEM_ERROR,
                    ar.exception.getClass().getName(),
                    ar.exception.toString());
        } else if (ar.result == null) {
            Log.w(LOG_TAG, "Timeout Waiting for CellInfo!");
            cb.onError(TelephonyManager.CellInfoCallback.ERROR_TIMEOUT, null, null);
        } else {
            // use the result as returned
            cb.onCellInfo((List<CellInfo>) ar.result);
        }
    } catch (RemoteException re) {
        Log.w(LOG_TAG, "Discarded CellInfo due to Callback RemoteException");
    }
    break;

刷新基站信息逻辑与第三部分getAllCellInfo逻辑一致,不再分析。

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-11-16 18:57:11  更:2021-11-16 18:58:40 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 3:43:22-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码