电话状态权限及IMEI获取流程源码分析

2023-05-16

IMEI是设备唯一性的一个重要指标,这篇文章对IMEI获取做一些分析,以达到以下两个目的:

1、梳理Android源码中获取IMEI流程

2、理解获取IMEI时,源码中权限调用流程

备注:以下源码分析,针对的是Android 6.0.1源码

在Android代码中,我们需要获取设备的IMEI,只需调用下面方法

 

TelephonyManager telephonyMgr = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
imei = telephonyMgr.getDeviceId();


接下来就看看源码中是怎样获取IMEI的。

 

/**
 * Returns the unique device ID, for example, the IMEI for GSM and the MEID
 * or ESN for CDMA phones. Return null if device ID is not available.
 *
 * <p>Requires Permission:
 *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
 */
public String getDeviceId() {
    try {
        ITelephony telephony = getITelephony();
        if (telephony == null)
            return null;
        return telephony.getDeviceId(mContext.getOpPackageName());
    } catch (RemoteException ex) {
        ......
    }
}


在TelephonyManager中,可以看到是先获取ITelephony对象,然后在通过该Interface的getDeviceid()方法来获取IMEI的。ITelephony是一个aidl的接口,所以接下来我们需要找到实现这个接口的对应实现类

/**
 * Implementation of the ITelephony interface.
 */
public class PhoneInterfaceManager extends ITelephony.Stub {
  
    ......
  
   /**
     * Returns the unique device ID of phone, for example, the IMEI for
     * GSM and the MEID for CDMA phones. Return null if device ID is not available.
     *
     * <p>Requires Permission:
     *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
     */
    @Override
    public String getDeviceId(String callingPackage) {
        if (!canReadPhoneState(callingPackage, "getDeviceId")) {
            return null;
        }
 
        final Phone phone = PhoneFactory.getPhone(0);
        if (phone != null) {
            return phone.getDeviceId();
        } else {
            return null;
        }
    }
  
    ......
}


        首先,通过上面类的声明可以看到,PhoneInterfaceManager这个类实现了ITelephony.stub接口,因此就是这个接口的对应的实现类。接下来我们看看对应实现的getDeviceId()方法。

        这个方法首先通过canReadPhoneState()方法来判断权限是否通过,如果没有权限直接就返回null了。权限通过之后才会去获取deviceId。

        我们这次分析的目的,一个是权限流程分析,一个是IMEI获取流程分析,这里我们通过canReadPhoneState()方法看看权限相关的判断流程,然后在接着看IMEI的获取流程。

1、权限判断流程

     通过上面canReadPhoneState()函数入口,我们看看具体的实现。

private boolean canReadPhoneState(String callingPackage, String message) {
    try {
        mApp.enforceCallingOrSelfPermission(
                android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, message);
 
        // SKIP checking for run-time permission since caller or self has PRIVILEDGED permission
        return true;
    } catch (SecurityException e) {
        mApp.enforceCallingOrSelfPermission(android.Manifest.permission.READ_PHONE_STATE,
                message);
    }
 
    if (mAppOps.noteOp(AppOpsManager.OP_READ_PHONE_STATE, Binder.getCallingUid(),
            callingPackage) != AppOpsManager.MODE_ALLOWED) {
        return false;
    }
 
    return true;
}


      该函数中,首先在try代码块中尝试获取READ_PRIVILEGED_PHONE_STATE权限,如果有这个权限就直接默认权限通过了。一般这个私有权限是系统的应用有,第三方应用都是没有的,所以会抛出安全异常,走到catch代码块中。

       在catch代码块,就是真正的去检查应用是否有对应需要的READ_PHONE_STATE权限了。接下来我们需要看看该方法的具体实现

class ContextImpl extends Context {
    ......
  
    @Override
    public void enforceCallingOrSelfPermission(
            String permission, String message) {
        enforce(permission,
                checkCallingOrSelfPermission(permission),
                true,
                Binder.getCallingUid(),
                message);
    }
  
    @Override
    public int checkCallingOrSelfPermission(String permission) {
        if (permission == null) {
            throw new IllegalArgumentException("permission is null");
        }
 
        return checkPermission(permission, Binder.getCallingPid(),
                Binder.getCallingUid());
    }
  
