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.CameraDeviceUserShimCallbackHandler.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;
}