Symfoware

Symfowareについての考察blog

Android ローカルストレージにアクセスし、画像ファイルを表示する

Androidで端末内に保存されている画像ファイルのサムネールを表示してみたいと思います。


こちらを参考にしました。
[Android] MediaStore スマホの画像を検索
初心者のためのM PERMISSIONS入門
Android 6.0のRuntime Permissionに対応する
MediaStore - Uri to query all types of files (media and non-media)




アクセス許可




ローカルストレージにアクセスするには、android.permission.READ_EXTERNAL_STORAGEの指定と、
ユーザーの許可が必要です。

まず、AndroidManifest.xmlにパーミッションの指定を追加。


  1. <?xml version="1.0" encoding="utf-8"?>
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
  3.     package="com.example.baranche.myapplication">
  4.     <uses-permission android:name="android.permission.INTERNET" />
  5.     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
  6.     <application
  7.         android:allowBackup="true"
  8.         android:icon="@mipmap/ic_launcher"
  9.         android:label="テスト"
  10.         android:roundIcon="@mipmap/ic_launcher_round"
  11.         android:supportsRtl="true"
  12.         android:theme="@style/AppTheme">
  13.         <activity android:name=".MainActivity">
  14.             <intent-filter>
  15.                 <action android:name="android.intent.action.MAIN" />
  16.                 <category android:name="android.intent.category.LAUNCHER" />
  17.             </intent-filter>
  18.         </activity>
  19.     </application>
  20. </manifest>



「uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"」が追加した箇所です。



次に、ローカルストレージへアクセスする前に、許可されているか。
許可されていなければ、requestPermissionsでストレージへの許可を取得します。


  1. import android.Manifest;
  2. import android.annotation.TargetApi;
  3. import android.content.pm.PackageManager;
  4. import android.os.Build;
  5. import android.support.v7.app.AppCompatActivity;
  6. import android.os.Bundle;
  7. import android.view.View;
  8. import android.widget.Toast;
  9. public class MainActivity extends AppCompatActivity {
  10.     // この定数は0以上の値をアプリ内で適当に決めて良い
  11.     private final int EXTERNAL_STORAGE_REQUEST_CODE = 1;
  12.     @Override
  13.     protected void onCreate(Bundle savedInstanceState) {
  14.         super.onCreate(savedInstanceState);
  15.         setContentView(R.layout.activity_main);
  16.     }
  17.     public void show(View view) {
  18.         checkPermission();
  19.     }
  20.     // Permissionの確認
  21.     @TargetApi(Build.VERSION_CODES.M)
  22.     public void checkPermission() {
  23.         // 既に許可している
  24.         if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE)== PackageManager.PERMISSION_GRANTED) {
  25.             readLocalStrage();
  26.             return;
  27.         }
  28.         // 許可していない場合、パーミッションの取得を行う
  29.         // 以前拒否されている場合は、なぜ必要かを通知し、手動で許可してもらう
  30.         if (!shouldShowRequestPermissionRationale(Manifest.permission.READ_EXTERNAL_STORAGE)) {
  31.             Toast.makeText(this, "許可されないとアプリが実行できません", Toast.LENGTH_SHORT).show();
  32.         }
  33.         // パーミッションの取得を依頼
  34.         requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, EXTERNAL_STORAGE_REQUEST_CODE);
  35.     }
  36.     // 結果の受け取り
  37.     @Override
  38.     @TargetApi(Build.VERSION_CODES.M)
  39.     public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
  40.         // requestPermissionsの引数に指定した値が、requestCodeで返却される
  41.         if (requestCode != EXTERNAL_STORAGE_REQUEST_CODE) {
  42.             return;
  43.         }
  44.         // 自分がリクエストしたコードで戻ってきた場合
  45.         // 使用が許可された
  46.         if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
  47.             // ローカルファイルの読み取り処理実行
  48.             readLocalStrage();
  49.             return;
  50.         }
  51.         // 拒否されたが永続的ではない場合
  52.         if (shouldShowRequestPermissionRationale(Manifest.permission.READ_EXTERNAL_STORAGE)) {
  53.             Toast.makeText(this, "許可されないとアプリが実行できません", Toast.LENGTH_SHORT).show();
  54.             // パーミッションの取得を依頼
  55.             requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, EXTERNAL_STORAGE_REQUEST_CODE);
  56.             return;
  57.         }
  58.         // 永続的に拒否された場合
  59.         Toast.makeText(this, "許可されないとアプリが実行できません\nアプリ設定>権限をチェックしてください", Toast.LENGTH_SHORT).show();
  60.     }
  61.     // ローカルストレージの読み取り処理
  62.     private void readLocalStrage() {
  63.     }
  64. }




