如何将文件保存到存储器(Android 13 – API 33)

huangapple go评论65阅读模式
英文:

How to save file to storage (android 13 - API33)

问题

I'm doing screen recording function for my app. So, I need save video mp4 files to external storage. My function active on API 29 and below, but not working on API 32 and above. Please show me steps to resolve this problem.

我正在为我的应用程序实现屏幕录制功能。因此,我需要将视频mp4文件保存到外部存储器。我的功能在API 29及以下版本上运行正常,但在API 32及以上版本上不起作用。请向我展示解决此问题的步骤。

I followed to this source code: https://github.com/Truiton/ScreenCapture

我遵循了这个源代码:https://github.com/Truiton/ScreenCapture

MainActivity.kt

class MainActivity : AppCompatActivity() {
    // 省略其他代码...
}

AndroidManifest.xml

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
    tools:ignore="ScopedStorage" />

(请注意,这是源代码的一部分,不能直接运行。您需要按照源代码提供的步骤来使用它。)

英文:

I'm doing screen recording function for my app. So, I need save video mp4 files to external storage. My function active on API 29 and below, but not working on API 32 and above. Please show me steps to resolve this problem.

I followed to this source code: https://github.com/Truiton/ScreenCapture

MainActivity.kt

class MainActivity : AppCompatActivity() {
private var mScreenDensity = 0
private var mProjectionManager: MediaProjectionManager? = null
private var mMediaProjection: MediaProjection? = null
private var mVirtualDisplay: VirtualDisplay? = null
private var mMediaProjectionCallback: MediaProjectionCallback? = null
private var mMediaRecorder: MediaRecorder? = null
private lateinit var binding: ActivityMainBinding
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val metrics = DisplayMetrics()
windowManager.defaultDisplay.getMetrics(metrics)
mScreenDensity = metrics.densityDpi
mMediaRecorder = MediaRecorder()
mProjectionManager = getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
binding.toggle.setOnClickListener { v: View? -&gt;
if ((ContextCompat.checkSelfPermission(
this@MainActivity,
Manifest.permission.WRITE_EXTERNAL_STORAGE
) + ContextCompat
.checkSelfPermission(
this@MainActivity,
Manifest.permission.RECORD_AUDIO
) + ContextCompat.checkSelfPermission(
this@MainActivity,
MANAGE_EXTERNAL_STORAGE
))
!= PackageManager.PERMISSION_GRANTED
) {
if (ActivityCompat.shouldShowRequestPermissionRationale(
this@MainActivity,
Manifest.permission.WRITE_EXTERNAL_STORAGE
) ||
ActivityCompat.shouldShowRequestPermissionRationale(
this@MainActivity,
Manifest.permission.RECORD_AUDIO
) || ActivityCompat.shouldShowRequestPermissionRationale(
this@MainActivity,
MANAGE_EXTERNAL_STORAGE
)
) {
binding.toggle.isChecked = false
Snackbar.make(
findViewById(android.R.id.content), R.string.label_permissions,
Snackbar.LENGTH_INDEFINITE
).setAction(&quot;ENABLE&quot;,
View.OnClickListener { v1: View? -&gt;
ActivityCompat.requestPermissions(
this@MainActivity,
arrayOf(
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.RECORD_AUDIO,
MANAGE_EXTERNAL_STORAGE
),
REQUEST_PERMISSIONS
)
}).show()
} else {
ActivityCompat.requestPermissions(
this@MainActivity,
arrayOf(
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.RECORD_AUDIO,
MANAGE_EXTERNAL_STORAGE
),
REQUEST_PERMISSIONS
)
}
} else {
onToggleScreenShare(v)
}
}
}
public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode != REQUEST_CODE) {
Log.e(TAG, &quot;Unknown request code: $requestCode&quot;)
return
}
if (resultCode != RESULT_OK) {
Toast.makeText(
this,
&quot;Screen Cast Permission Denied&quot;, Toast.LENGTH_SHORT
).show()
binding.toggle.isChecked = false
return
}
mMediaProjectionCallback = MediaProjectionCallback()
mMediaProjection = mProjectionManager!!.getMediaProjection(resultCode, data!!)
mMediaProjection?.registerCallback(mMediaProjectionCallback, null)
mVirtualDisplay = createVirtualDisplay()
mMediaRecorder!!.start()
}
fun onToggleScreenShare(view: View?) {
if ((view as ToggleButton?)!!.isChecked) {
initRecorder()
shareScreen()
} else {
mMediaRecorder!!.stop()
mMediaRecorder!!.reset()
Log.v(TAG, &quot;Stopping Recording&quot;)
stopScreenSharing()
}
}
private fun shareScreen() {
if (mMediaProjection == null) {
startActivityForResult(mProjectionManager!!.createScreenCaptureIntent(), REQUEST_CODE)
return
}
mVirtualDisplay = createVirtualDisplay()
mMediaRecorder!!.start()
}
private fun createVirtualDisplay(): VirtualDisplay {
return mMediaProjection!!.createVirtualDisplay(
&quot;MainActivity&quot;,
DISPLAY_WIDTH, DISPLAY_HEIGHT, mScreenDensity,
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
mMediaRecorder!!.surface, null /*Callbacks*/, null /*Handler*/
)
}
@SuppressLint(&quot;SimpleDateFormat&quot;)
private fun initRecorder() {
try {
mMediaRecorder!!.setAudioSource(MediaRecorder.AudioSource.MIC)
mMediaRecorder!!.setVideoSource(MediaRecorder.VideoSource.SURFACE)
mMediaRecorder!!.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP)
mMediaRecorder!!.setOutputFile(
Environment
.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
.toString() + &quot;/&quot; + &quot;Prank_Record_&quot; + SimpleDateFormat(&quot;dd-MM-yyyy-hh_mm_ss&quot;).format(
Date()
) + &quot;.mp4&quot;
)
mMediaRecorder!!.setVideoSize(DISPLAY_WIDTH, DISPLAY_HEIGHT)
mMediaRecorder!!.setVideoEncoder(MediaRecorder.VideoEncoder.H264)
mMediaRecorder!!.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB)
mMediaRecorder!!.setVideoEncodingBitRate(512 * 1000)
mMediaRecorder!!.setVideoFrameRate(30)
val rotation = windowManager.defaultDisplay.rotation
val orientation = ORIENTATIONS[rotation + 90]
mMediaRecorder!!.setOrientationHint(orientation)
mMediaRecorder!!.prepare()
} catch (e: IOException) {
e.printStackTrace()
}
}
private inner class MediaProjectionCallback : MediaProjection.Callback() {
override fun onStop() {
if (binding.toggle.isChecked) {
binding.toggle.isChecked = false
mMediaRecorder!!.stop()
mMediaRecorder!!.reset()
Log.v(TAG, &quot;Recording Stopped&quot;)
}
mMediaProjection = null
stopScreenSharing()
}
}
private fun stopScreenSharing() {
if (mVirtualDisplay == null) {
return
}
mVirtualDisplay!!.release()
//mMediaRecorder.release(); //If used: mMediaRecorder object cannot
// be reused again
destroyMediaProjection()
}
public override fun onDestroy() {
super.onDestroy()
destroyMediaProjection()
}
private fun destroyMediaProjection() {
if (mMediaProjection != null) {
mMediaProjection!!.unregisterCallback(mMediaProjectionCallback)
mMediaProjection!!.stop()
mMediaProjection = null
}
Log.i(TAG, &quot;MediaProjection Stopped&quot;)
}
@RequiresApi(Build.VERSION_CODES.R)
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array&lt;String&gt;,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when (requestCode) {
REQUEST_PERMISSIONS -&gt; {
if (true) {
onToggleScreenShare(binding.toggle)
} else {
binding.toggle.isChecked = false
Snackbar.make(
findViewById(android.R.id.content), R.string.label_permissions,
Snackbar.LENGTH_INDEFINITE
).setAction(&quot;ENABLE&quot;,
View.OnClickListener {
val intent = Intent()
intent.action = Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION
intent.addCategory(Intent.CATEGORY_DEFAULT)
intent.data = Uri.parse(&quot;package:$packageName&quot;)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY)
intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
startActivity(intent)
}).show()
}
return
}
}
}
companion object {
private const val TAG = &quot;MainActivity&quot;
private const val REQUEST_CODE = 1000
private const val DISPLAY_WIDTH = 720
private const val DISPLAY_HEIGHT = 1280
private val ORIENTATIONS = SparseIntArray()
private const val REQUEST_PERMISSIONS = 10
init {
ORIENTATIONS.append(Surface.ROTATION_0, 90)
ORIENTATIONS.append(Surface.ROTATION_90, 0)
ORIENTATIONS.append(Surface.ROTATION_180, 270)
ORIENTATIONS.append(Surface.ROTATION_270, 180)
}
}

}

