【问题标题】:How to update UI elements from a background thread in Android?如何从 Android 的后台线程更新 UI 元素?
【发布时间】:2019-03-15 15:10:30
【问题描述】:

我使用 camera2 api 实现了一个自定义的 Android 相机,当我点击捕获按钮时,我将捕获的图像保存到我的手机永久存储中。

现在我需要在保存之前显示一个弹出窗口来输入图片名称,并且这个弹出窗口有一个 imageView 来显示捕获的图像。

即使我从主线程加载图像,我也会收到此错误

  "Only the original thread that created a view hierarchy can touch its views"

这里是代码

  private void initPopup(final Bitmap bitmap) {

    popAddName = new Dialog(this);
    popAddName.setContentView(R.layout.popup_add_name);
    popAddName.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
    popAddName.getWindow().setLayout(Toolbar.LayoutParams.MATCH_PARENT, Toolbar.LayoutParams.WRAP_CONTENT);
    popAddName.getWindow().getAttributes().gravity = Gravity.TOP;

    popup_add = popAddName.findViewById(R.id.popup_add);
    popup_img = popAddName.findViewById(R.id.popup_img);
    popup_name = popAddName.findViewById(R.id.popup_name);

    Thread thread = new Thread() {
        @Override
        public void run() {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    Picasso.with(getApplicationContext()).load(getImageUri(getApplicationContext(), bitmap)).placeholder(R.drawable.placeholder).into(popup_img);
                }
            });

        }
    };
    thread.start();
}

这是将Bitmap转换为Uri以进行Picasso加载的方法

  public Uri getImageUri(Context inContext, Bitmap inImage) {
    ByteArrayOutputStream bytes = new ByteArrayOutputStream();
    inImage.compress(Bitmap.CompressFormat.JPEG, 100, bytes);
    String path = MediaStore.Images.Media.insertImage(inContext.getContentResolver(), inImage, "Title", null);
    return Uri.parse(path);
}

这是捕获方法

    ImageReader.OnImageAvailableListener readerListener = new ImageReader.OnImageAvailableListener() {
            @Override
            public void onImageAvailable(ImageReader imageReader) {
                Image image = null;
                try {
                    image = reader.acquireLatestImage();
                    ByteBuffer buffer = image.getPlanes()[0].getBuffer();
                    byte[] bytes = new byte[buffer.capacity()];
                    buffer.get(bytes);
                    //CONVERTION BYTES[] TO BITMAP
                    Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);

                    //create a new folder in external storage directory
                    String lorealFolder = "coffretPics";
                    File f = new File(Environment.getExternalStorageDirectory(), lorealFolder);
                    if (!f.exists()) {
                        f.mkdirs();
                    }

                    initPopup(bitmap);
                    popAddName.show();

                    file = new File(Environment.getExternalStorageDirectory() + "/" + lorealFolder + "/" + UUID.randomUUID().toString() + ".jpg");


                    save(bytes);


                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    {
                        if (image != null)
                            image.close();
                    }
                }
            }

            private void save(byte[] bytes) throws IOException {
               /* OutputStream outputStream = null;
                try {
                    outputStream = new FileOutputStream(file);
                    outputStream.write(bytes);
                } finally {
                    if (outputStream != null) {
                        outputStream.close();
                    }
                }*/
            }
        };

谁能告诉我这是什么问题?

【问题讨论】:

  • 您必须向 UI 线程发布消息并从那里更改 UI。有多种方法可以做到这一点(如果您在活动中,则使用 runOnUIThread,对主线程使用 Handler 并向其发布消息,使用 rxjava 或类似库等)。选择你最喜欢的。
  • 在您的情况下,回调正在另一个线程上运行,因为它是从它用于联网的线程中调用的。所以你需要在那里发布到 ui 线程。通常你不想在网络线程上做所有的处理,然后只在ui线程上做ui更改。
  • @GabeSechan 你能发布一个例子,我很乐意接受。还是谢谢你,先生。
  • @SamuelPhilipp 这不是因为我已经在尝试该解决方案但它不起作用。

标签: java android error-handling android-camera2


【解决方案1】:

更新答案:

创建一个名为 AppExecutors 的类

public class AppExecutors {
    // For Singleton instantiation
    private static final Object LOCK = new Object();
    private static AppExecutors sInstance;
    private final Executor diskIO;
    private final Executor mainThread;
    private final Executor networkIO;

    private AppExecutors(Executor diskIO, Executor networkIO, Executor mainThread) {
        this.diskIO = diskIO;
        this.networkIO = networkIO;
        this.mainThread = mainThread;
    }

    public static AppExecutors getInstance() {
        if (sInstance == null) {
            synchronized (LOCK) {
                sInstance = new AppExecutors(Executors.newSingleThreadExecutor(),
                        Executors.newFixedThreadPool(3),
                        new MainThreadExecutor());
            }
        }
        return sInstance;
    }

    public Executor diskIO() {
        return diskIO;
    }

    public Executor mainThread() {
        return mainThread;
    }

    public Executor networkIO() {
        return networkIO;
    }

    private static class MainThreadExecutor implements Executor {
        private Handler mainThreadHandler = new Handler(Looper.getMainLooper());

        @Override
        public void execute(@NonNull Runnable command) {
            mainThreadHandler.post(command);
        }
    }

获取主线程如下:

        AppExecutors.getInstance().mainThread().execute(new Runnable() {
        @Override
        public void run() {
            // Do the work on UI thread here.
        }
    });

这应该可以解决您的问题。

【讨论】:

  • 我遇到了这个错误java.lang.IllegalStateException: 方法调用应该从主线程发生。
  • 仍然出现只有创建视图层次结构的原始线程才能触及其视图错误。
  • Try File > Invalidate Caches / Restart on Android Studio
  • 我认为问题是我正在使用毕加索从后台线程设置图像,而毕加索的加载方法已经是后台线程。
  • 我刚刚摆脱了毕加索并使用 setImageBitmap 加载了图像,现在它正在工作。感谢先生的帮助和时间。
猜你喜欢
  • 2012-04-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多