    @Override
    public int checkPermission(String permission, int pid, int uid) {
        if (permission == null) {
            throw new IllegalArgumentException("permission is null");
        }
 
        try {
            return ActivityManagerNative.getDefault().checkPermission(
                    permission, pid, uid);
        } catch (RemoteException e) {
            return PackageManager.PERMISSION_DENIED;
        }
    }
     
}
  
public abstract class ActivityManagerNative extends Binder implements IActivityManager {
    ......
  
    public int checkPermission(String permission, int pid, int uid)
            throws RemoteException {
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        data.writeInterfaceToken(IActivityManager.descriptor);
        data.writeString(permission);
        data.writeInt(pid);
        data.writeInt(uid);
        mRemote.transact(CHECK_PERMISSION_TRANSACTION, data, reply, 0);
        reply.readException();
        int res = reply.readInt();
        data.recycle();
        reply.recycle();
        return res;
    }
  
    ......
}


        从上面调用可以看到,首先是在ContextImpl中开始经过层层调用,在进程中最终调用到ActivityManagerNative中,通过Parcel传输相关数据,完成一次IPC操作来获取权限是否通过的结果。

        上述IPC操作,最终会调用到ActivityManagerService中完成,下面我们看看ActivityMangerService中的执行情况

public final class ActivityManagerService extends ActivityManagerNative
        implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {   
    ......
  
    /**
     * As the only public entry point for permissions checking, this method
     * can enforce the semantic that requesting a check on a null global
     * permission is automatically denied.  (Internally a null permission
     * string is used when calling {@link #checkComponentPermission} in cases
     * when only uid-based security is needed.)
     *
     * This can be called with or without the global lock held.
     */
    @Override
    public int checkPermission(String permission, int pid, int uid) {
        if (permission == null) {
            return PackageManager.PERMISSION_DENIED;
        }
        return checkComponentPermission(permission, pid, uid, -1, true);
    }
  
    /**
     * This can be called with or without the global lock held.
     */
    int checkComponentPermission(String permission, int pid, int uid,
            int owningUid, boolean exported) {
        if (pid == MY_PID) {
            return PackageManager.PERMISSION_GRANTED;
        }
        return ActivityManager.checkComponentPermission(permission, uid,
                owningUid, exported);
    }
     
}
  
public class ActivityManager {
    ......
  
    /** @hide */
    public static int checkComponentPermission(String permission, int uid,
            int owningUid, boolean exported) {
        // Root, system server get to do everything.
        final int appId = UserHandle.getAppId(uid);
        if (appId == Process.ROOT_UID || appId == Process.SYSTEM_UID) {
            return PackageManager.PERMISSION_GRANTED;
        }
        // Isolated processes don't get any permissions.
        if (UserHandle.isIsolated(uid)) {
            return PackageManager.PERMISSION_DENIED;
        }
        // If there is a uid that owns whatever is being accessed, it has
        // blanket access to it regardless of the permissions it requires.
        if (owningUid >= 0 && UserHandle.isSameApp(uid, owningUid)) {
            return PackageManager.PERMISSION_GRANTED;
        }
        // If the target is not exported, then nobody else can get to it.
        if (!exported) {
            /*
            RuntimeException here = new RuntimeException("here");
            here.fillInStackTrace();
            Slog.w(TAG, "Permission denied: checkComponentPermission() owningUid=" + owningUid,
                    here);
            */
            return PackageManager.PERMISSION_DENIED;
        }
        if (permission == null) {
            return PackageManager.PERMISSION_GRANTED;
        }
        try {
            return AppGlobals.getPackageManager()
                    .checkUidPermission(permission, uid);
        } catch (RemoteException e) {
            // Should never happen, but if it does... deny!
            Slog.e(TAG, "PackageManager is dead?!?", e);
        }
        return PackageManager.PERMISSION_DENIED;
    }
}


从上面调用可以看到,从ActivityManagerService的方法checkPermission()开始,最终会调用到ActivityManager的checkCompomentPermission()方法,最终完成权限的判断。

从ActivityManager的checkCompomentPermission()方法中一堆的if语句,其实也不难发现,权限的判断,主要还是和appId,uid,pid等参数相关,一般第三方app都是通过PackageManagerService来判断的。

下面是PMS中具体的判断逻辑,这里就不再进一步展开分析了,有兴趣的同学可以继续看看。

public class PackageManagerService extends IPackageManager.Stub {  
    ......
  
