如何将照片相机应用程序保存在 Public 目录中
How to save photo Camera-App in Public directory
我想实现以下目标:
- 在 MyApp 中,通过
intent
启动 phone 的相机应用
- Phone 的相机应用将照片保存在 public 目录(DCIM 或 DCIM/Camera)
- 保存的照片有我选择的文件名。
- 已保存的照片在相册应用程序中可用
- 我的应用在相机应用 return 聚焦到 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 中成功保存照片文件,但无法将文件保存到其 Public 目录中。由于这发生在相机应用程序中,我无法调试它。
我的问题:
- 是否可以将相机应用程序保存到 Public 目录 DCIM?
- 如果是,怎么做?
- 如何让我的照片在图库应用程序中可见?
(我的函数 galleryAddPic()
完成而没有崩溃,但没有达到目的。照片在图库应用程序中仍然不可见。)
我正在与:
- Android工作室 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);
}
}
首先,相册应用看不到您的照片是因为它保存在此处:
/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)。
我想实现以下目标:
- 在 MyApp 中,通过
intent
启动 phone 的相机应用
- Phone 的相机应用将照片保存在 public 目录(DCIM 或 DCIM/Camera)
- 保存的照片有我选择的文件名。
- 已保存的照片在相册应用程序中可用
- 我的应用在相机应用 return 聚焦到 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 中成功保存照片文件,但无法将文件保存到其 Public 目录中。由于这发生在相机应用程序中,我无法调试它。
我的问题:
- 是否可以将相机应用程序保存到 Public 目录 DCIM?
- 如果是,怎么做?
- 如何让我的照片在图库应用程序中可见?
(我的函数 galleryAddPic()
完成而没有崩溃,但没有达到目的。照片在图库应用程序中仍然不可见。)
我正在与:
- Android工作室 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);
}
}
首先,相册应用看不到您的照片是因为它保存在此处:
/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)。