Camera2的空指针崩溃

937 阅读1分钟

1.崩溃信息

异常进程#线程

com.xxx.xxxx#LegacyCameraCallback(4215)

#4215 LegacyCameraCallback

出错堆栈

java.lang.NullPointerException

Attempt to invoke virtual method 'android.hardware.camera2.CaptureRequest android.hardware.camera2.impl.CameraDeviceImpl$CaptureCallbackHolder.getRequest(int)' on a null object reference

解析原始

1 android.hardware.camera2.impl.CameraDeviceImpl$CameraDeviceCallbacks.onResultReceived(CameraDeviceImpl.java:1923)

2 android.hardware.camera2.legacy.CameraDeviceUserShimCameraCallbackThreadCameraCallbackThreadCallbackHandler.handleMessage(CameraDeviceUserShim.java:323)

3 android.os.Handler.dispatchMessage(Handler.java:106)

4 android.os.Looper.loop(Looper.java:173)

5 android.os.HandlerThread.run(HandlerThread.java:65)

在某些上复线概率高。使用 Xiaomi MI PAD 4 在退出Activity的时候崩溃

2.找到hook点

这是使用CameraX,所以首先找 CameraDeviceImpl 这个对象

<service
    android:name="androidx.camera.core.impl.MetadataHolderService"
    android:enabled="false"
    android:exported="false"
    tools:ignore="Instantiatable,MissingServiceExportedEqualsTrue"
    tools:node="merge" >
    <meta-data
        android:name="androidx.camera.core.impl.MetadataHolderService.DEFAULT_CONFIG_PROVIDER"
        android:value="androidx.camera.camera2.Camera2Config$DefaultProvider" />
</service>

CameraX 默认使用的是 Camera2Config$DefaultProvider 需要替换 android:value

<meta-data
    tools:replace="android:value"
    android:name="androidx.camera.core.impl.MetadataHolderService.DEFAULT_CONFIG_PROVIDER"
    android:value="com.xxx.xxxx.CameraXProvider" />

使用自己的 CameraFactory

class CameraFactory implements CameraFactory

代理 Camera2CameraFactory 创建 CameraInternal CameraDeviceImpl 对象是在 CameraDevice.StateCallback 的 onOpened 里获得, 需要hook CameraInternal 的 StateCallback。 设置一个回调代理旧的回调

CameraDevice.StateCallback callback;

CameraDeviceStateCallback(CameraDevice.StateCallback callback) {
    this.callback = callback;
}
@Override
public CameraInternal getCamera(@NonNull String cameraId) throws CameraUnavailableException {
    CameraInternal internal = factory.getCamera(cameraId);
    Log.d(TAG, "getCamera:cameraId=" + cameraId + ",internal=" + internal + ",c=" + internal.getClass());
    String className = "androidx.camera.camera2.internal.Camera2CameraImpl";
    if (internal.getClass().getName().equals(className)) {
        try {
            Field field = Class.forName(className).getDeclaredField("mCaptureSessionRepository");
            field.setAccessible(true);
            Object object = field.get(internal);
            Log.d(TAG, "getCamera:cameraId=" + cameraId + ",object=" + object);
            if (object != null) {
                Class<?> session = object.getClass();
                Field fieldCall = session.getDeclaredField("mCameraStateCallback");
                fieldCall.setAccessible(true);
                CameraDevice.StateCallback callback = (CameraDevice.StateCallback) fieldCall.get(object);
                Log.d(TAG, "getCamera:cameraId=" + cameraId + ",callback=" + callback);
                CameraDeviceStateCallback callback2 = new CameraDeviceStateCallback(callback);
                fieldCall.set(object, callback2);
            }
        } catch (Exception e) {
            Log.e(TAG, "CaptureSession", e);
        }
    }
    return internal;
}

空指针是 CaptureCallbackHolder 这个变量在 SparseArray mCaptureCallbackMap = new SparseArray(); 存着。所以hook mCaptureCallbackMap 这个对象就行。 因为在 public void removeAt(int index) 会调多次 所以 Object get(int key) 返回空 提前准备一个 SparseArray 存一下这个变量不移除

@Override
public void put(int key, Object value) {
    Log.d(TAG, "put:key=" + key + ",value=" + value + ",lp=" + Looper.myLooper());
    super.put(key, value);
    sparseArraySave.put(key, value);//暂存
}

@Override
public Object get(int key) {
    Object value = super.get(key);
    if (value == null) {
        value = sparseArraySave.get(key);//取出
    }
    return value;
}