AndroidManifest.xml

 &lt;uses-permission android:name=&quot;android.permission.READ_EXTERNAL_STORAGE&quot;/&gt;
&lt;uses-permission android:name=&quot;android.permission.WRITE_EXTERNAL_STORAGE&quot;/&gt;
&lt;uses-permission android:name=&quot;android.permission.RECORD_AUDIO&quot;/&gt;
&lt;uses-permission android:name=&quot;android.permission.MANAGE_EXTERNAL_STORAGE&quot;
tools:ignore=&quot;ScopedStorage&quot; /&gt;

答案1

得分: 2

From Api level 29 accessing external storage has been restricted in order to improve user privacy and security so if your app is working with api level 32 then I am guessing that you have added the storage permissions in manifest and also requested them at the runtime.

I have code snippet in my app that does the same function that you want.

val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
    addCategory(Intent.CATEGORY_OPENABLE)
    type = "video/mp4"
    putExtra(Intent.EXTRA_TITLE, "my_video.mp4")
}

startActivityForResult(intent, CREATE_DOCUMENT_REQUEST_CODE)

intent with the action ACTION_CREATE_DOCUMENT to create a new document. Specify the category CATEGORY_OPENABLE to allow the user to choose a location. The type specifies the MIME type of the file. In this case, we use "video/mp4". The putExtra method is used to specify the default file name.

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)

    if (requestCode == CREATE_DOCUMENT_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
        val uri: Uri? = data?.data
        if (uri != null) {
            // Save the video file to the chosen location
            saveVideoToUri(uri, context)
        }
    }
}

