You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android DeviceAdminReceiver密码失败时拍照功能失效问题排查

问题分析与解决方案

一、核心问题排查

  1. 相机被设备管理策略禁用
    你的device_admin.xml中添加了<disable-camera />策略,这会直接让系统禁用设备相机,导致无法调用相机拍照,这是最关键的问题。

  2. 权限申请的错误强转
    在onPasswordFailed方法中,你把Receiver的Context强转为AppCompatActivity来申请权限:

    ActivityCompat.requestPermissions((AppCompatActivity) context,
            new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA_PERMISSION);
    

    但DeviceAdminReceiver的上下文是Receiver类型,并非Activity,强转会直接抛出ClassCastException,导致权限申请失败。

  3. Camera2 API的回调与资源管理问题

    • ImageReader.setOnImageAvailableListener设置在onCaptureCompleted回调里,此时图像可能已经生成,会错过监听时机。
    • 调用cameraManager.openCamera时传入null作为Handler,回调会在主线程执行,相机操作属于耗时操作,容易引发ANR或回调不执行。
    • 未正确关闭CameraCaptureSession,会导致相机资源泄漏。
  4. 存储权限与路径问题
    Android 10(API 29)及以上版本,Environment.getExternalStoragePublicDirectory已被废弃,且Scoped Storage限制了直接写入公共目录的权限,导致图片无法保存。

二、修正后的解决方案

1. 移除相机禁用策略

修改device_admin.xml,删除<disable-camera />:

<device-admin xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-policies>
        <limit-password />
        <watch-login />
    </uses-policies>
</device-admin>

2. 修复权限申请逻辑

Receiver无法直接申请权限,需启动透明Activity处理权限请求:

  • 创建PermissionRequestActivity(透明主题):
public class PermissionRequestActivity extends AppCompatActivity {
    private static final int REQUEST_CAMERA_STORAGE = 100;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 设置透明主题避免界面闪烁
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,
                WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
                WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
        getWindow().setAlpha(0.0f);

        requestPermissions();
    }

    private void requestPermissions() {
        List<String> permissions = new ArrayList<>();
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
            permissions.add(Manifest.permission.CAMERA);
        }
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q && ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            permissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
        }

        if (!permissions.isEmpty()) {
            ActivityCompat.requestPermissions(this, permissions.toArray(new String[0]), REQUEST_CAMERA_STORAGE);
        } else {
            MyDeviceAdminReceiver.captureImage(this);
            finish();
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == REQUEST_CAMERA_STORAGE) {
            boolean allGranted = true;
            for (int result : grantResults) {
                if (result != PackageManager.PERMISSION_GRANTED) {
                    allGranted = false;
                    break;
                }
            }
            if (allGranted) {
                MyDeviceAdminReceiver.captureImage(this);
            }
            finish();
        }
    }
}
  • 在AndroidManifest.xml中注册该Activity并设置透明主题:
<activity
    android:name=".PermissionRequestActivity"
    android:exported="false"
    android:theme="@style/TransparentTheme" />
  • 添加透明主题到styles.xml:
<style name="TransparentTheme" parent="Theme.AppCompat.Light.NoActionBar">
    <item name="android:windowBackground">@android:color/transparent</item>
    <item name="android:windowIsTranslucent">true</item>
    <item name="android:windowAnimationStyle">@null</item>
</style>
  • 修改MyDeviceAdminReceiver的onPasswordFailed方法:
@Override
public void onPasswordFailed(Context context, Intent intent) {
    super.onPasswordFailed(context, intent);

    boolean hasCameraPerm = ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED;
    boolean hasStoragePerm = true;
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
        hasStoragePerm = ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
    }

    if (hasCameraPerm && hasStoragePerm) {
        captureImage(context);
    } else {
        Intent permIntent = new Intent(context, PermissionRequestActivity.class);
        permIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(permIntent);
    }
}

3. 修复Camera2 API实现

调整监听时机、添加Handler线程、正确释放资源:

public static void captureImage(Context context) {
    CameraManager cameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
    HandlerThread handlerThread = new HandlerThread("CameraCaptureThread");
    handlerThread.start();
    Handler handler = new Handler(handlerThread.getLooper());

    try {
        String cameraId = getBackCameraId(cameraManager);
        if (cameraId == null) {
            Log.e(TAG, "No back camera found");
            handlerThread.quitSafely();
            return;
        }

        Size imageDimensions = getImageDimensions(cameraManager, cameraId);
        if (imageDimensions == null) {
            Log.e(TAG, "Failed to get image dimensions");
            handlerThread.quitSafely();
            return;
        }

        final ImageReader imageReader = ImageReader.newInstance(
                imageDimensions.getWidth(),
                imageDimensions.getHeight(),
                ImageFormat.JPEG,
                1
        );

        // 提前设置图像监听
        imageReader.setOnImageAvailableListener(reader -> {
            try (Image image = reader.acquireLatestImage()) {
                if (image == null) return;
                ByteBuffer buffer = image.getPlanes()[0].getBuffer();
                byte[] bytes = new byte[buffer.capacity()];
                buffer.get(bytes);
                saveImage(context, bytes);
            } finally {
                imageReader.close();
                handlerThread.quitSafely();
            }
        }, handler);

        List<Surface> outputSurfaces = Collections.singletonList(imageReader.getSurface());

        CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
            @Override
            public void onOpened(@NonNull CameraDevice camera) {
                try {
                    CaptureRequest.Builder captureRequestBuilder = camera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
                    captureRequestBuilder.addTarget(imageReader.getSurface());
                    captureRequestBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO);
                    captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);

                    camera.createCaptureSession(outputSurfaces, new CameraCaptureSession.StateCallback() {
                        @Override
                        public void onConfigured(@NonNull CameraCaptureSession session) {
                            try {
                                session.capture(captureRequestBuilder.build(), new CameraCaptureSession.CaptureCallback() {
                                    @Override
                                    public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
                                        super.onCaptureCompleted(session, request, result);
                                        session.close();
                                        camera.close();
                                    }
                                }, handler);
                            } catch (CameraAccessException e) {
                                Log.e(TAG, "Capture failed: " + e.getMessage());
                                session.close();
                                camera.close();
                                handlerThread.quitSafely();
                            }
                        }

                        @Override
                        public void onConfigureFailed(@NonNull CameraCaptureSession session) {
                            Log.e(TAG, "Capture session configuration failed");
                            session.close();
                            camera.close();
                            handlerThread.quitSafely();
                        }
                    }, handler);
                } catch (CameraAccessException e) {
                    Log.e(TAG, "Camera open failed: " + e.getMessage());
                    camera.close();
                    handlerThread.quitSafely();
                }
            }

            @Override
            public void onDisconnected(@NonNull CameraDevice camera) {
                camera.close();
                handlerThread.quitSafely();
            }

            @Override
            public void onError(@NonNull CameraDevice camera, int error) {
                Log.e(TAG, "Camera error: " + error);
                camera.close();
                handlerThread.quitSafely();
            }
        };

        if (ActivityCompat.checkSelfPermission(context, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
            cameraManager.openCamera(cameraId, stateCallback, handler);
        } else {
            Log.e(TAG, "Camera permission not granted");
            handlerThread.quitSafely();
        }
    } catch (CameraAccessException e) {
        Log.e(TAG, "Camera manager error: " + e.getMessage());
        handlerThread.quitSafely();
    }
}

4. 适配Android 10+存储逻辑

使用MediaStore保存图片:

private static void saveImage(Context context, byte[] imageBytes) {
    SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault());
    String fileName = "IMG_" + sdf.format(new Date()) + ".jpg";

    ContentValues values = new ContentValues();
    values.put(MediaStore.Images.Media.DISPLAY_NAME, fileName);
    values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        values.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + "/SecurityCapture");
        values.put(MediaStore.Images.Media.IS_PENDING, 1);
    }

    ContentResolver resolver = context.getContentResolver();
    Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
    if (uri == null) {
        Log.e(TAG, "Failed to create media store entry");
        return;
    }

    try (OutputStream outputStream = resolver.openOutputStream(uri)) {
        outputStream.write(imageBytes);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            values.clear();
            values.put(MediaStore.Images.Media.IS_PENDING, 0);
            resolver.update(uri, values, null, null);
        }
        Log.d(TAG, "Image saved to: " + uri.toString());
    } catch (IOException e) {
        Log.e(TAG, "Failed to save image: " + e.getMessage());
        resolver.delete(uri, null, null);
    }
}

三、额外注意事项

  • 需要在MainActivity中引导用户激活设备管理员权限,否则onPasswordFailed不会触发。
  • 发送邮件功能可通过Intent.ACTION_SEND启动系统邮件应用,若需后台发送,需使用JavaMail库并配合前台服务(Android 10+后台启动Activity受限)。

内容的提问来源于stack exchange,提问作者Lambda Developers

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 22:17:02