使用线程获取图像资源时,意图选择器保持打开状态

Posted

技术标签:

【中文标题】使用线程获取图像资源时,意图选择器保持打开状态【英文标题】:Intent chooser stays open when using a Thread to get an image resource 【发布时间】:2014-08-04 05:11:49 【问题描述】:

我正在构建一个照片上传表单,它必须从相机或画廊或任何其他可用资源(Dropbox/Google Drive/Astro 文件管理器等)中获取图像。我想除了一个小组件外,我已经完成了所有工作。

当所选图像来自网络资源(例如 Google 云端硬盘)时,可能需要很长时间才能返回活动。我想让这个异步,以避免在图像下载时让用户盯着黑屏。

我已经编写了很多代码来让这一切正常工作,但这里是它的一般过程:

    Activity 启动并显示表单。

    Intent 选择器会自动打开并要求用户从相机或其他应用中选择图像。

    用户选择某个应用(例如 Google Drive),选择一张图片,然后该应用关闭,返回所选图片Uri 作为结果。

    缓存文件内容以解决 KitKat 权限问题,如果 Activity 关闭并需要恢复,该权限可能会阻止重新读取图像。

    以缓存文件为源,异步显示缩放位图。

    Intent 选择器关闭,图像显示在 ImageButton 中,用户可以看到要在提交前填写的表单。

    点击提交按钮后,将启动一个异步任务,允许分段上传到服务器。

    异步上传使用回调函数通知用户上传进度。

我在 #3 和 #4 之间的转换中陷入困境。

如果我使用线程或异步任务 Intent 选择器将保持打开状态,但图像也会显示在表单上。

如果我只是强迫用户等待(串行) 用户一直盯着黑屏,然后 Intent Chooser 关闭,图像显示在表单上。

打开 Intent 选择器

/**
 * see http://***.com/a/12347567/940217
 */
private void openImageIntent() 

    // Camera.
    final List<Intent> cameraIntents = new ArrayList<Intent>();
    final PackageManager packageManager = getPackageManager();
    Intent captureIntent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);

    File photoFile;
    try 
        uploadableImage.makeCameraTempFile();
        photoFile = uploadableImage.getCameraTempFile();
        uploadableImage.setCameraFilepath(photoFile.getAbsolutePath());
     catch (IOException ex) 
        // Error occurred while creating the File
        photoFile = null;
        uploadableImage.invalidate();
        Log.e(TAG, "Error while creating the temporary file needed for image capture from camera.");
        ex.printStackTrace();
    
    // Continue only if the File was successfully created
    if (photoFile != null && packageManager != null) 
        final List<ResolveInfo> listCam = packageManager.queryIntentActivities(captureIntent, 0);
        for (ResolveInfo res : listCam) 
            // Create the File where the photo should go
            if (res.activityInfo == null) 
                continue;
            
            final String packageName = res.activityInfo.packageName;
            final Intent intent = new Intent(captureIntent);
            intent.setComponent(new ComponentName(res.activityInfo.packageName, res.activityInfo.name));
            intent.setPackage(packageName);
            intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photoFile));
            cameraIntents.add(intent);
        
     else 
        Toast.makeText(this, "Could not make temporary file.", Toast.LENGTH_LONG).show();
    

    // Filesystem.
    final Intent galleryIntent = new Intent();
    galleryIntent.setType("image/*");
    galleryIntent.setAction(Intent.ACTION_GET_CONTENT);

    // Chooser of filesystem options.
    final Intent chooserIntent = Intent.createChooser(galleryIntent, "Select Source");

    // Add the camera options.
    chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, cameraIntents.toArray(new Parcelable[cameraIntents.size()]));
    startActivityForResult(chooserIntent, REQUEST_IMAGE_CAPTURE);