requestPermissionsを実行すると、確認ダイアログが表示されます。

779_01.png


許可か拒否をクリックすると、onRequestPermissionsResultが呼び出されます。
requestCodeには、requestPermissionsで指定したIDが入っているので、呼び出した時と同じIDかチェック。

grantResults[0]がダイアログで選択された値の結果。
PackageManager.PERMISSION_GRANTEDの場合は許可されたので、処理を続行。

許可されなかった場合は、shouldShowRequestPermissionRationaleの戻り値を確認しています。

これがtrueの場合は、単に拒否しただけ。
falseの場合には、「再度表示しない」にチェックがつけられた状態で拒否したパターンになるようです。

「再度表示しない」をチェックして拒否された場合、アプリから再度許可のリクエストを行うことはできず、
ユーザーに設定画面から許可してもらわないといけないようです。

※一度設定した内容が端末内に保存されるようで、アンインストールしても失われない模様。
検証が難しい。


設定を変更するには、設定画面を開き、

779_02.png

アプリケーションの項目を選択。

779_03.png

設定を変更したいアプリケーションを選択。

779_04.png

パーミッションを選択。

779_05.png

これでやっと設定変更箇所にたどり着きます。

779_06.png





ContentResolver



ローカルストレージなどには、ContentResolverを介してアクセスします。
アクセスはデータベースと似た感じで、検索クエリーを実行してカーソルを取得。
カーソルをフェッチしながら中身を取り出すイメージです。


ContentResolver#query(取得領域, 取得するフィールド, 検索条件, 検索条件の値, ソート順)

のようにしてクエリーを実行します。
取得領域以外、nullを指定すると全件取得となるようです。


  1.         ContentResolver contentResolver = getContentResolver();
  2.         Cursor cursor = null;
  3.         String[] projection = null; // 取得するフィールド
  4.         String selection = null; // 検索条件
  5.         String[] selectionArgs = null; // 検索条件の値
  6.         String sortOrder = null; // ソート順
  7.         try {
  8.             cursor = contentResolver.query(
  9.                     MediaStore.Images.Media.EXTERNAL_CONTENT_URI, projection, selection, selectionArgs, sortOrder);
  10.         } catch (Exception e) {
  11.             e.printStackTrace();
  12.             Toast.makeText(this, "例外が発生、Permissionを許可していますか?", Toast.LENGTH_SHORT).show();
  13.             return;
  14.         }





検索条件を指定する例として、
・フィールドは、_ID(primary key)、DATA(ファイルパス)、WIDTH(画像の幅)、HEIGHT(画像の高さ)、SIZE(容量)を取得
・ファイルタイプはjpegのみ
・最新順
で取得してみます。


  1.         ContentResolver contentResolver = getContentResolver();
  2.         Cursor cursor = null;
  3.         // 取得するフィールド
  4.         String[] projection = {
  5.                 MediaStore.Images.Media._ID,
  6.                 MediaStore.Images.Media.DATA,
  7.                 MediaStore.Images.Media.WIDTH,
  8.                 MediaStore.Images.Media.HEIGHT,
  9.                 MediaStore.Images.Media.SIZE
  10.         };
  11.         // 検索条件
  12.         // mimetypeで検索 ?はプレースフォルダーで、値はselectionArgsで指定
  13.         String selection = MediaStore.Files.FileColumns.MIME_TYPE + "=?";;
  14.         // 検索条件に指定する値
  15.         // まずjpgのmimetypeを取得
  16.         String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension("jpg");
  17.         // 検索条件の値として設定
  18.         String[] selectionArgs = new String[]{ mimeType };
  19.         // _IDの降順でソート
  20.         String sortOrder = MediaStore.Images.Media._ID + " desc";
  21.         try {
  22.             cursor = contentResolver.query(
  23.                     MediaStore.Images.Media.EXTERNAL_CONTENT_URI, projection, selection, selectionArgs, sortOrder);
  24.         } catch (Exception e) {
  25.             e.printStackTrace();
  26.             Toast.makeText(this, "例外が発生、Permissionを許可していますか?", Toast.LENGTH_SHORT).show();
  27.             return;
  28.         }









ContentProviderClient



今回は、1回のクエリ実行で全件データを取得していますが、データ件数が少ない場合は
ContentProviderClientを使用したほうが良いようです。

Android:高速化。ContentResolver?ContentProviderClient?

全件フェッチしてクローズする処理なので、今回は使用を見送りました。




サムネールの作成



MediaStore.Images.Thumbnails.getThumbnail(ContentResolver, _ID, 種類, BitmapFactory.Options)

これでサムネール画像が取得できます。
これらを総合して、ボタンをクリックしたらサムネールの一覧を表示するプログラムは以下のようになりました。

