5

我正在关注有关在 Android 应用程序中使用相机的教程。在模拟器和物理设备上运行调试时,我收到错误“权限拒绝:无法使用相机” 。我在清单文件中尝试了各种权限。似乎大多数遇到此错误的人都有错字、缺少权限或权限不在清单中的正确位置。

这是我的清单文件:

<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.karudo.dbzrealpowerup" >

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.FLASHLIGHT" />
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-feature android:name="android.hardware.camera2" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".DBZHome"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name=".DBZStartPowerUp"
            android:label="@string/title_activity_dbzstart_power_up" >
        </activity>
    </application>

</manifest>

这是我的活动:

package com.example.karudo.dbzrealpowerup;

import android.app.Activity;
import android.content.Context;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.os.Bundle;
import android.util.Size;
import android.view.Menu;
import android.view.MenuItem;
import android.view.TextureView;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class DBZStartPowerUp extends Activity {

    private Size mPreviewSize;
    private String mCameraId;
    private TextureView mTextureView;
    private TextureView.SurfaceTextureListener mSurfaceTextureListener =
            new TextureView.SurfaceTextureListener() {

                @Override
                public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
                    setupCamera(width, height);
                    openCamera();
                }

                @Override
                public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {

                }

                @Override
                public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
                    return false;
                }

                @Override
                public void onSurfaceTextureUpdated(SurfaceTexture surface) {

                }
            };

    private CameraDevice mCameraDevice;
    private CameraDevice.StateCallback mCameraDeviceStateCallback
            = new CameraDevice.StateCallback() {

        @Override
        public void onOpened(CameraDevice camera) {
            mCameraDevice = camera;
            Toast.makeText(getApplicationContext(), "Camera Opened!", Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onDisconnected(CameraDevice camera) {
            camera.close();
            mCameraDevice = null;
        }

        @Override
        public void onError(CameraDevice camera, int error) {
            camera.close();
            mCameraDevice = null;
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_dbzstartpowerup);
        mTextureView = (TextureView) findViewById(R.id.dbzCameraPreview);
    }

    @Override
    public void onResume() {
        super.onResume();
        if(mTextureView.isAvailable()) {

        } else {
            mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_dbzstartpowerup, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    private void setupCamera(int width, int height) {
        CameraManager cameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
        try {
            for(String cameraId : cameraManager.getCameraIdList()) {
                CameraCharacteristics cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraId);
                if (cameraCharacteristics.get(CameraCharacteristics.LENS_FACING) ==
                        CameraCharacteristics.LENS_FACING_FRONT) {
                    continue;
                }
                StreamConfigurationMap map = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
                mPreviewSize = getPreferredPreviewSize(map.getOutputSizes(SurfaceTexture.class), width, height);
                mCameraId = cameraId;
                return;
            }
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    private Size getPreferredPreviewSize(Size[] mapSizes, int width, int height) {
        List<Size> collectorSizes = new ArrayList<>();
        for(Size option : mapSizes) {
            if(width > height) {
                if(option.getWidth() > width &&
                        option.getHeight() > height) {
                    collectorSizes.add(option);
                }
            } else {
                if(option.getWidth() > height &&
                        option.getHeight() > width) {
                    collectorSizes.add(option);
                }
            }
        }
        if(collectorSizes.size() > 0) {
            return Collections.min(collectorSizes, new Comparator<Size>() {
                @Override
                public int compare(Size lhs, Size rhs) {
                    return Long.signum(lhs.getWidth() * lhs.getHeight() - rhs.getWidth() * rhs.getHeight());
                }
            });
        }
        return mapSizes[0];
    }

    private void openCamera() {
        CameraManager cameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
        try {
            cameraManager.openCamera(mCameraId, mCameraDeviceStateCallback, null);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }
}

我的logcat中的错误:

10-04 03:15:02.740     961-8780/? E/CameraService﹕ Permission Denial: can't use the camera pid=20601, uid=10059
10-04 03:15:02.741  20601-20601/com.example.karudo.dbzrealpowerup E/AndroidRuntime﹕ FATAL EXCEPTION: main
    Process: com.example.karudo.dbzrealpowerup, PID: 20601
    java.lang.SecurityException: Lacking privileges to access camera service
            at android.hardware.camera2.utils.CameraBinderDecorator.throwOnError(CameraBinderDecorator.java:108)
            at android.hardware.camera2.legacy.CameraDeviceUserShim.connectBinderShim(CameraDeviceUserShim.java:336)
            at android.hardware.camera2.CameraManager.openCameraDeviceUserAsync(CameraManager.java:324)
            at android.hardware.camera2.CameraManager.openCamera(CameraManager.java:454)
            at com.example.karudo.dbzrealpowerup.DBZStartPowerUp.openCamera(DBZStartPowerUp.java:163)
            at com.example.karudo.dbzrealpowerup.DBZStartPowerUp.access$100(DBZStartPowerUp.java:23)
            at com.example.karudo.dbzrealpowerup.DBZStartPowerUp$1.onSurfaceTextureAvailable(DBZStartPowerUp.java:34)
            at android.view.TextureView.getHardwareLayer(TextureView.java:368)
            at android.view.View.updateDisplayListIfDirty(View.java:15151)
            at android.view.View.draw(View.java:15948)
            at android.view.ViewGroup.drawChild(ViewGroup.java:3609)
            at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3399)
            at android.view.View.updateDisplayListIfDirty(View.java:15169)
            at android.view.View.draw(View.java:15948)
            at android.view.ViewGroup.drawChild(ViewGroup.java:3609)
            at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3399)
            at android.view.View.updateDisplayListIfDirty(View.java:15169)
            at android.view.View.draw(View.java:15948)
            at android.view.ViewGroup.drawChild(ViewGroup.java:3609)
            at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3399)
            at android.view.View.updateDisplayListIfDirty(View.java:15169)
            at android.view.View.draw(View.java:15948)
            at android.view.ViewGroup.drawChild(ViewGroup.java:3609)
            at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3399)
            at android.view.View.draw(View.java:16181)
            at com.android.internal.policy.PhoneWindow$DecorView.draw(PhoneWindow.java:2690)
            at android.view.View.updateDisplayListIfDirty(View.java:15174)
            at android.view.ThreadedRenderer.updateViewTreeDisplayList(ThreadedRenderer.java:281)
            at android.view.ThreadedRenderer.updateRootDisplayList(ThreadedRenderer.java:287)
            at android.view.ThreadedRenderer.draw(ThreadedRenderer.java:322)
            at android.view.ViewRootImpl.draw(ViewRootImpl.java:2615)
            at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:2434)
            at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2067)
            at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1107)
            at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6013)
            at android.view.Choreographer$CallbackRecord.run(Choreographer.java:858)
            at android.view.Choreographer.doCallbacks(Choreographer.java:670)
            at android.view.Choreographer.doFrame(Choreographer.java:606)
            at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:844)
            at android.os.Handler.handleCallback(Handler.java:739)
            at android.os.Handler.dispatchMessage(Handler.java:95)
            at android.os.Looper.loop(Looper.java:148)
            at android.app.ActivityThread.main(ActivityThread.java:5417)
            at java.lang.reflect.Method.invoke(Native Method)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)

