2015-11-08

Android Studio でカメラアプリにメディアスキャンを追加

スポンサーリンク

前回までに作成したカメラアプリでは、撮影した写真がアルバムアプリに反映されるようになっていませんでした。今回はアルバムアプリに反映されるようにカメラアプリにメディアスキャンを追加します。

前回までの記事

scanFile() メソッドを追加する


写真をアルバムアプリに反映するには MediaScannerConnection クラスの scanFile() メソッドを実行します。scanFile() メソッドの第2引数にはスキャンするファイルのパスを、第3引数にはメディアの種類を渡します。
  • MainActivity.java を以下の通り書き換え
package com.example.camera;

import android.app.Activity;
import android.hardware.Camera;
import android.hardware.Camera.Parameters;
import android.hardware.Camera.Size;
import android.media.MediaScannerConnection;
import android.os.Bundle;
import android.os.Environment;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.FrameLayout.LayoutParams;
import android.widget.Toast;

import java.io.File;
import java.io.FileOutputStream;
import java.util.List;

public class MainActivity extends Activity {

    private SurfaceView sv;
    private Camera cam;
    private FrameLayout fl;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
        requestWindowFeature(Window.FEATURE_NO_TITLE);

        fl = new FrameLayout(this);
        setContentView(fl);

        sv = new SurfaceView(this);
        SurfaceHolder sh = sv.getHolder();
        sh.addCallback(new SurfaceHolderCallback());

        Button btn = new Button(this);
        btn.setText("撮影");
        btn.setLayoutParams(new LayoutParams(200, 150));
        btn.setOnClickListener(new TakePictureClickListener());

        fl.addView(sv);
        fl.addView(btn);
    }

    class SurfaceHolderCallback implements SurfaceHolder.Callback {
        @Override
        public void surfaceCreated(SurfaceHolder holder) {
            cam = Camera.open();
            Parameters param = cam.getParameters();
            List<Size> ss = param.getSupportedPictureSizes();
            Size pictSize = ss.get(0);

            param.setPictureSize(pictSize.width, pictSize.height);
            cam.setParameters(param);
        }
        @Override
        public void surfaceChanged(SurfaceHolder holder, int f, int w, int h) {
            try {
                cam.setDisplayOrientation(0);
                cam.setPreviewDisplay(sv.getHolder());

                Parameters param = cam.getParameters();
                List<Size> previewSizes =
                        cam.getParameters().getSupportedPreviewSizes();
                Size pre = previewSizes.get(0);
                param.setPreviewSize(pre.width, pre.height);

                LayoutParams lp = new LayoutParams(pre.width, pre.height);
                sv.setLayoutParams(lp);

                Grid grid = new Grid(getApplicationContext(), pre.width, pre.height);
                fl.addView(grid);

                cam.setParameters(param);
                cam.startPreview();
            } catch (Exception e) { }
        }
        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {
            cam.stopPreview();
            cam.release();
        }
    }

    class TakePictureClickListener implements View.OnClickListener {
        @Override
        public void onClick(View v) {
            cam.autoFocus(autoFocusCallback);
        }

        private Camera.AutoFocusCallback autoFocusCallback = new Camera.AutoFocusCallback() {
            @Override
            public void onAutoFocus(boolean success, Camera camera) {
                cam.takePicture(new Camera.ShutterCallback() {
                    @Override
                    public void onShutter() {}
                }, null, new TakePictureCallback());
            }
        };
    }

    class TakePictureCallback implements Camera.PictureCallback {
        @Override
        public void onPictureTaken(byte[] data, Camera camera) {
            try {
                File dir = new File(
                        Environment.getExternalStorageDirectory(), "Camera");
                if(!dir.exists()) {
                    dir.mkdir();
                }
                File f = new File(dir, "img.jpg");
                FileOutputStream fos = new FileOutputStream(f);
                fos.write(data);

                String[] path = {Environment.getExternalStorageDirectory() + "/Camera/img.jpg"};
                String[] mimeType = {"image/jpeg"};
                MediaScannerConnection.scanFile(getApplicationContext(), path, mimeType, null);

                Toast.makeText(getApplicationContext(),
                        "写真を保存しました", Toast.LENGTH_LONG).show();
                fos.close();
                cam.startPreview();
            } catch (Exception e) { }
        }
    }
}