    @Override
    public int checkUidPermission(String permName, int uid) {
        final int userId = UserHandle.getUserId(uid);
 
        if (!sUserManager.exists(userId)) {
            return PackageManager.PERMISSION_DENIED;
        }
 
        synchronized (mPackages) {
            Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid));
            if (obj != null) {
                final SettingBase ps = (SettingBase) obj;
                final PermissionsState permissionsState = ps.getPermissionsState();
                if (permissionsState.hasPermission(permName, userId)) {
                    return PackageManager.PERMISSION_GRANTED;
                }
                // Special case: ACCESS_FINE_LOCATION permission includes ACCESS_COARSE_LOCATION
                if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && permissionsState
                        .hasPermission(Manifest.permission.ACCESS_FINE_LOCATION, userId)) {
                    return PackageManager.PERMISSION_GRANTED;
                }
            } else {
                ArraySet<String> perms = mSystemPermissions.get(uid);
                if (perms != null) {
                    if (perms.contains(permName)) {
                        return PackageManager.PERMISSION_GRANTED;
                    }
                    if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && perms
                            .contains(Manifest.permission.ACCESS_FINE_LOCATION)) {
                        return PackageManager.PERMISSION_GRANTED;
                    }
                }
            }
        }
 
        return PackageManager.PERMISSION_DENIED;
    }
}

2、IMEI获取流程

      下面我们回到前面PhoneInterfaceManager中的getDeviceId()方法,继续分析获取IMEI的流程。

/**
 * Implementation of the ITelephony interface.
 */
public class PhoneInterfaceManager extends ITelephony.Stub {
  
    ......
  
   /**
     * Returns the unique device ID of phone, for example, the IMEI for
     * GSM and the MEID for CDMA phones. Return null if device ID is not available.
     *
     * <p>Requires Permission:
     *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
     */
    @Override
    public String getDeviceId(String callingPackage) {
        if (!canReadPhoneState(callingPackage, "getDeviceId")) {
            return null;
        }
 
        final Phone phone = PhoneFactory.getPhone(0);
        if (phone != null) {
            return phone.getDeviceId();
        } else {
            return null;
        }
    }
  
    ......

从上面可以看到,首先是通过PhoneFactory拿到一个Phone对象,Phone是一个Interface,初步的实现类是PhoneBase。

public abstract class PhoneBase extends Handler implements Phone {
  
}


看到对应类的声明,其实也是一个虚类,在这个类中也没有实现getDeviceId()方法,因此需要继续看看PhoneBase的实现类,发现有这么几个,分别是

CDMAPhone
GSMPhone
ImsPhoneBase
SipPhoneBase

public class CDMAPhone extends PhoneBase {
    ......
  
    //returns MEID or ESN in CDMA
    @Override
    public String getDeviceId() {
        String id = getMeid();
        if ((id == null) || id.matches("^0*$")) {
            Rlog.d(LOG_TAG, "getDeviceId(): MEID is not initialized use ESN");
            id = getEsn();
        }
        return id;
    }
}
  
public class GSMPhone extends PhoneBase {
    ......
  
    @Override
    public String getDeviceId() {
        return mImei;
    }
}
  
abstract class ImsPhoneBase extends PhoneBase {
    ......
  
    @Override
    public String getDeviceId() {
        return null;
    }
}
  
abstract class SipPhoneBase extends PhoneBase {
    ......
  
    @Override
    public String getDeviceId() {
        return null;
    }
  
}

可以看出,GSMPhone应该是提供IMEI的类。下面我们具体看看在GSMPhone中,mImei这个全局变量是怎样赋值的。

 

public class GSMPhone extends PhoneBase {
    ......
  
    @Override
    public void handleMessage (Message msg) {
         ......
         switch (msg.what) {
            case EVENT_RADIO_AVAILABLE: {
                mCi.getBasebandVersion(
                        obtainMessage(EVENT_GET_BASEBAND_VERSION_DONE));
 
                mCi.getIMEI(obtainMessage(EVENT_GET_IMEI_DONE));
                mCi.getIMEISV(obtainMessage(EVENT_GET_IMEISV_DONE));
                mCi.getRadioCapability(obtainMessage(EVENT_GET_RADIO_CAPABILITY));
                startLceAfterRadioIsAvailable();
            }
            break;
            ......
  
            case EVENT_GET_IMEI_DONE:
                ar = (AsyncResult)msg.obj;
 
                if (ar.exception != null) {
                    break;
                }
 
                mImei = (String)ar.result;
            break;
            ......
    }
}

通过上面代码可以看到,在handleMessage()方法中,收到EVENT_GET_IMEI_DONE消息后,就得到了IMEI。进一步发现,上面有一行代码