我是应用程序开发的新手,诚然我不擅长调试,但从我看到的其他人的文件(以及只有 4 个月大的教程)来看,我的清单权限似乎是正确的。

谁能告诉我我做错了什么?

干杯,李。

更新:我通过调试发现它一达到这个方法就会崩溃......

private void openCamera() {
    CameraManager cameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
    try {
        cameraManager.openCamera(mCameraId, mCameraDeviceStateCallback, null);
    } catch (CameraAccessException e) {
        e.printStackTrace();
    }

}

...特别是当它运行 try 语句时。

更新 2:如果我注释掉 try/catch 语句,应用程序不会崩溃,但预期的结果(即打印“相机已打开!”)不会发生。有任何想法吗?

更新 3:我很抱歉,我刚刚意识到我的上述编辑是多么愚蠢。很明显为什么它不再崩溃,但至少我现在知道我必须调试我的cameraManager.openCamera参数。如果有人可以看一下,代码就在那里:)

4

5 回答 5

11

如果您使用的是 Android 6 Marshmallow,则此问题可能是由新的权限管理引起的。就我而言,我通过覆盖以下活动方法解决了这个问题:

@Override
public void onRequestPermissionsResult(int requestCode,  String permissions[], int[] grantResults) {
    switch (requestCode) {
        case CAMERA_PERMISSION:
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                if(mClss != null) {
                    Intent intent = new Intent(this, ClassUsingCamera);
                    startActivity(intent);
                }
            } else {
                Toast.makeText(this, "Please grant camera permission to use the QR Scanner", Toast.LENGTH_SHORT).show();
            }
            return;
    }
}

然后我使用以下代码启动了需要相机(ClassUsingCamera)的活动:

if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
                != PackageManager.PERMISSION_GRANTED) {
     ActivityCompat.requestPermissions(this,
                    new String[]{Manifest.permission.CAMERA}, CAMERA_PERMISSION);
} else {
       Intent intent = new Intent(this, ClassUsingCamera);
       startActivity(intent);
        }

在第一个应用程序启动时,您会看到弹出窗口,要求您授予对相机的访问权限。

另一种选择是使用 PermissionsDispatcher,如此处所述https://github.com/hotchemi/PermissionsDispatcher

于 2016-01-17T17:39:24.740 回答
3

仅当我在具有(Android 6.0.0)或具有(API 23)的仿真器的设备上尝试该应用程序时,我才遇到相同的错误。但与其他人一起工作正常。

这是因为 android 以上 M 对运行时应用程序的授予权限进行了一些更改。

要做到这一点,请按照以下几个步骤操作。

首先:添加这个静态变量。

private static final int REQUEST_CAMERA_RESULT = 1;

然后 修改你的 openCamera 方法

private void openCamera() {
    CameraManager cameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
    try {
        Log.v("CAMERA", mCameraId + " " + mCameraDeviceStateCallback);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){
            if(ContextCompat.checkSelfPermission(this, android.Manifest.permission.CAMERA)
                    == PackageManager.PERMISSION_GRANTED){
                cameraManager.openCamera(mCameraId, mCameraDeviceStateCallback,mBackgroundHandler);
            }
            else {
                if (shouldShowRequestPermissionRationale(android.Manifest.permission.CAMERA)){
                    Toast.makeText(this,"No Permission to use the Camera services", Toast.LENGTH_SHORT).show();
                }
                requestPermissions(new String[] {android.Manifest.permission.CAMERA},REQUEST_CAMERA_RESULT);
            }
        }
        else {
            cameraManager.openCamera(mCameraId, mCameraDeviceStateCallback, mBackgroundHandler);
        }
    } catch (CameraAccessException e) {
        e.printStackTrace();
    }
}

随意将 mBackgroundHandler 更改为 null,我只是让它在后台线程中处理相机工作。

然后,覆盖这个方法

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
    switch (requestCode){
        case  REQUEST_CAMERA_RESULT:
            if (grantResults[0] != PackageManager.PERMISSION_GRANTED){
                Toast.makeText(this, "Cannot run application because camera service permission have not been granted", Toast.LENGTH_SHORT).show();
            }
            break;
        default:
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        break;
    }
}
于 2016-02-11T16:33:06.063 回答
1

我可以说几点:

  1. 如果连接到相机服务失败(例如,如果相机被任何其他应用程序使用或设备管理器已禁用相机或任何应用程序无法释放相机。)
  1. 确保您在代码中安全地关闭/释放相机。

  2. 您是否尝试过检查摄像头是否被其他人使用,或者您的策略管理器是否有一些关闭摄像头的设置?

于 2015-10-04T03:56:38.920 回答
0

尝试将此代码放入您的 manifest.xml 文件中。

 <uses-sdk
    android:minSdkVersion="8"
    android:targetSdkVersion="19" />

<uses-permission
    android:name="android.permission.INTERNET"
    android:maxSdkVersion="19" />
<uses-permission android:name="android.permission.SET_WALLPAPER" />
<uses-permission android:name="android.permission.SET_DEBUG_APP"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
于 2015-10-04T03:48:33.467 回答
0

您拥有的关于权限的第一个选项是转到该特定应用程序的 android 设置并从那里授予它所需的所有权限。您拥有的第二个选项是检查在运行时是否需要该权限并请求应用程序所需的权限,或者更好的方法是在运行需要该权限的功能时请求权限。

好吧,通过一些在活动打开时要求应用程序权限的代码。但是您可以修改该代码以在需要拍摄相机等功能时请求许可。

另一件事是,某些权限(例如 Internet)是系统自动授予的,因此您无需请求。但是,您需要明确要求一些。

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>

<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.CAMERA"/>  

上面显示了在 AndroidManifest 中打开应用程序标记正上方声明的权限。

    public boolean hasFineLocationPermission(){
        if (ContextCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED){
            return true;
        }
        else{
            return false;
        }
    }

    public boolean hasExternalStoragePermission(){
        if (ContextCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
            return true;
        }
        else{
            return false;
        }
    }

public boolean hasLocationForegroundService(){
    if (ContextCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.FOREGROUND_SERVICE) == PackageManager.PERMISSION_GRANTED){
        return true;
    }
    else{
        return false;
    }
}