・activity_mail.xml


  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout
  3.     xmlns:android="http://schemas.android.com/apk/res/android"
  4.     xmlns:app="http://schemas.android.com/apk/res-auto"
  5.     xmlns:tools="http://schemas.android.com/tools"
  6.     android:layout_width="match_parent"
  7.     android:layout_height="match_parent"
  8.     android:orientation="vertical"
  9.     >
  10.     <LinearLayout
  11.         android:layout_width="match_parent"
  12.         android:layout_height="wrap_content"
  13.         android:orientation="horizontal">
  14.         <Button
  15.             android:layout_width="wrap_content"
  16.             android:layout_height="wrap_content"
  17.             android:text="表示"
  18.             android:onClick="show" />
  19.     </LinearLayout>
  20.     <GridView
  21.         android:id="@+id/gridView1"
  22.         android:layout_width="match_parent"
  23.         android:layout_height="match_parent"
  24.         android:columnWidth="100dp"
  25.         android:horizontalSpacing="1dp"
  26.         android:verticalSpacing="1dp"
  27.         android:numColumns="auto_fit"
  28.         android:stretchMode="columnWidth"
  29.         android:gravity="center">
  30.     </GridView>
  31. </LinearLayout>





・MainActivity.java


  1. import android.Manifest;
  2. import android.annotation.TargetApi;
  3. import android.content.ContentResolver;
  4. import android.content.pm.PackageManager;
  5. import android.database.Cursor;
  6. import android.os.Build;
  7. import android.provider.MediaStore;
  8. import android.support.v7.app.AppCompatActivity;
  9. import android.os.Bundle;
  10. import android.view.View;
  11. import android.webkit.MimeTypeMap;
  12. import android.widget.GridView;
  13. import android.widget.Toast;
  14. import java.util.ArrayList;
  15. import java.util.List;
  16. public class MainActivity extends AppCompatActivity {
  17.     // この定数は0以上の値をアプリ内で適当に決めて良い
  18.     private final int EXTERNAL_STORAGE_REQUEST_CODE = 1;
  19.     @Override
  20.     protected void onCreate(Bundle savedInstanceState) {
  21.         super.onCreate(savedInstanceState);
  22.         setContentView(R.layout.activity_main);
  23.     }
  24.     public void show(View view) {
  25.         checkPermission();
  26.     }
  27.     // Permissionの確認
  28.     @TargetApi(Build.VERSION_CODES.M)
  29.     public void checkPermission() {
  30.         // 既に許可している
  31.         if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE)== PackageManager.PERMISSION_GRANTED) {
  32.             readLocalStrage();
  33.             return;
  34.         }
  35.         // 許可していない場合、パーミッションの取得を行う
  36.         // 以前拒否されている場合は、なぜ必要かを通知し、手動で許可してもらう
  37.         if (!shouldShowRequestPermissionRationale(Manifest.permission.READ_EXTERNAL_STORAGE)) {
  38.             Toast.makeText(this, "許可されないとアプリが実行できません", Toast.LENGTH_SHORT).show();
  39.         }
  40.         // パーミッションの取得を依頼
  41.         requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, EXTERNAL_STORAGE_REQUEST_CODE);
  42.     }
  43.     // 結果の受け取り
  44.     @Override
  45.     @TargetApi(Build.VERSION_CODES.M)
  46.     public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
  47.         // requestPermissionsの引数に指定した値が、requestCodeで返却される
  48.         if (requestCode != EXTERNAL_STORAGE_REQUEST_CODE) {
  49.             return;
  50.         }
  51.         // 自分がリクエストしたコードで戻ってきた場合
  52.         // 使用が許可された
  53.         if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
  54.             // ローカルファイルの読み取り処理実行
  55.             readLocalStrage();
  56.             return;
  57.         }
  58.         // 拒否されたが永続的ではない場合
  59.         if (shouldShowRequestPermissionRationale(Manifest.permission.READ_EXTERNAL_STORAGE)) {
  60.             Toast.makeText(this, "許可されないとアプリが実行できません", Toast.LENGTH_SHORT).show();
  61.             // パーミッションの取得を依頼
  62.             requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, EXTERNAL_STORAGE_REQUEST_CODE);
  63.             return;
  64.         }
  65.         // 永続的に拒否された場合
  66.         Toast.makeText(this, "許可されないとアプリが実行できません\nアプリ設定>権限をチェックしてください", Toast.LENGTH_SHORT).show();
  67.     }
  68.     // ローカルストレージの読み取り処理
  69.     private void readLocalStrage() {
  70.         ContentResolver contentResolver = getContentResolver();
  71.         Cursor cursor = null;
  72.         // 取得するフィールド
  73.         String[] projection = {
  74.                 MediaStore.Images.Media._ID,
  75.                 MediaStore.Images.Media.DATA,
  76.                 MediaStore.Images.Media.WIDTH,
  77.                 MediaStore.Images.Media.HEIGHT,
  78.                 MediaStore.Images.Media.SIZE
  79.         };
  80.         // 検索条件
  81.         // mimetypeで検索 ?はプレースフォルダーで、値はselectionArgsで指定
  82.         String selection = MediaStore.Files.FileColumns.MIME_TYPE + "=?";;
  83.         // 検索条件に指定する値
  84.         // まずjpgのmimetypeを取得
  85.         String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension("jpg");
  86.         // 検索条件の値として設定
  87.         String[] selectionArgs = new String[]{ mimeType };
  88.         // _IDの降順でソート
  89.         String sortOrder = MediaStore.Images.Media._ID + " desc";
  90.         try {
  91.             cursor = contentResolver.query(
  92.                     MediaStore.Images.Media.EXTERNAL_CONTENT_URI, projection, selection, selectionArgs, sortOrder);
  93.         } catch (Exception e) {
  94.             e.printStackTrace();
  95.             Toast.makeText(this, "例外が発生、Permissionを許可していますか?", Toast.LENGTH_SHORT).show();
  96.             return;
  97.         }
  98.         if (cursor == null) {
  99.             return;
  100.         }
  101.         List<Bundle> rows = new ArrayList<>();
  102.         while (cursor.moveToNext()) {
  103.             // カーソルから値を取り出し
  104.             String filePath = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
  105.             long _id = cursor.getLong(cursor.getColumnIndex(MediaStore.Images.Media._ID));
  106.             // ※必要なら、ここでサイズや容量のチェックも実行できる
  107.             // arrayに転送
  108.             Bundle bundle = new Bundle();
  109.             bundle.putLong("_id", _id);
  110.             bundle.putString("path", filePath);
  111.             rows.add(bundle);
  112.         }
  113.         cursor.close();
  114.         // データを表示
  115.         MyGridViewAdapter adapter = new MyGridViewAdapter(this, rows);
  116.         GridView gv = (GridView)findViewById(R.id.gridView1);
  117.         gv.setAdapter(adapter);
  118.     }
  119. }





