如何将照片相机应用程序保存在 Public 目录中

How to save photo Camera-App in Public directory

我想实现以下目标:

  1. 在 MyApp 中,通过 intent
  2. 启动 phone 的相机应用
  3. Phone 的相机应用将照片保存在 public 目录(DCIM 或 DCIM/Camera)
  4. 保存的照片有我选择的文件名。
  5. 已保存的照片在相册应用程序中可用
  6. 我的应用在相机应用 return 聚焦到 MyApp 后编辑 Exif。

在我的 MainActivity 中,我使用一个布尔值 USE_ANDROID_EXTERNAL_STORAGE_PUBLIC_DIRECTORY 来切换:

相机应用程序将在 ExternalFilesDir 中成功保存照片文件,但无法将文件保存到其 Public 目录中。由于这发生在相机应用程序中,我无法调试它。

我的问题:

(我的函数 galleryAddPic() 完成而没有崩溃,但没有达到目的。照片在图库应用程序中仍然不可见。)

我正在与:

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);
    }



}

首先,相册应用看不到您的照片是因为它保存在此处: /storage/emulated/0/Android/data/pub.openbook.labellor/files/Pictures

data/ 文件夹是您应用程序的私有路径,MediaScanner 会忽略它。如果照片不适合通过图库应用程序查看,您应该只将照片保存在那里。我建议查看 data and file storage overview 以了解更多信息。

Is it even possible to have the Camera App save to Public Directory DCIM?

不一定是“DCIM”,但device's default pictures folder中的图像是可以保存的,是的。事实上,这是您应该用于相机应用程序的常用文件夹。

How do I make my the photo visible in the Gallery App?

如果照片文件保存在设备的默认图片文件夹中,则在调用 MediaScanner 后,它也应该在任何图库应用程序中可见(您的实现没问题)。

If so, how?

我不想编写自定义示例,而是想向您指出 official docs for taking photos。我链接的部分应该正是您所需要的,并对所有必需的步骤进行了详尽的解释。基本上,您想删除当前的 jpgFile() 函数并将其替换为文档中建议的实现。

请注意,在遵循推荐的实现时,您不需要 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) 东西,因为您使用的是 MediaStore(它也适用于早期的 API)。