public boolean hasReadPhoneStatePermission(){
    if (ContextCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED){
        return true;
    }
    else{
        return false;
    }
}

public boolean hasCameraPermission(){
    if (ContextCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED){
        return true;
    }
    else{
        return false;
    }
}

public boolean hasAccessNetworkState(){
    if (ContextCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.ACCESS_NETWORK_STATE) == PackageManager.PERMISSION_GRANTED){
        return true;
    }
    else{
        return false;
    }
}

我们上面有一些函数。所有功能所做的基本上是检查是否授予了某个权限。如果返回 true,则无需请求该许可。如果不返回 false,那么请稍候请求该许可。例如:

if (ContextCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED){ return true; }

这将检查我们是否可以访问相机,如果我们这样做,它会返回 True,如果没有,它将返回 false,如函数 hasCameraPermission() 所示

现在我们可以使用以下函数检查是否授予了所有权限:

public void getApplicationPermissions(){

    List<String> listPermissionsNeeded = new ArrayList<>();
    if (!hasFineLocationPermission()){

        listPermissionsNeeded.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION);

    }

    if (!hasExternalStoragePermission()){

        listPermissionsNeeded.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);

    }

    if (!hasLocationForegroundService()){
        listPermissionsNeeded.add(Manifest.permission.ACCESS_FINE_LOCATION);

    }
    if (!hasReadPhoneStatePermission()){
        listPermissionsNeeded.add(Manifest.permission.READ_PHONE_STATE);
    }

    if (!hasCameraPermission()){
        listPermissionsNeeded.add(Manifest.permission.CAMERA);
    }

    if (!hasAccessNetworkState()){
        listPermissionsNeeded.add(Manifest.permission.ACCESS_NETWORK_STATE);
    }

    Log.i("listPermissionsNeeded", String.valueOf(listPermissionsNeeded));

    if (!listPermissionsNeeded.isEmpty()){
        ActivityCompat.requestPermissions(this,listPermissionsNeeded.toArray(new String[listPermissionsNeeded.size()]),0);
    }
    else{
        Toast.makeText(this,"All Permissions Required Are Granted",Toast.LENGTH_SHORT).show();
    }

}

上面的代码首先声明了一个List<String> listPermissionsNeeded = new ArrayList<>();String类型的列表

然后检查是否:

if (!hasCameraPermission()){
            listPermissionsNeeded.add(Manifest.permission.CAMERA);
        } 

含义:如果尚未授予相机权限,则通过添加将该权限添加到列表中Manifest.permission.CAMERA

如果我们的权限列表是空的,我们将只向用户展示祝酒词。但是,如果它确实有一些权限,我们调用函数ActivityCompat.requestPermissions()传入上下文、我们的列表和一个标识符,在这种情况下为 0:

if (!listPermissionsNeeded.isEmpty()){
    ActivityCompat.requestPermissions(this,listPermissionsNeeded.toArray(new String[listPermissionsNeeded.size()]),0);
}
else{
    Toast.makeText(this,"All Permissions Required Are Granted",Toast.LENGTH_SHORT).show();
} 

当调用该 requestPermissions 时,会调用另一个 android 方法,即onRequestPermissionsResult(). 我们实现如下:

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        if(requestCode=0){
         for (int i=0;i<grantResults.length;i++){
            if (grantResults[i] == -1){
                getApplicationPermissions();

            }
        }

        }

    }

请求代码将为 0,而 grantResults 将是我们在函数中传递的列表:ActivityCompat.requestPermissions(this,listPermissionsNeeded.toArray(new String[listPermissionsNeeded.size()]),0);

当用户拒绝权限时,您将在 grantResult 中返回 -1 值。我在该函数中所做的只是遍历我的 grantResults 并搜索任何已被拒绝的权限,如果有已被拒绝的权限,我会一次又一次地请求它。这对用户体验来说太糟糕了。但你明白了。

最后我们需要检查正在使用的Android版本是否为Android Marshmallow Api Level 23。据我所知,android 6.0以下只要在Manifest中声明即可获得所有权限,但从6.0及以上开始需要请求权限。这样您就可以避免低于该级别的 api 级别崩溃。对此我不是 100%,我发现这方面的信息相互矛盾,但已解决,有人可以在那里纠正我。

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){
            getApplicationPermissions();

        }

希望我没有让你感到困惑。如果你有任何问题,你可以问,或者我可以发送 github 链接,这样你就可以看到所有代码是如何连接的,即如果你需要它。

所有代码都是用 Java 编写的。

于 2021-07-21T10:08:18.430 回答