・MyGridViewAdapter


  1. import android.content.ContentResolver;
  2. import android.content.Context;
  3. import android.graphics.Bitmap;
  4. import android.os.Bundle;
  5. import android.provider.MediaStore;
  6. import android.view.LayoutInflater;
  7. import android.view.View;
  8. import android.view.ViewGroup;
  9. import android.widget.BaseAdapter;
  10. import android.widget.ImageView;
  11. import java.util.List;
  12. public class MyGridViewAdapter extends BaseAdapter {
  13.     private Context mContext;
  14.     private ContentResolver mContentResolver;
  15.     private LayoutInflater mRayoutInflater = null;
  16.     private List<Bundle> mList;
  17.     public MyGridViewAdapter(Context context, List<Bundle> list) {
  18.         this.mContext = context;
  19.         this.mContentResolver = this.mContext.getContentResolver();
  20.         this.mList = list;
  21.     }
  22.     @Override
  23.     public int getCount() {
  24.         if (mList == null) {
  25.             return 0;
  26.         }
  27.         return mList.size();
  28.     }
  29.     @Override
  30.     public Object getItem(int position) {
  31.         return mList.get(position);
  32.     }
  33.     @Override
  34.     public long getItemId(int position) {
  35.         return position;
  36.     }
  37.     @Override
  38.     public View getView(int i, View convertView, ViewGroup viewGroup) {
  39.         View view;
  40.         if (convertView != null) {
  41.             view = convertView;
  42.         }
  43.         else {
  44.             view = new ImageView(mContext);
  45.         }
  46.         ImageView thumbnail = (ImageView)view;
  47.         Bundle item = mList.get(i);
  48.         long _id = item.getLong("_id");
  49.         Bitmap bmp = MediaStore.Images.Thumbnails.getThumbnail(mContentResolver, _id, MediaStore.Images.Thumbnails.MINI_KIND, null);
  50.         thumbnail.setImageBitmap(bmp);
  51.         return view;
  52.     }
  53. }




実行結果

779_07.png

ちゃんとダウンロードした画像やカメラで撮影した画像が表示できました。




【参考URL】

[Android] MediaStore スマホの画像を検索
初心者のためのM PERMISSIONS入門
Android 6.0のRuntime Permissionに対応する
MediaStore - Uri to query all types of files (media and non-media)

関連記事

テーマ:プログラミング - ジャンル:コンピュータ

  1. 2017/08/19(土) 19:32:14|
  2. Java
  3. | トラックバック:0
  4. | コメント:0
  5. | 編集
次のページ