カメラアプリを起動して写真を撮影すると、アルバムアプリに反映されることが確認できました。
スポンサーリンク

12 件のコメント:

  1. 撮った写真が1枚しか保存されず、2枚目を保存しようとすると上書きされてしまい結果1枚しか保存されません。
    どうしたら良いでしょうか。

    返信削除
  2. MainActivity.java にある以下の1文が、撮った写真のファイル名を指定している箇所です。

    File f = new File(dir, "img.jpg");

    この中の "img.jpg" が固定されているため、2枚目を撮影すると上書きされてしまいます。
    "img.jpg" の部分を動的に変化させれば、上書きを避けることができます。

    具体的には、ファイル名を
    ・連番にする(例:0001.jpg、0002.jpg、0003.jpg、...)
    ・日付と時間にする(例:20160102035512.jpg)
    などが考えられます。

    おすすめは連番ですが、日付と時間にするほうが手っ取り早いです。
    やり方は以下の通りです。

    ① import するパッケージの追加
    MainActivity.java 最初あたりの import 文が書かれているところに以下の2行を追加します。

    import java.text.SimpleDateFormat;
    import java.util.Date;

    ②ファイル名を撮影した日付と時間に設定する
    撮った写真のファイル名を指定している箇所を以下のように修正します。

    File f = new File(dir, "img.jpg");

    Date date = new Date();
    SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddkkmmss");
    String fn = sdf.format(date) + ".jpg";
    File f = new File(dir, fn);

    ③メディアスキャンするファイル名を修正
    以下の1文を以下のように修正します。

    String[] path = {Environment.getExternalStorageDirectory() + "/Camera/img.jpg"};

    String[] path = {Environment.getExternalStorageDirectory() + "/Camera/" + fn};

    以上で、複数の写真が記録できるようになります。
    ※ただし、1秒間に2枚以上撮影できてしまうと上書きが発生しますので注意してください。

    返信削除
  3. わかりやすく教えて頂き、ありがとうございました!
    おかげで無事複数枚保存出来るようになりました!

    もう一つお伺いしたいことがありまして、
    今って撮った写真の保存先があると思うんですが、この保存先の他に違う場所にも保存するというのは可能ですかね?

    つまりは撮った写真を今保存先になっているアルバムアプリとまた別のアプリ(カメラアプリ自体に保存されるでもいいです)の両方に保存されるといった感じです。

    返信削除
  4. 『保存』と『登録』について分けて説明します。
    以下の1文が写真の『保存』するフォルダを指定している箇所です。この保存先は Android デバイス上のフォルダのことです。

    File dir = new File(Environment.getExternalStorageDirectory(), "Camera");

    次に、以下の箇所で Android が持っているメディアストレージという画像や音楽のファイルを管理するデータベースに『登録』しています。

    String[] path = {Environment.getExternalStorageDirectory() + "/Camera/img.jpg"};
    String[] mimeType = {"image/jpeg"};
    MediaScannerConnection.scanFile(getApplicationContext(), path, mimeType, null);

    このメディアストレージへの登録によって、アルバムアプリに写真が表示されるようになります。
    メディアストレージに登録してしまえば、保存先を気にすることなく別の写真ビューアアプリでも写真が見れると思います。

    (※ 以下を実行する場合は、リスクを理解した上で実行してください。)
    ちなみに、メディアストレージは Android デバイスの設定画面からすべてのアプリ一覧を見るとアプリの一つとして表示されます。
    メディアストレージのアプリ情報画面で「データを削除」するとアルバムアプリの写真は全て表示されなくなります(ミュージックやビデオも同様)。
    これはデータベースの登録情報が削除されるだけで、保存先にはファイルがちゃんと残っています。
    デバイス自体を再起動するとメディアスキャンが実行され、しばらくするとアルバムアプリに写真が表示されるようになります。

    返信削除
  5. つまりは撮った写真を保存する用のプロジェクトを別で作り、そのプロジェクト内に先ほどの

    String[] path = {Environment.getExternalStorageDirectory() + "/Camera/img.jpg"};
    String[] mimeType = {"image/jpeg"};
    MediaScannerConnection.scanFile(getApplicationContext(), path, mimeType, null);

    を書いたら今保存先になっているフォルダをもう一つ作り出せるということですかね?

    理解力が乏しくて申し訳ありません。

    返信削除
  6. 残念ながら間違いです。
    色々と書きすぎて混乱させてしまったかもしれません。

    シンプルに回答してみます。

    > つまりは撮った写真を今保存先になっているアルバムアプリとまた別のアプリ(カメラアプリ自体に保存されるでもいいです)
    > の両方に保存されるといった感じです。

    上の記述から、実現したいことが『撮った写真を、アルバムアプリと別のアプリの両方で表示させたい』だと解釈すると、
    追加で必要な処理はありません。

    返信削除
  7. では、撮った写真の保存先をもう一つ作るとしたら新しいアクティビティにはどんな処理が必要なのでしょうか。

    今のままだと保存先は一つしかないので・・・。

    返信削除
  8. android初心者さんの質問が色んな意味に捉えることができるので、求めている回答ではないかもしれません。

    実現したいことが、『撮った写真を複数のフォルダに保存すること』であれば、
    別のフォルダを用意して、そのフォルダに同じ写真を書き込むのが簡単だと思います。

    具体例としては、fos.close();の後あたりに以下のソースを追加します。

    File dir2 = new File(Environment.getExternalStorageDirectory(), "Camera2");
    if(!dir2.exists()) {
    dir2.mkdir();
    }
    File f2 = new File(dir2, fn);
    FileOutputStream fos2 = new FileOutputStream(f2);
    fos2.write(data);
    fos2.close();

    これで、「Camera」というフォルダと「Camera2」というフォルダに同じ写真が1枚ずつ保存されるようになります。

    返信削除
  9. 説明が下手で申し訳ありません!出来るだけ細かくご説明します!

    フォルダを増やしたいのではなくて、ストレージを増やしたいという言い方のほうが適切ですかね・・・?
    撮った写真のフォルダが保管されている場所がありますよね?
    その保管場所自体を増やしたいんです。
    今のままだとフォルダを増やしただけなので結局は一つのギャラリーというアプリの中のフォルダが増えるだけですよね?
    理想は新しいアプリを用意して、そのアプリ内にも保存されてる…みたいな感じです。

    返信削除
  10. 『写真のフォルダが保管されている場所』が意味するのは、物理的な場所(micro SDカードなど)と、論理的な場所(メディアストレージなどのデータベース)のどちらでしょうか?(詳しくは分かりませんが、どちらも増やす方法はあると思います。)

    また、いくつか誤解があるようです。
    ギャラリーというアプリの中にフォルダを作っているわけではないですし、撮影した写真はアプリ内に保存されているわけでもないです。Android デバイス上の内部ストレージにフォルダを作って、そのフォルダに写真を保存しています。このフォルダ内の写真は、新しく作成したアプリからも見れるので、保管場所を増やす必要はないように感じます。

    返信削除
  11. おそらく論理的な場所の方です。

    あとすみません、写真を撮るボタンの位置を変えたいのですがどこを変えればいいでしょう?
    色んな方法を試しているのですが一向に変わらずで・・・。

    返信削除
  12. androidにはデータベース管理用にSQLiteが搭載されているので、これを使えばアプリ独自のデータベースが構築できると思います。私は使ったことがないので解説できないですが。

    ボタンの位置については、現状は画面のレイアウトにLinearLayoutを使っていますが、RelativeLayoutを使ってレイアウト用のXMLファイルを作れば簡単に位置が変更できます。

    返信削除