             mCi.getIMEI(obtainMessage(EVENT_GET_IMEI_DONE));
发送了这个消息出去,于是我们继续看看前面的调用mCi是什么。
       mCi是GSMPhone基类PhoneBase中的一个全局变量,其类型为CommandsInterface,为一个Interface,接下来就看看这个Interface的实现,这里就举出所有实现类型了,实现这个Interface的是RIL这个类

 

public final class RIL extends BaseCommands implements CommandsInterface {
    ......
  
    @Override
    public void
    getIMEI(Message result) {
        RILRequest rr = RILRequest.obtain(RIL_REQUEST_GET_IMEI, result);
 
        if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
 
        send(rr);
    }
  
    private void
    send(RILRequest rr) {
        Message msg;
 
        if (mSocket == null) {
            rr.onError(RADIO_NOT_AVAILABLE, null);
            rr.release();
            return;
        }
 
        msg = mSender.obtainMessage(EVENT_SEND, rr);
 
        acquireWakeLock();
 
        msg.sendToTarget();
    }
}

看到上面代码,在RIL类中,将获取IMEI的消息进行一些包装,然后在send()方法中通过mSender对象发送出去了。mSender是什么了?其实也在RIL这个类中,是个内部类,叫RILSender

class RILSender extends Handler implements Runnable {
    ......
  
    @Override public void
    handleMessage(Message msg) {
         ......
         switch (msg.what) {
                case EVENT_SEND:
                    try {
                        LocalSocket s;
 
                        s = mSocket;
 
                        if (s == null) {
                            rr.onError(RADIO_NOT_AVAILABLE, null);
                            rr.release();
                            decrementWakeLock();
                            return;
                        }
 
                        synchronized (mRequestList) {
                            mRequestList.append(rr.mSerial, rr);
                        }
 
                        byte[] data;
 
                        data = rr.mParcel.marshall();
                        rr.mParcel.recycle();
                        rr.mParcel = null;
 
                        if (data.length > RIL_MAX_COMMAND_BYTES) {
                            throw new RuntimeException(
                                    "Parcel larger than max bytes allowed! "
                                                          + data.length);
                        }
 
                        // parcel length in big endian
                        dataLength[0] = dataLength[1] = 0;
                        dataLength[2] = (byte)((data.length >> 8) & 0xff);
                        dataLength[3] = (byte)((data.length) & 0xff);
 
                        //Rlog.v(RILJ_LOG_TAG, "writing packet: " + data.length + " bytes");
 
                        s.getOutputStream().write(dataLength);
                        s.getOutputStream().write(data);
                    } catch {
                        ......
                    }
    }
}

熟悉电话框架这块的同学应该比较了解,电话这块,其实也是一个RILSender和一个RILReceiver,分别用于发送和接收。两个分别运行在不同线程,全双工工通信,处理RILRequest和RILResponse。

这里不展开讲Android的电话框架,直接进入处理部分,看看上面发送的消息,是怎样被处理的。

/**
 * Call from RIL to us to make a RIL_REQUEST
 *
 * Must be completed with a call to RIL_onRequestComplete()
 *
 * RIL_onRequestComplete() may be called from any thread, before or after
 * this function returns.
 *
 * Will always be called from the same thread, so returning here implies
 * that the radio is ready to process another command (whether or not
 * the previous command has completed).
 */
static void
onRequest (int request, void *data, size_t datalen, RIL_Token t) {
    ......
  
    case RIL_REQUEST_GET_IMEI:
        p_response = NULL;
        err = at_send_command_numeric("AT+CGSN", &p_response);
 
        if (err < 0 || p_response->success == 0) {
            RIL_onRequestComplete(t, RIL_E_GENERIC_FAILURE, NULL, 0);
        } else {
            RIL_onRequestComplete(t, RIL_E_SUCCESS,
                p_response->p_intermediates->line, sizeof(char *));
        }
        at_response_free(p_response);
        break;
  
   ......
  
}
-------------------------------------------------------------------------------
int at_send_command_numeric (const char *command,
                                 ATResponse **pp_outResponse)
{
    int err;
    err = at_send_command_full (command, NUMERIC, NULL,
                                    NULL, 0, pp_outResponse);
    ......
    return err;
}
-------------------------------------------------------------------------------
static int at_send_command_full (const char *command, ATCommandType type,
                    const char *responsePrefix, const char *smspdu,
                    long long timeoutMsec, ATResponse **pp_outResponse)
{
    int err;
    ......
    err = at_send_command_full_nolock(command, type,
                    responsePrefix, smspdu,
                    timeoutMsec, pp_outResponse);
 
    ......
    return err;
}
-------------------------------------------------------------------------------
/**
 * Internal send_command implementation
 * Doesn't lock or call the timeout callback
 *
 * timeoutMsec == 0 means infinite timeout
 */
 
static int at_send_command_full_nolock (const char *command, ATCommandType type,
                    const char *responsePrefix, const char *smspdu,
                    long long timeoutMsec, ATResponse **pp_outResponse)
{
    int err = 0;
    ......
 
    err = writeline (command);
 
    s_type = type;
    s_responsePrefix = responsePrefix;
    s_smsPDU = smspdu;
    sp_response = at_response_new();
 
    while (sp_response->finalResponse == NULL && s_readerClosed == 0) {
        if (timeoutMsec != 0) {
            ......
        } else {
            err = pthread_cond_wait(&s_commandcond, &s_commandmutex);
        }
        ......
    }
 
    ......
    err = 0;
error:
    clearPendingCommand();
    return err;
}

上面代码比较多,主要是与硬件层打交道,将获取IMEI的命令写到硬件层,然后等待结果。这样子,通过写命令到硬件层,然后等待对应的IMEI数据被写到对应申请的内存地址后,就可以返回了。

接着底层通过相应的IPC通道将数据返回给调用进程,调用进程读取对应的数据,就拿到了IMEI了。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

电话状态权限及IMEI获取流程源码分析 的相关文章

