java.lang.IllegalAccessException: 尝试从非可视上下文访问可视化服务WindowManager。

8

我一直在努力将相机功能集成到我的应用程序中,但是不希望出现以下错误:

E/ContextImpl:尝试从非可视上下文com.camtest.App@385f002访问视觉服务WindowManager:Visual services,例如WindowManager、WallpaperService或LayoutInflater应该从Activity或其他可视上下文中访问。使用Activity或使用Context#createWindowContext(int, Bundle)创建的上下文,它们已调整为屏幕上某个区域的配置和可视边界。

java.lang.IllegalAccessException: 尝试从非可视上下文com.camtest.App@385f002访问视觉服务WindowManager

这个错误是由这行代码引起的:

final ListenableFuture<ProcessCameraProvider> cameraProviderFuture = ProcessCameraProvider.getInstance(this);

我查看了如何实现createWindowContext,但由于一些目标设备较旧且不符合升级到Android 11的条件,因此createWindowContext不是一个选项。

第一次尝试时,我按照CodeLabs之一来实现CameraX。相机的表现与预期相同,但触发了异常。所以我找到了一个不同的示例来实现CameraX,但我仍然遇到了相同的IllegalAccessException异常。

有任何建议吗?

package com.camtest;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.camera.core.Camera;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.ImageCapture;
import androidx.camera.core.ImageCaptureException;
import androidx.camera.core.Preview;
import androidx.camera.lifecycle.ProcessCameraProvider;
import androidx.camera.view.PreviewView;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.LifecycleOwner;

import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.view.View;
import android.widget.ImageView;
import android.widget.Toast;

import com.google.common.util.concurrent.ListenableFuture;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

public class CamTest extends AppCompatActivity {

    private Executor executor = Executors.newSingleThreadExecutor();
    private int REQUEST_CODE_PERMISSIONS = 9001;
    private final String[] REQUIRED_PERMISSIONS = new String[]{"android.permission.CAMERA", "android.permission.WRITE_EXTERNAL_STORAGE"};

    PreviewView mPreviewView;
    ImageView captureImage;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.camera_test);

        mPreviewView = findViewById(R.id.camera);//was previewView
        captureImage = findViewById(R.id.captureImg);

        if(allPermissionsGranted()){
            startCamera(); //start camera if permission has been granted by user
        } else{
            ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS);
        }
    }

    private void startCamera() {

        final ListenableFuture<ProcessCameraProvider> cameraProviderFuture = ProcessCameraProvider.getInstance(this); //This line triggers `E/ContextImpl: Tried to access visual service WindowManager from a non-visual Context`

        cameraProviderFuture.addListener(new Runnable() {
            @Override
            public void run() {
                try {

                    ProcessCameraProvider cameraProvider = cameraProviderFuture.get();
                    bindPreview(cameraProvider);

                } catch (ExecutionException | InterruptedException e) {
                    // No errors need to be handled for this Future.
                    // This should never be reached.
                }
            }
        }, ContextCompat.getMainExecutor(this));
    }

    void bindPreview(@NonNull ProcessCameraProvider cameraProvider) {
        Preview preview = new Preview.Builder().build();

        ImageCapture imageCapture = new ImageCapture.Builder()
                .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
                .build();

        CameraSelector cameraSelector = new CameraSelector.Builder()
                .requireLensFacing(CameraSelector.LENS_FACING_BACK)
                .build();

        Camera camera = cameraProvider.bindToLifecycle(
                ((LifecycleOwner) this),
                cameraSelector,
                preview,
                imageCapture);

        preview.setSurfaceProvider(
                mPreviewView.getSurfaceProvider());


        captureImage.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                SimpleDateFormat mDateFormat = new SimpleDateFormat("yyyyMMddHHmmss", Locale.US);
                File file = new File(getBatchDirectoryName(), mDateFormat.format(new Date())+ ".jpg");

                ImageCapture.OutputFileOptions outputFileOptions = new ImageCapture.OutputFileOptions.Builder(file).build();
                imageCapture.takePicture(outputFileOptions, executor, new ImageCapture.OnImageSavedCallback () {
                    @Override
                    public void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) {
                        new Handler().post(new Runnable() {
                            @Override
                            public void run() {
                                Toast.makeText(CamTest.this, "Image Saved successfully", Toast.LENGTH_SHORT).show();
                            }
                        });
                    }
                    @Override
                    public void onError(@NonNull ImageCaptureException error) {
                        error.printStackTrace();
                    }
                });
            }
        });
    }

    public String getBatchDirectoryName() {

        String app_folder_path = "";
        app_folder_path = Environment.getExternalStorageDirectory().toString() + "/images";
        File dir = new File(app_folder_path);
        if (!dir.exists() && !dir.mkdirs()) {

        }

        return app_folder_path;
    }

    private boolean allPermissionsGranted(){

        for(String permission : REQUIRED_PERMISSIONS){
            if(ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED){
                return false;
            }
        }
        return true;
    }
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {

        if(requestCode == REQUEST_CODE_PERMISSIONS){
            if(allPermissionsGranted()){
                startCamera();
            } else{
                Toast.makeText(this, "Permissions not granted by the user.", Toast.LENGTH_SHORT).show();
                this.finish();
            }
        }
    }
}