onActivityResult

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) 
    if (resultCode == RESULT_OK && requestCode == REQUEST_IMAGE_CAPTURE) 
        if (tempUpFile != null)
            tempUpFile.invalidate();
        
        if (data == null) 
            Log.d(TAG, "Data was null -- source was from camera");
            uploadableImage.preserveCameraFile();
            setPic();
         else 

            Uri selectedImageUri = data.getData();
            if (selectedImageUri == null) 
                Log.e(TAG, "An error that should never occur has occurred -- selectedImageUri was null when trying to get the data from the Image Capture activity result.");
                Toast.makeText(this, "Unexpected error when trying to get an image from the gallery or file manager.", Toast.LENGTH_LONG).show();
                return;
            
            uploadableImage.setSourceImageUri(selectedImageUri, imageButton);
            setPic();  // only do this if doing this serially.
        
     else if (resultCode == RESULT_CANCELED)
        uploadableImage.invalidate();
        if (tempUpFile != null)
            uploadableImage = tempUpFile;
        
        setPic();
    

UploadableImage#setSourceImageUri

public void setSourceImageUri(Uri selectedImageUri, final ImageView iView) 

    // Important!! - clean up after ourselves!
    deleteFile(this.cameraFile);
    this.cameraFile = null;

    InputStream tempInStream = null;
    OutputStream tempOutStream = null;
    try 
        this.cacheFile = createFile();
        tempInStream = context.getContentResolver().openInputStream(selectedImageUri);
        tempOutStream = new FileOutputStream(this.cacheFile);
     catch (IOException e) 
        Toast.makeText(context, "Error while trying to read the selected image.", Toast.LENGTH_LONG).show();
        Log.e(TAG, "IOException while trying to copy the selected image to our cache file");
        e.printStackTrace();
    

    if (tempInStream == null || tempOutStream == null)
        Toast.makeText(context, "Error while trying to "+(tempInStream == null ? "read" :"store")+" the selected image.", Toast.LENGTH_LONG).show();
        Log.e(TAG, "Error while trying to get the " + (tempInStream == null ? "input" : "output") + " stream");
        return;
    

    // Works, but makes the UI hang on a black screen while waiting for the resource.
    try 
        copy(tempInStream, tempOutStream);
    catch (IOException e)
        Toast.makeText(context, "Error while trying to read the selected image.", Toast.LENGTH_LONG).show();
        Log.e(TAG, "IOException while trying to copy the selected image to our cache file");
        e.printStackTrace();
    

    /*
    // Works, but leaves the IntentChooser open
    final InputStream inStream = tempInStream;
    final OutputStream outStream = tempOutStream;
    new Thread(new Runnable() 
        public void run() 
            try 
                copy(inStream, outStream);
                // FIXME: somehow notify/display the bitmap in the UI
                // --maybe use Message and Handler? callback Interface?
             catch (IOException e) 
                Toast.makeText(context, "Error while trying to read the selected image.", Toast.LENGTH_LONG).show();
                Log.e(TAG, "IOException while trying to copy the selected image to our cache file");
                e.printStackTrace();
            
        
    ).start();
    */

编辑:

我发现这将/不会发生,具体取决于 API 级别。

不发生:

KitKat (Nexus 4) ICS MR1(仿真器)

确实发生:

KitKat(模拟器) ICS MR1(Galaxy Tab 10.1)

编辑 2:

这里是一些不应该发生的截图;图片来自 KitKat 模拟器。

开始活动

从图库或其他资源中选择一张图片

onActivityResult 返回并且选择器保持打开状态

这就是它应该如何处理 onActivityResult

【问题讨论】:

@PearsonArtPhoto 截图已添加。 【参考方案1】:

感觉很愚蠢,但我找到了解决方案。

问题出在第二步:“Intent 选择器自动打开...”

为了做到这一点,我检查了位图是否可用,如果没有,那么它应该显示占位符图像,然后打开 Intent 选择器。我在onStart 方法中有这个逻辑,它通常在onActivityResult 之后运行。但是,因为这一切都是异步发生的,所以我不能依赖这个顺序。

我必须做的是放入一个布尔标志,它可以告诉我我们是否正在等待位图,然后如果我们等待则不要打开选择器。

【讨论】:

以上是关于使用线程获取图像资源时,意图选择器保持打开状态的主要内容,如果未能解决你的问题,请参考以下文章

通过意图打开邮件应用程序不会打开选择器

如何仅从 Google Photos 应用程序中选择图像? Android 谷歌照片意图

当用户取消意图选择器时是不是有事件?

相机文件选择器未在 WebView android 中打开

获取选择选择器意图谷歌驱动器文件文件没有谷歌驱动器Api集成Android

Android 图像选择器和裁剪器