  • VMware Ubuntu虚拟机忘记密码

    前言 xff1a 在VMware运行Ubuntu虚拟机时 xff0c 开机之后忘记密码怎么办 xff1f 环境 xff1a Ubuntu版本 xff1a ubuntu 16 04 6 server amd64 xff1b VMware版本
  • Ubuntu18.04安装matlabR2019A

    载安装包和破解文件 链接 https pan baidu com s 1X09GAchToEqyMRol3msGAA 密码 wak6 下载完成后解压 右击 iso镜像文件 xff0c 选择使用其他程序打开 选择磁盘映像挂载器 打开后会在桌面
  • 03-对抗样本攻击

    对抗样本攻击 Github xff1a https github com Gary11111 03 GAN 研究背景 尽管深度学习在很多计算机视觉领域的任务上表现出色 xff0c Szegedy第一次发现了深度神经网络在图像分类领域存在有意
  • 冒泡排序算法

    冒泡排序是非常好理解的 xff0c 以从小到大排序为例 xff0c 每一轮排序就找出未排序序列中最大值放在最后 设数组的长度为N xff1a xff08 1 xff09 比较前后相邻的二个数据 xff0c 如果前面数据大于后面的数据 xff
  • 将一个分支上的commit 转移到另一个分支上git cherry-pick <commit id>

    使用 cherry pick 根据git 文档 xff1a Apply the changes introduced by some existing commits 就是对已经存在的commit 进行apply 可以理解为再次提交 xff
  • Git 如何查看和修改用户名、邮箱

    用户名和邮箱地址是本地git客户端的一个变量 xff0c 不随git库而改变 每次commit都会用用户名和邮箱纪录 1 查看用户名和地址 git config user name git config user email 2 修改用户名
  • Android PendingIntent

    在Android中 xff0c 我们常常使用PendingIntent来表达一种 留待日后处理 的意思 从这个角度来说 xff0c PendingIntent可以被理解为一种特殊的异步处理机制 不过 xff0c 单就命名而言 xff0c P
  • .CR2格式文件怎么快速批量转换成JPG等格式

    打开需要转换的CR2文件夹 用FSViewer exe看图软件打开CR2文件 xff0c 然后再双击打开已打开的图片并选中所有需要转换的CR2文件 点击工具 选择批量转换选中的图像 若是右边窗口中没有文件 xff0c 则需要手动点击 全部添
  • 利用AlarmManager完成精准的轮询

    问题分析 想起轮询我们一般会想起利用Handler和Timer xff0c 然而AlarmManager相比于Handler和Timer有优势 xff0c 具体的分析我参考了一个大神的博客 xff1a 最近在做一个需求 xff1a 客户端按
  • Intellij IDEA junit 使用之org.junit不存在

