如何将相机应用中的照片保存到公共目录?

5
我希望实现以下功能:
1. 从 MyApp 中通过一个 intent 启动手机的相机应用程序 2. 手机的相机应用程序将照片保存在公共目录中 (DCIM 或 DCIM/Camera) 3. 保存的照片使用我选择的文件名。 4. 保存的照片在图库应用程序中可用 5. 相机应用程序返回焦点到 MyApp 后,我的应用程序编辑 Exif。
在我的 MainActivity 中,我使用布尔变量 USE_ANDROID_EXTERNAL_STORAGE_PUBLIC_DIRECTORY 在以下两个选项之间进行切换:
- 将照片文件保存到 ExternalFilesDir (在我的情况下:/storage/emulated/0/Android/data/pub.openbook.labellor/files/Pictures) - 将照片文件保存到Public Directory(在我的情况下为 /storage/emulated/0/DCIM)。
相机应用程序可以成功将照片文件保存到 ExternalFilesDir,但无法将文件保存到其公共目录中。由于这是在相机应用程序内发生的,因此我无法进行调试。
我的问题如下:
- 是否可能让相机应用程序保存到公共目录 DCIM? - 如果是,怎样做? - 如何使我的照片在图库应用程序中可见?
(我的函数 galleryAddPic() 可以正常完成但未达到目的。照片仍然在图库应用程序中不可见。)
我正在使用:
- AndroidStudio 4.0.1 - SDK平台 Android 10.0+(R), API 30 - 我的测试设备具有 android.os.Build.VERSION.SDK_INT 23
package pub.openbook.labellor;

import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;

import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.snackbar.Snackbar;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.core.content.FileProvider;

import android.os.Environment;
import android.provider.MediaStore;
import android.view.View;

import android.view.Menu;
import android.view.MenuItem;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.util.Log;
import android.widget.LinearLayout;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;

import static android.os.Environment.getExternalStoragePublicDirectory;


public class MainActivity extends AppCompatActivity {

    static final int REQUEST_IMAGE_CAPTURE = 1;
    static final int REQUEST_TAKE_PHOTO = 1;
    static final boolean USE_ANDROID_EXTERNAL_STORAGE_PUBLIC_DIRECTORY = true;

    private static final String IMAGES_FOLDER_NAME = "Camera";
    String stPathToJpgFile;