The result is RESULT_OK. If so, we retrieve the Uri of the chosen location and pass it to the saveVideoToUri function.

fun saveVideoToUri(uri: Uri, context: Context) {
    try {
        context.contentResolver.openOutputStream(uri)?.use { outputStream ->
            // Write the video data to the output stream
            outputStream.write(videoData)
        }
    } catch (e: IOException) {
        e.printStackTrace()
    }
}
英文:

From Api level 29 accessing external storage has been restricted in order to improve user privacy and security so if your app is working with api level 32 then I am guessing that you have added the storage permissions in manifest and also requested them at the runtime.

I have code snippet in my app that does the same function that you want .

val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = &quot;video/mp4&quot;
putExtra(Intent.EXTRA_TITLE, &quot;my_video.mp4&quot;)
}
startActivityForResult(intent, CREATE_DOCUMENT_REQUEST_CODE)

intent with the action ACTION_CREATE_DOCUMENT to create a new document.Specify the category CATEGORY_OPENABLE to allow the user to choose a location. The type specifies the MIME type of the file. In this case, we use "video/mp4". The putExtra method is used to specify the default file name.

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == CREATE_DOCUMENT_REQUEST_CODE &amp;&amp; resultCode == Activity.RESULT_OK) {
val uri: Uri? = data?.data
if (uri != null) {
// Save the video file to the chosen location
saveVideoToUri(uri)
}
}
}

The result is RESULT_OK. If so, we retrieve the Uri of the chosen location and pass it to the saveVideoToUri function.