    1 同理打开 xff1a File gt project Structure gt librabries gt 点击左上角 43 号 gt From Maven 2 输入 junit 4 12 3 确定即可 在build gradle文件中
  • Android studio显示你的主机中的软件中止了一个已建立的连接

    Android studio在run的时候显示你的主机中的软件中止了一个已建立的连接 出现的场景 是在run项目的时候出现了这样的 如下图 当我把热点关了就好了 再也不会出现类似的情况了
  • 设计模式、重构、编程规范等的经典书籍书籍推荐

    有关设计模式 重构 编程规范等的经典书籍很多 xff0c 有很多你应该已经听说过 甚至看过 今天 xff0c 我就结合我的经验 xff0c 对这些书籍进行一个整理和点评 你可以据此来选择适合你的书籍 xff0c 结合着专栏一块儿来学习 xf
  • Android打开或者关闭GPS

    打开和关闭gps 需要系统权限 android permission WRITE SECURE SETTINGS 打开或者关闭gps public void openGPS boolean open if Build VERSION SDK
  • 【Erro】安装homebrew报错curl: (7) Failed to connect to raw.githubusercontent.com port 443: Operation

    下载brew bin bash c 34 curl fsSL https raw githubusercontent com Homebrew install master install sh 34 出现这样的错误 xff1f curl
  • ChatGPT火爆科研圈,登上《Nature》《Science》正刊

    ChatGPT火出圈了 xff0c 几乎涉及到各行各业的每个领域 xff0c 科研圈更甚 Science 期刊主编H HOLDEN THORP发表关于ChatGPT的社论 xff1a ChatGPT is fun but not an au
  • 解决 Unable to determine application id: com.android.tools.idea.run.ApkProvisionException

    问题 xff1a Unable to determine application id com android tools idea run ApkProvisionException Error loading build artifac
  • Android SurfaceView预览变形完美解决方法

    这个问题百度上一搜一大把 xff0c 基本上都是说找到和SurfaceView的比例相近的camera预览尺寸 xff0c 但是发现预览时候还是差了点意思 xff0c 具体看下面这个回调就知道是为什么了 64 Override public
  • Camera2 教程 一概览

    从 Android 5 0 开始 xff0c Google 引入了一套全新的相机框架 Camera2 xff08 android hardware camera2 xff09 并且废弃了旧的相机框架 Camera1 xff08 androi
  • Camera2 二开关相机

    1 创建相机项目 正如前所说的 xff0c 我们会开发一个具有完整相机功能的应用程序 xff0c 所以第一步要做的就是创建一个相机项目 xff0c 这里我用 AS 创建了一个叫 Camera2Sample 的项目 xff0c 并且有一个 A
  • Camera2 三预览

    1 获取预览尺寸 CameraCharacteristics 是一个只读的相机信息提供者 xff0c 其内部携带大量的相机信息 xff0c 包括代表相机朝向的 LENS FACING xff1b 判断闪光灯是否可用的 FLASH INFO

随机推荐

  • Camera2 四拍照

    1 理解 Capture 工作流程 在正式介绍如何拍照之前 xff0c 我们有必要深入理解几种不同模式的 Capture 的工作流程 xff0c 只要理解它们的工作流程就很容易掌握各种拍照模式的实现原理 xff0c 在第一章 Camera2
  • 解决从PDF复制文字后乱码问题

    背景 需要从PDF复制文字出来做笔记 xff0c 可是谁知道PDF通过adobe打开后复制出来后是乱码 xff0c 如下图所示 xff1a 解决 尝试过安装字体 xff0c 可惜没卵用 方法1 CAJViewer打开 用该软件打开后复制 x
  • Android Studio不能启动模拟器原因探秘 The emulator process for AVD xxx has terminated

    文章背景 在Android Studio中创建模拟器后 xff0c 启动模拟器时弹出提示 The emulator process for AVD Pixel 2 API 31 has terminated xff0c 但是并没有显示具体错
  • AndroidStudio-快捷键-格式化代码

    Windows Ctrl 43 Alt 43 L Ctrl 43 Shift 43 F 无效 亲测 和qq热键冲突 我的解决方式是把qq除捕获屏幕外的热键全部设置为无 Mac OPTION 43 CMD 43 L
  • 安装APK时报错:Failure [INSTALL_FAILED_TEST_ONLY: installPackageLI]

