使用文件对象在 Android (API > 24) 中捕获和保存视频?
Capturing and Saving Videos in Android (API > 24) using File object?
我想录制一段视频,录制完成后保存 5 秒。我有以下代码在 API < 24 上工作正常,但是对于 API > 24 我得到了错误。
代码:
public void startRecording()
{
File mediaFile = new
File(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)
+ "/myvideo.mp4");
Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
intent.putExtra(MediaStore.EXTRA_DURATION_LIMIT,5);
fileUri = Uri.fromFile(mediaFile);
intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri);
startActivityForResult(intent, VIDEO_CAPTURE);
}
protected void onActivityResult(int requestCode,
int resultCode, Intent data) {
if (requestCode == VIDEO_CAPTURE) {
if (resultCode == RESULT_OK) {
Toast.makeText(this, "Video has been saved to:\n" +
data.getData(), Toast.LENGTH_LONG).show();
} else if (resultCode == RESULT_CANCELED) {
Toast.makeText(this, "Video recording cancelled.",
Toast.LENGTH_LONG).show();
} else {
Toast.makeText(this, "Failed to record video",
Toast.LENGTH_LONG).show();
}
}
}
错误:
2019-10-08 01:15:43.483 21573-21573/com.mobilecomputing.learn2sign E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.mobilecomputing.learn2sign, PID: 21573
android.os.FileUriExposedException: file:///storage/emulated/0/myvideo.mp4 exposed beyond app through ClipData.Item.getUri()
at android.os.StrictMode.onFileUriExposed(StrictMode.java:1978)
at android.net.Uri.checkFileUriExposed(Uri.java:2371)
at android.content.ClipData.prepareToLeaveProcess(ClipData.java:963)
at android.content.Intent.prepareToLeaveProcess(Intent.java:10228)
at android.content.Intent.prepareToLeaveProcess(Intent.java:10213)
at android.app.Instrumentation.execStartActivity(Instrumentation.java:1854)
at android.app.Activity.startActivityForResult(Activity.java:4599)
at androidx.fragment.app.FragmentActivity.startActivityForResult(FragmentActivity.java:676)
at android.app.Activity.startActivityForResult(Activity.java:4557)
at androidx.fragment.app.FragmentActivity.startActivityForResult(FragmentActivity.java:663)
at com.mobilecomputing.learn2sign.PlayHelpVideo.startRecording(PlayHelpVideo.java:125)
at com.mobilecomputing.learn2sign.PlayHelpVideo.onClick(PlayHelpVideo.java:46)
at android.view.View.performClick(View.java:6669)
at android.view.View.performClickInternal(View.java:6638)
at android.view.View.access00(View.java:789)
at android.view.View$PerformClick.run(View.java:26145)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6898)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:537)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
我检查了一下,错误是因为文件对象的语法,API > 24 不支持它。
虽然我可以找到其他有效的代码,但我很好奇这段代码是否有一个小的调整,可以让它也适用于 API > 24。也许在同一条线上。
谁能帮我解决这个问题?
编辑:
我试过:https://inthecheesefactory.com/blog/how-to-share-access-to-file-with-fileprovider-on-android-nougat/en 但是这样做也会使应用程序在 API 22 上崩溃。
更新:
Post 根据回答的答案之一进行更改,代码适用于 API < 24 或更少,不会正常崩溃。然而在 API > 24 虽然它没有崩溃,但是 createImageFile() 函数抛出异常:
private File createImageFile() throws IOException {
// Create an image file name
String imageFileName = "myvideo";
File storageDir = new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_DCIM), "Camera");
File image = File.createTempFile(
imageFileName, /* prefix */
".mp4", /* suffix */
storageDir /* directory */
);
// Save a file: path for use with ACTION_VIEW intents
Log.v("myTag","FAB create image");
mCurrentPhotoPath = "file:" + image.getAbsolutePath();
return image;
}
这是从以下函数调用的:
public void startRecording()
{
Log.v("myTag","FAB recording");
File mediaFile = null;
try {
mediaFile = createImageFile();
} catch (IOException ex) {
Log.v("myTag","Exception");
return;
}
Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
intent.putExtra(MediaStore.EXTRA_DURATION_LIMIT,5);
//fileUri = Uri.fromFile(mediaFile);
fileUri = FileProvider.getUriForFile(PlayHelpVideo.this,
BuildConfig.APPLICATION_ID + ".provider",
mediaFile);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri);
startActivityForResult(intent, VIDEO_CAPTURE);
}
您可以使用 FileProvider
class 授予对特定文件或文件夹的访问权限,以便其他应用程序可以访问它们。创建您自己的 class 继承 FileProvider
以确保您的 FileProvider
不与此处所述的导入依赖项中声明的 FileProviders
冲突。
将 file://
URI 替换为 content://
URI 的步骤:
Add a class extending FileProvider
public class GenericFileProvider extends FileProvider {}
Add a FileProvider <provider>
tag in AndroidManifest.xml
under
<application>
tag. Specify a unique authority for the
android:authorities
attribute to avoid conflicts, imported
dependencies might specify ${applicationId}.provider
and other
commonly used authorities.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
...
<application
...
<provider
android:name=".GenericFileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"/>
</provider>
</application>
</manifest>
然后在 res/xml
文件夹中创建一个 provider_paths.xml
文件。如果文件夹不存在,则可能需要创建该文件夹。该文件的内容如下所示。它描述了我们想共享对根文件夹 (path=".")
中名为 external_files.
的外部存储的访问权限
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="."/>
</paths>
最后一步是更改下面的代码行
fileUri = Uri.fromFile(mediaFile);
至
fileUri= FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".my.package.name.provider", mediaFile);
我想录制一段视频,录制完成后保存 5 秒。我有以下代码在 API < 24 上工作正常,但是对于 API > 24 我得到了错误。
代码:
public void startRecording()
{
File mediaFile = new
File(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)
+ "/myvideo.mp4");
Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
intent.putExtra(MediaStore.EXTRA_DURATION_LIMIT,5);
fileUri = Uri.fromFile(mediaFile);
intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri);
startActivityForResult(intent, VIDEO_CAPTURE);
}
protected void onActivityResult(int requestCode,
int resultCode, Intent data) {
if (requestCode == VIDEO_CAPTURE) {
if (resultCode == RESULT_OK) {
Toast.makeText(this, "Video has been saved to:\n" +
data.getData(), Toast.LENGTH_LONG).show();
} else if (resultCode == RESULT_CANCELED) {
Toast.makeText(this, "Video recording cancelled.",
Toast.LENGTH_LONG).show();
} else {
Toast.makeText(this, "Failed to record video",
Toast.LENGTH_LONG).show();
}
}
}
错误:
2019-10-08 01:15:43.483 21573-21573/com.mobilecomputing.learn2sign E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.mobilecomputing.learn2sign, PID: 21573
android.os.FileUriExposedException: file:///storage/emulated/0/myvideo.mp4 exposed beyond app through ClipData.Item.getUri()
at android.os.StrictMode.onFileUriExposed(StrictMode.java:1978)
at android.net.Uri.checkFileUriExposed(Uri.java:2371)
at android.content.ClipData.prepareToLeaveProcess(ClipData.java:963)
at android.content.Intent.prepareToLeaveProcess(Intent.java:10228)
at android.content.Intent.prepareToLeaveProcess(Intent.java:10213)
at android.app.Instrumentation.execStartActivity(Instrumentation.java:1854)
at android.app.Activity.startActivityForResult(Activity.java:4599)
at androidx.fragment.app.FragmentActivity.startActivityForResult(FragmentActivity.java:676)
at android.app.Activity.startActivityForResult(Activity.java:4557)
at androidx.fragment.app.FragmentActivity.startActivityForResult(FragmentActivity.java:663)
at com.mobilecomputing.learn2sign.PlayHelpVideo.startRecording(PlayHelpVideo.java:125)
at com.mobilecomputing.learn2sign.PlayHelpVideo.onClick(PlayHelpVideo.java:46)
at android.view.View.performClick(View.java:6669)
at android.view.View.performClickInternal(View.java:6638)
at android.view.View.access00(View.java:789)
at android.view.View$PerformClick.run(View.java:26145)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6898)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:537)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
我检查了一下,错误是因为文件对象的语法,API > 24 不支持它。
虽然我可以找到其他有效的代码,但我很好奇这段代码是否有一个小的调整,可以让它也适用于 API > 24。也许在同一条线上。
谁能帮我解决这个问题?
编辑:
我试过:https://inthecheesefactory.com/blog/how-to-share-access-to-file-with-fileprovider-on-android-nougat/en 但是这样做也会使应用程序在 API 22 上崩溃。
更新:
Post 根据回答的答案之一进行更改,代码适用于 API < 24 或更少,不会正常崩溃。然而在 API > 24 虽然它没有崩溃,但是 createImageFile() 函数抛出异常:
private File createImageFile() throws IOException {
// Create an image file name
String imageFileName = "myvideo";
File storageDir = new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_DCIM), "Camera");
File image = File.createTempFile(
imageFileName, /* prefix */
".mp4", /* suffix */
storageDir /* directory */
);
// Save a file: path for use with ACTION_VIEW intents
Log.v("myTag","FAB create image");
mCurrentPhotoPath = "file:" + image.getAbsolutePath();
return image;
}
这是从以下函数调用的:
public void startRecording()
{
Log.v("myTag","FAB recording");
File mediaFile = null;
try {
mediaFile = createImageFile();
} catch (IOException ex) {
Log.v("myTag","Exception");
return;
}
Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
intent.putExtra(MediaStore.EXTRA_DURATION_LIMIT,5);
//fileUri = Uri.fromFile(mediaFile);
fileUri = FileProvider.getUriForFile(PlayHelpVideo.this,
BuildConfig.APPLICATION_ID + ".provider",
mediaFile);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri);
startActivityForResult(intent, VIDEO_CAPTURE);
}
您可以使用 FileProvider
class 授予对特定文件或文件夹的访问权限,以便其他应用程序可以访问它们。创建您自己的 class 继承 FileProvider
以确保您的 FileProvider
不与此处所述的导入依赖项中声明的 FileProviders
冲突。
将 file://
URI 替换为 content://
URI 的步骤:
Add a class extending FileProvider
public class GenericFileProvider extends FileProvider {}
Add a FileProvider
<provider>
tag inAndroidManifest.xml
under<application>
tag. Specify a unique authority for theandroid:authorities
attribute to avoid conflicts, imported dependencies might specify${applicationId}.provider
and other commonly used authorities.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
...
<application
...
<provider
android:name=".GenericFileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"/>
</provider>
</application>
</manifest>
然后在 res/xml
文件夹中创建一个 provider_paths.xml
文件。如果文件夹不存在,则可能需要创建该文件夹。该文件的内容如下所示。它描述了我们想共享对根文件夹 (path=".")
中名为 external_files.
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="."/>
</paths>
最后一步是更改下面的代码行
fileUri = Uri.fromFile(mediaFile);
至
fileUri= FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".my.package.name.provider", mediaFile);