fun saveVideoToUri(uri: Uri,context:Context) {
try {
context.contentResolver.openOutputStream(uri)?.use { outputStream -&gt;
// Write the video data to the output stream
outputStream.write(videoData)
}
} catch (e: IOException) {
e.printStackTrace()
}
}

答案2

得分: 1

  1. 你需要在清单文件中添加这两个权限,并在代码中请求用户授权。

    &lt;uses-permission android:name=&quot;android.permission.READ_MEDIA_VIDEO&quot; /&gt;
    &lt;uses-permission android:name=&quot;android.permission.READ_MEDIA_IMAGES&quot; /&gt;
    
  2. 在Android R及更高版本中,你无法在没有 MANAGE_EXTERNAL_STORAGE 权限的情况下访问存储,但你可以访问共享存储,比如 Downloads 文件夹和你的应用文件夹。

  3. 现在你可以使用以下代码在 Downloads 文件夹中创建你的应用文件夹。

     fun createAppDirectoryInDownloads(context: Context): File? {
    val downloadsDirectory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
    val appDirectory = File(downloadsDirectory, "YourAppDirectoryName")
    if (!appDirectory.exists()) {
    val directoryCreated = appDirectory.mkdir()
    if (!directoryCreated) {
    // 创建目录失败
    return null
    }
    }
    return appDirectory
    }
    
  4. 现在你可以使用以下代码在应用文件夹中创建你的视频文件。

     companion object {
    @JvmStatic
    fun createFileInAppDirectory(context: Context, fileName: String): File? {
    val appDirectory = createAppDirectoryInDownloads(context)
    if (appDirectory != null) {
    val file = File(appDirectory, fileName)
    try {
    if (!file.exists()) {
    val fileCreated = file.createNewFile()
    if (!fileCreated) {
    // 创建文件失败
    return null
    }
    }
    return file
    } catch (e: IOException) {
    e.printStackTrace()
    }
    }
    return null
    }
    }
    

它对我有效,希望你的问题也能通过这个解决。

英文:
  1. You need these two permissions in your manifest and ask the user for permissions programmatically

    &lt;uses-permission android:name=&quot;android.permission.READ_MEDIA_VIDEO&quot; /&gt;
    &lt;uses-permission android:name=&quot;android.permission.READ_MEDIA_IMAGES&quot; /&gt;
    
  2. In Android R+ you can not access storage without MANAGE_EXTERNAL_STORAGE see documentation here but you can access shared storage Like Downloads and your app folder.

  3. now you can create your folder directory using the below code

     fun createAppDirectoryInDownloads(context: Context): File? {
    val downloadsDirectory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
    val appDirectory = File(downloadsDirectory, &quot;YourAppDirectoryName&quot;)
    if (!appDirectory.exists()) {
    val directoryCreated = appDirectory.mkdir()
    if (!directoryCreated) {
    // Failed to create the directory
    return null
    }
    }
    return appDirectory
    }
    
  4. now create your video file in your directory using below code

     companion object {
    @JvmStatic
    fun createFileInAppDirectory(context: Context, fileName: String): File? {
    val appDirectory = createAppDirectoryInDownloads(context)
    if (appDirectory != null) {
    val file = File(appDirectory, fileName)
    try {
    if (!file.exists()) {
    val fileCreated = file.createNewFile()
    if (!fileCreated) {
    // Failed to create the file
    return null
    }
    }
    return file
    } catch (e: IOException) {
    e.printStackTrace()
    }
    }
    return null
    }
    

It's Working For me,I just hope your problem solve with this.

答案3

得分: 0

在 Android sdk 33 设备上,您将不会首先请求写入外部存储权限。

您默认具有对外部存储中所有公共目录的写入权限。

英文:

On an Android sdk 33 device you will not ask for write exteral storage permission to begin with.

You have write permission by default to all public directories on external storage.

huangapple
  • 本文由 发表于 2023年5月22日 18:21:52
  • 转载请务必保留本文链接:https://go.coder-hub.com/76305189.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定