    使用AS自动运行时会在app build outputs apk debug文件夹下自动生成测试APK xff1a app debug apk xff0c 用命令adb install app debug apk时报错 xff1a Fail
  • 计算机网络-划分子网 四大类必会题型

    必记知识点 A类 xff1a 0 126 xff0c 默认子网掩码 xff1a 255 0 0 0 B类 xff1a 128 191 xff0c 默认子网掩码 xff1a 255 255 0 0 C类 xff1a 192 223 xff0c
  • C语言-解释复杂声明

    基本术语 xff1a 声明符 xff1a int a 就是一个声明符 标识符 定义的变量名字 xff0c 如 xff1a int a xff0c 那么a就是一个标识符 1 两个原则 xff1a 始终从内往外读声明符 xff0c 括号优先级高
  • Android EditText 不自动获取焦点

    在Activity上面显示一个EditText xff0c 进入该页面时想阻止这个EditText自动获取焦点而自动调起键盘 思路如下 xff1a 可以采取让父级控件来获取焦点就可以了 例如说在这个EditText外面包一个LinearLa
  • Wireshark中无法显示网卡列表的解决方法

    1 问题描述 打开Wireshark时 xff0c 都会有一个网卡列表 xff0c 在该列表中显示了电脑的所有网卡 但是 xff0c 有时打开Wireshark时 xff0c 该网卡列表不显示 xff0c 如图1所示 图1 不显示网卡列表
  • Android事件分发与事件处理源码分析

    一 前言 Android中事件分发与事件处理是一个老生常谈的问题了 xff0c 自己在网上也看过很多文章 xff0c 但是大部分人都只是抛出一些结论或是一些流程图或者干脆就是一些运行demo的截图等 xff0c 对于这些结论和流程图是怎么来
  • 解决小米pad USB安装apk时AS报错:INSTALL_FAILED_USER_RESTRICTED

    设置 更多设置 开发者选项 gt 取消启用MIUI优化 用USB接口 xff0c 选择传输文件 xff08 MTP xff09
  • git 通过 comment 关键字查找 commit

    git log grep 61 word 比如 xff1a git log grep 61 同步
  • git合并多个 Commit

    在使用 Git 作为版本控制的时候 xff0c 我们可能会由于各种各样的原因提交了许多临时的 commit xff0c 而这些 commit 拼接起来才是完整的任务 那么我们为了避免太多的 commit 而造成版本控制的混乱 xff0c 通
  • git查看某次提交的文件列表

    Git操作常用 xff1a 一 查看某次提交的文件列表 首先使用git log查看历史提交记录 xff1a 复制你想要查看记录的某个提交代号9ddc9dca00b 使用命令git show 9ddc9dca00b stat查看详细文件列表
  • Android SoundPool插入耳机后依然有外放声音

    使用soundPool播放声音 xff0c 当手机已经接通耳机时 xff0c 还会有外放声音 xff0c 是因为在初始化soundpool是用的流类型 xff08 streamType xff09 导致的 xff0c 有些流类型系统是一定会
  • 一文搞懂Android JetPack组件原理之Lifecycle、LiveData、ViewModel与源码分析技巧

    Lifecycle LiveData和ViewModel作为AAC架构的核心 xff0c 常常被用在Android业务架构中 在京东商城Android应用中 xff0c 为了事件传递等个性化需求 xff0c 比如ViewModel间通信 V
  • Linux下安装npm

    1 root 登录linux 2 没有目录就自己创建一个 cd usr local node 3 下载安装包 wget https npm taobao org mirrors node v4 4 7 node v4 4 7 linux x
  • AudioService之音频输出通道切换

    前言 xff1a 音频输出的方式有很多种 xff0c 外放即扬声器 xff08 Speaker xff09 听筒 xff08 Telephone Receiver xff09 有线耳机 xff08 WiredHeadset xff09 蓝牙
  • Android音频——音量调节

    一 音量相关概念 1 相关术语解释 track volume 单个App设置音量时设置的是这个 xff0c 它只影响本App的音量 stream volume xff1a 设置某一stream的音量 xff0c Android系统中支持10
  • 电话状态权限及IMEI获取流程源码分析

    IMEI是设备唯一性的一个重要指标 xff0c 这篇文章对IMEI获取做一些分析 xff0c 以达到以下两个目的 xff1a 1 梳理Android源码中获取IMEI流程 2 理解获取IMEI时 xff0c 源码中权限调用流程 备注 xff