共有メモリにファイルを保存したい場合があります。そのためには
SAF(Storage Access Framework)を利用できます。ファイル保存に使うのは ACTION_CREATE_DOCUMENTで、取り出したい場合は ACTION_OPEN_DOCUMENT を使用
Android Studio
2021.1.1
2021.1.1
SAF, Storage Access Framework
SAFを使うことによりコンテンツを容易に参照できるようになりました。
- ドキュメント ストレージ プロバイダ全体から簡単にドキュメント、画像、その他のファイルを参照して開くことができる
- 標準の Picker UI により、アプリやプロバイダを通じて一貫性のある方法でファイルにアクセスできる
Intent ACTION_CREATE
テキストファイルをSAFを使って保存します。
IntentはACTION_CREATE_DOCUMENT
setTypeでMIMEの設定
EXTRA_TITLEでは名前を設定する時のデフォルト候補。
1 2 3 4 5 6 |
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT) intent.addCategory(Intent.CATEGORY_OPENABLE) intent.type = "text/plain" intent.putExtra(Intent.EXTRA_TITLE, "text_file.txt") resultLauncher.launch(intent) |
registerForActivityResultを使ってUriを受け取り、そのUriに出力していきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
private var resultLauncher = registerForActivityResult( StartActivityForResult() ) { result: ActivityResult -> if (result.resultCode == AppCompatActivity.RESULT_OK) { val resultData = result.data if (resultData != null) { val uri = resultData.data // 書き込む文字列 val str = "Storage Access Framework\n" try { contentResolver.openOutputStream(uri!!).use { outputStream -> outputStream?.write(str.toByteArray()) } } catch (e: Exception) { e.printStackTrace() } } } } |
サンプルコード
テキストファイルに「Storage Access Framework」の文字列を入れてそれを保存してみます。
MainActivity.kt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
//package your.package.name import android.content.Intent import android.os.Bundle import android.widget.Button import android.widget.TextView import androidx.activity.result.ActivityResult import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult import androidx.appcompat.app.AppCompatActivity import java.util.* class MainActivity : AppCompatActivity() { private var resultLauncher = registerForActivityResult( StartActivityForResult() ) { result: ActivityResult -> if (result.resultCode == RESULT_OK) { val resultData = result.data if (resultData != null) { val uri = resultData.data // Uriを表示 val textView = findViewById<TextView>(R.id.text_view) textView.text = String.format(Locale.US, "Uri: %s",uri.toString()) val str = "Storage Access Framework\n" try { contentResolver.openOutputStream(uri!!).use { outputStream -> outputStream?.write(str.toByteArray()) } } catch (e: Exception) { e.printStackTrace() } } } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val button = findViewById<Button>(R.id.button) button.setOnClickListener { createFile() } } private fun createFile() { val fileName = "text_file.txt" val intent = Intent(Intent.ACTION_CREATE_DOCUMENT) intent.addCategory(Intent.CATEGORY_OPENABLE) intent.type = "text/plain" intent.putExtra(Intent.EXTRA_TITLE, fileName) resultLauncher.launch(intent) } } |
activity_main.xml
strings.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:background="#fee" android:gravity="center_horizontal" tools:context=".MainActivity"> <TextView android:id="@+id/text_view" android:textColor="#000" android:layout_margin="10dp" android:layout_width="match_parent" android:layout_height="wrap_content" /> <Button android:id="@+id/button" android:text="@string/button" android:layout_margin="10dp" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <ImageView android:id="@+id/image_view" android:scaleType="centerInside" android:contentDescription="@string/description" android:layout_margin="10dp" android:layout_width="match_parent" android:layout_height="wrap_content" /> </LinearLayout> |
strings.xml
1 2 3 4 5 |
<resources> <string name="app_name">YourApp Name</string> <string name="button">Save File</string> <string name="description">a picture</string> </resources> |
実行してボタンをタップすると共有ストレージの「Downloads」に移行して、そこに保存できます。
adb を使ってストレージを覗いてみると、text_file.txt が存在します。
また、ファイル内容も想定したものです。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
hoge>adb shell $ ls acct bugreports d debug_ramdisk etc linkerconfig mnt proc sdcard system apex cache data default.prop init lost+found odm product storage system_ext bin config data_mirror dev init.environ.rc metadata oem res sys vendor $ cd sdcard/Download/ $ ls -la total 16 -rw------- 1 u0_a151 u0_a151 24 2020-11-02 23:00 data-storage.pdf -rw------- 1 u0_a151 u0_a151 25 2020-11-02 23:17 text_file.txt $ cat text_file.txt Storage Access Framework $ |
画像ファイルの保存
SAFを使って「Download」ディレクトリにテキストファイルを保存しましたが、同じように画像を保存することも可能です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
class MainActivity : AppCompatActivity() { private var resultLauncher = registerForActivityResult( StartActivityForResult() ) { result: ActivityResult -> if (result.resultCode == RESULT_OK) { val resultData = result.data if (resultData != null) { val uri = resultData.data val bmp = BitmapFactory.decodeResource(resources, R.drawable.img) try {contentResolver.openOutputStream(uri!!).use { outputStream -> val compress = bmp.compress( Bitmap.CompressFormat.PNG, 100, outputStream ) compress } } catch (e: Exception) { e.printStackTrace() } } } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val button = findViewById<Button>(R.id.button) button.setOnClickListener { createFile() } } private fun createFile() { val intent = Intent(Intent.ACTION_CREATE_DOCUMENT) intent.addCategory(Intent.CATEGORY_OPENABLE) intent.type = "image/*" intent.putExtra(Intent.EXTRA_TITLE, "img.jpg") resultLauncher.launch(intent) } } |
関連ページ:
データを保存するストレージ領域が目的・用途によって区分されてセキュリティー的に厳しくなりました。
対象範囲別外部ストレージ
S...
References:
Storage Access Framework
インテント
ACTION_OPEN_DOCUMENT
ACTION_GET_CONTENT