这个活动是由MainActivity的onCreate中下面的代码启动的:

Button button_test = findViewById(R.id.button_test);
button_test.setOnClickListener(view -> {
     Intent intent = new Intent(MainActivity.this, CamTest.class);
     startActivityForResult(intent,0);
});

编辑:完整的堆栈跟踪-

E/ContextImpl: Tried to access visual service WindowManager from a non-visual Context:com.camtest.App@dd90e6b Visual services, such as WindowManager, WallpaperService or LayoutInflater should be accessed from Activity or other visual Context. Use an Activity or a Context created with Context#createWindowContext(int, Bundle), which are adjusted to the configuration and visual bounds of an area on screen.
    java.lang.IllegalAccessException: Tried to access visual service WindowManager from a non-visual Context:com.camtest.App@dd90e6b
        at android.app.ContextImpl.getSystemService(ContextImpl.java:1914)
        at android.content.ContextWrapper.getSystemService(ContextWrapper.java:803)
        at androidx.camera.camera2.internal.Camera2UseCaseConfigFactory.<init>(Camera2UseCaseConfigFactory.java:50)
        at androidx.camera.camera2.Camera2Config.lambda$defaultConfig$1(Camera2Config.java:60)
        at androidx.camera.camera2.-$$Lambda$Camera2Config$g_hY10kZhqC56um0PalOLTzuFlU.newInstance(Unknown Source:0)
        at androidx.camera.core.CameraX.lambda$initAndRetryRecursively$9$CameraX(CameraX.java:575)
        at androidx.camera.core.-$$Lambda$CameraX$u-Xx2b6YXY5GXNXRh-mDiDnHdpQ.run(Unknown Source:10)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
        at java.lang.Thread.run(Thread.java:923)

编辑 #2: 为了重现这个错误,必须启用 VmPolicy 的 StrictMode。在 MainActivity.onCreate 中添加以下代码:

if( BuildConfig.BUILD_TYPE.contentEquals( "debug" ) ){
    /*StrictMode.setThreadPolicy( new StrictMode.ThreadPolicy.Builder()
            .detectAll()
            .penaltyLog()
            .build());*/
    StrictMode.setVmPolicy( new StrictMode.VmPolicy.Builder()
            .detectAll()//.detectNonSdkApiUsage()
            .penaltyLog()
            .build());
}

编辑 #3: 从 CameraX 版本 1.0.0-beta12 升级到 1.0.0-rc1(截至今天的当前版本)没有产生任何影响。


2
错误提示表明您正在使用Application上下文,因为我猜测com.camtest.App就是这个。您能否发布完整的堆栈跟踪? - CommonsWare
1
这是一个非常奇怪的堆栈跟踪。我不确定Context来自哪里。如果您没有使用最新的CameraX,可以尝试升级。否则,如果您有能力创建一个重现问题的示例项目,可以在问题跟踪器上提交问题。一时之间,感觉您没有做错任何事情。 - CommonsWare
1
你提供的上下文中的“this”不正确。这就是为什么它会给你这个错误的原因。你尝试过使用getApplicationContext()或getBaseContext()吗?或者尝试在CamText.class中创建名为Context context的全局变量,并在OnCreate中分配context = getApplicationContext()或getBaseContext(),无论哪个有效。 - SlothCoding
1
@CragMonkey 我的最后一次赌注是将你的 startCamera() 和其他所有内容移动到 MainActivity 中,并从那里启动它。至少在测试时这样做,看看会发生什么。这是我现在能提出的唯一建议,也许明天我会想到其他的东西。 - SlothCoding
仍然存在于'androidx.camera:camera-core:1.1.0-alpha09'中,因为surfaceManagerProvider.newInstance(mAppContext, - Pnemonic
显示剩余5条评论
1个回答

3

建议从Activity中传递Context而不是Application

堆栈跟踪表明,您正在传递com.camtest.App的实例。由于您只是从Activity.this中进行传递,我猜测您使用的库正在错误地调用Context.getApplicationContext()。您需要与库的维护者联系解决此问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接