    View mainCoordinatorLayout;
    private static final String logTag = MainActivity.class.getSimpleName();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // initialize layout:
        setContentView(R.layout.activity_main);
        // initialize variables:
        mainCoordinatorLayout = (View) findViewById(R.id.main_coordinator_layout);
        Toolbar toolbar = findViewById(R.id.toolbar);
        FloatingActionButton fab = findViewById(R.id.fab);
        // initialize layout & and components:
        setSupportActionBar(toolbar);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                dispatchTakePictureIntent(view);
            }
        });


        LinearLayout checkboxContainer = (LinearLayout) findViewById(R.id.checkbox_container);
        CheckBox cb = new CheckBox(this);
        cb.setText("Tutlane");
        cb.setChecked(true);
        checkboxContainer.addView(cb);
        cb = new CheckBox(this);
        cb.setText("Another");
        cb.setChecked(false);
        checkboxContainer.addView(cb);
        cb = new CheckBox(this);
        cb.setText("Label threee");
        cb.setChecked(false);
        checkboxContainer.addView(cb);

        Log.d(logTag, "============ android.os.Build.VERSION.SDK_INT " + android.os.Build.VERSION.SDK_INT + "=====================");
    }

    @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_main, 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);
    }
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        Log.d(logTag, "onActivityResult() back from take picture intent;");

        if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
            Log.d(logTag, "onActivityResult() photo resides at"+stPathToJpgFile);
            galleryAddPic(stPathToJpgFile);
            ImageView imageView = (ImageView) findViewById(R.id.thumbnail_view);
            Context mContext;
            mContext = (Context)this;
            Bitmap d = new BitmapDrawable(mContext.getResources() , stPathToJpgFile).getBitmap();
            if (null != d) {

                int nh = (int) ( d.getHeight() * (512.0 / d.getWidth()) );
                Bitmap scaled = Bitmap.createScaledBitmap(d, 512, nh, true);
                imageView.setImageBitmap(scaled);
            }
        }
    }

    /*
    Use resident Camera App to take picture and save (filename has labels)
     */
    private void dispatchTakePictureIntent(View view) {

        // take picture with resident camera app:
        Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        // Ensure that there's a camera activity to handle the intent
        if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
            // Create the File where the photo should go
            File photoFile = null;
            try {
                photoFile = jpgFile();
            } catch (IOException ex) {
                // Error occurred while creating the File
                Log.e(logTag, "jpgFile() throws IOException");
            }
            Log.d(logTag, "dispatchTakePictureIntent() photoFile = "+photoFile );
            // Continue only if the File was successfully created
            if (photoFile != null) {
                Uri photoURI = FileProvider.getUriForFile(this,
                        BuildConfig.APPLICATION_ID + ".provider",
                        photoFile);
                takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
                startActivityForResult(takePictureIntent, REQUEST_TAKE_PHOTO);
            }
        }
    }

    /*

     */
    private File jpgFile() throws IOException {
        // File object to be returned:
        File jpgFile;

        // Create an image file name with datestamp and labels:
        String timeStamp = new SimpleDateFormat("yyMMdd").format(new Date());
        String imageFileName = timeStamp + ".label1";
        Log.d(logTag, "jpgFile() imageFileName = "+imageFileName );

        File externalFilesDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
        Log.d(logTag, "jpgFile() externalFilesDir = "+externalFilesDir );

        // create the File object:
        if (USE_ANDROID_EXTERNAL_STORAGE_PUBLIC_DIRECTORY) {
            // Since getExternalStoragePublicDirectory() has been deprecated in Build.VERSION_CODES.Q and higher:
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                Log.d(logTag, "jpgFile() Build.VERSION_CODES.Q or higher");
                // get the Activity Context:
                Context mContext;
                mContext = (Context)this;
                ContentResolver resolver = mContext.getContentResolver();
                ContentValues contentValues = new ContentValues();
                contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, imageFileName);
                contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg");
                contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, "DCIM/" + IMAGES_FOLDER_NAME);
                Uri imageUri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);
                jpgFile = new File(imageUri.getPath());
            } else {
                Log.d(logTag, "jpgFile() lower than Build.VERSION_CODES.Q");
                String stStorageDir = Environment.getExternalStoragePublicDirectory(
                        Environment.DIRECTORY_DCIM).toString() + File.separator + IMAGES_FOLDER_NAME;
                // make sure the directory string points to a directory that exists:
                if (!new File(stStorageDir).exists()) { new File(stStorageDir).mkdir(); }
                jpgFile = new File(stStorageDir, imageFileName + ".jpg");
            }
        } else {
            //write photos to directory private to this app:
            File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
            jpgFile = new File(storageDir, imageFileName + ".jpg");
        }
        // Save a file: path for use with ACTION_VIEW intents
        stPathToJpgFile = jpgFile.getAbsolutePath();
        return jpgFile;
    }
    /*
    Invoke the system's media scanner to add your photo to the Media Provider's database,
     making it available in the Android Gallery application and to other apps.
     */
    private void galleryAddPic (String stPathToPicFile) {
        Log.d(logTag, "galleryAddPic() stPathToPicFile "+stPathToPicFile);
        Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
        File f = new File(stPathToPicFile);
        Uri contentUri = Uri.fromFile(f);
        mediaScanIntent.setData(contentUri);
        this.sendBroadcast(mediaScanIntent);
    }

    public Uri addImageToGallery(ContentResolver cr, String imgType, File filepath) {
        ContentValues values = new ContentValues();
        values.put(MediaStore.Images.Media.TITLE, "player");
        values.put(MediaStore.Images.Media.DISPLAY_NAME, "player");
        values.put(MediaStore.Images.Media.DESCRIPTION, "");
        values.put(MediaStore.Images.Media.MIME_TYPE, "image/" + imgType);
        values.put(MediaStore.Images.Media.DATE_ADDED, System.currentTimeMillis());
        values.put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis());
        values.put(MediaStore.Images.Media.DATA, filepath.toString());

        return cr.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
    }



}

你确定外部存储是否已正确挂载?因为根据文档,如果没有挂载,它将会默默失败。 - cgb_pandey
是的,我相信外部存储已经正确挂载。毕竟,MyApp成功地将照片保存到ExternalFilesDir(也在外部存储上)。此外,我可以使用文件管理器应用程序访问这两个目录。 - Ivo Renkema
1个回答

2
首先,相册应用无法看到您的照片是因为它保存在这里:/storage/emulated/0/Android/data/pub.openbook.labellor/files/Pictures
data/ 文件夹是您的应用程序的私有路径,MediaScanner 将忽略它。如果不希望使用相册应用查看照片,则只应将照片保存在那里。我建议您查看data and file storage overview 以了解更多信息。

是否可以让相机应用程序保存到公共目录 DCIM 中?

它不一定是 "DCIM",但是确实可以将图像保存在设备的默认图片文件夹中。事实上,这是相机应用程序应该使用的通常文件夹。

如何使我的照片在相册应用程序中可见?

如果照片文件保存在设备的默认图片文件夹中,则在调用 MediaScanner 后,任何相册应用程序都应该能够看到它(您的实现是正确的)。
如果是这样,怎么做呢?
与其编写自定义示例,我想指向拍照的官方文档。我链接的部分应该正是你所需要的,其中详细解释了所有必需的步骤。基本上,你需要删除当前的jpgFile()函数,并使用文档中建议的实现来替换它。
请注意,在遵循推荐的实现时,你不需要if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)这些东西,因为你正在使用MediaStore(它也适用于早期API)。

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