Edited at

撮影した画像を自動でmBaaSに保存するアプリをAndroid Studioで作ってみた

More than 3 years have passed since last update.

[追記]

続編を書きましたのでよければこちらもご覧ください!

撮影した画像をプッシュ通知で共有するアプリをAndroid Studioで作ってみた



  • 前回の予告(ニフティクラウド mobile backend(mBaaS)のファイルストアの使い方について書く)を回収する


  • GoogleがEclipseのADTサポート打ち切りを発表したのでAndroid Studioに慣れる


ということで、今回はAndroid Studioを使って、撮影した画像をmBaaSに自動で保存するアプリを作成します。

※そもそもmBaaSとは?→http://mb.cloud.nifty.com/about.htm

完成までの流れは以下の通り

1. mBaaSの登録(無料のプランで問題ないです)→http://mb.cloud.nifty.com/

2. Android Studioのセットアップ(省略)

3. 普通のカメラアプリを作成する

4. mBaaSと連携させる(画像をファイルストアに保存する)


カメラアプリの作成

基本的には以下のサイトのコードを参考に作成。

http://qiita.com/fslasht/items/be41e84cfbc4bbb91af7

こちらのページでは何段階かに分けて画像の縦横比の調整などをされていますが、今回のメインはあくまで撮影した画像の保存なので、第一段階のコードだけ利用させていただきました。

ただ、このページの内容を完成させてから第二段階以降に進まれても問題なく動くとは思います。

開始時点の注意事項としては、Brank Activity with Fragmentで新規プロジェクトを作成すること。

個人的に詰まったのは以下の点


  • 編集以前になんか色々足りてない

     → SDK Managerでサポートライブラリをインストールする

      参考:http://www.programing-style.com/android/android-tool/android-supportlibrary-setup/ ここの1と2をやることで解決


  • 非推奨のクラスがたくさん

     →今回は無視してガンガン進めます。


  • FragmentやonTouchListener、AppCompatActivityが参照できないといわれる

     → 自動でインポートを生成してくれるようで、微妙に拾いきれない様子

       クラス名が赤くなったら、とりあえず自分でimport文を書いてみると意外といける


  • R.id.container がない

     → activity_main.xmlに「android:id="@+id/container"」を追加する


以下手を加えたコード

MainActivity.java


package com.example.username.ncmbcamera;

import android.hardware.Camera;
import android.hardware.Camera.Size;
import android.os.Bundle;
import android.os.Environment; //環境によって必要なら追加
import android.support.v4.app.Fragment;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;

import java.io.FileOutputStream;

public class MainActivity extends AppCompatActivity {
final static private String TAG = "NCMB Camera";

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction().add(R.id.container, new CameraFragment()).commit();
}
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}

return super.onOptionsItemSelected(item);
}

/**
* カメラ撮影用フラグメント
*/

public static class CameraFragment extends Fragment {

// ------------------------------------------------------------
// メンバー変数
// ------------------------------------------------------------
private Camera camera_; // カメラインスタンス
View rootView_; // ルートView
SurfaceView surfaceView_; // プレビュー用SurfaceView

// ------------------------------------------------------------
// リスナー
// ------------------------------------------------------------

// Surfaceリスナー
private SurfaceHolder.Callback surfaceListener_ = new SurfaceHolder.Callback() {
// Surface作成
public void surfaceCreated(SurfaceHolder holder) {
// カメラインスタンスを取得
camera_ = Camera.open();
try {
camera_.setPreviewDisplay(holder);
} catch (Exception e) {
e.printStackTrace();
}
}

// Surface破棄時
public void surfaceDestroyed(SurfaceHolder holder) {
// カメラインスタンス開放
camera_.release();
camera_ = null;
}

// Surface変更時
// プレビューのパラメーターを設定し、プレビューを開始する
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
Log.d(TAG, "surfaceChanged width:" + width + " height:" + height);

Camera.Parameters parameters = camera_.getParameters();

// デバッグ用表示
Size size = parameters.getPictureSize();
Log.d(TAG, "getPictureSize width:" + size.width + " size.height:" + size.height);
size = parameters.getPreviewSize();
Log.d(TAG, "getPreviewSize width:" + size.width + " size.height:" + size.height);

// プレビューのサイズを変更
// parameters.setPreviewSize(width, height); // 画面サイズに合わせて変更しようとしたが失敗する
parameters.setPreviewSize(640, 480); // 使用できるサイズはカメラごとに決まっている

// パラメーターセット
camera_.setParameters(parameters);
// プレビュー開始
camera_.startPreview();
}
};

// シャッターが押されたときに呼ばれるコールバック
private Camera.ShutterCallback shutterListener_ = new Camera.ShutterCallback() {
public void onShutter() {
}
};

// JPEGイメージ生成後に呼ばれるコールバック
private Camera.PictureCallback pictureListener_ = new Camera.PictureCallback() {
// データ生成完了
public void onPictureTaken(byte[] data, Camera camera) {
// SDカードにJPEGデータを保存する
if (data != null) {
FileOutputStream fos = null;
try {
String pictPath = Environment.getExternalStorageDirectory().getPath()+ "/camera_test.jpg";
fos = new FileOutputStream(pictPath);
fos.write(data);
fos.close();

} catch (Exception e) {
e.printStackTrace();
}

// プレビューを再開する
camera.startPreview();
}
}
};

// 画面タッチ時のコールバック
OnTouchListener ontouchListener_ = new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
if (camera_ != null) {
// 撮影実行
camera_.takePicture(shutterListener_, null, pictureListener_);
}
}
return false;
}
};

// ------------------------------------------------------------
// Fragment
// ------------------------------------------------------------

// Fragmentコンストラクタ
public CameraFragment() {
}

// View作成
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// View作成
rootView_ = inflater.inflate(R.layout.fragment_main, container, false);

// View内のView取得
surfaceView_ = (SurfaceView) rootView_ .findViewById(R.id.surface_view);

// SurfaceHolder設定
SurfaceHolder holder = surfaceView_.getHolder();
holder.addCallback(surfaceListener_);
holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);

// タッチリスナー設定
rootView_.setOnTouchListener(ontouchListener_);

return rootView_;
}
}
}

activity_main.xml


<fragment xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"

android:id="@+id/container" //必要なら追加
android:name="com.example.username.ncmbcamera.MainActivityFragment"
tools:layout="@layout/fragment_main"
android:layout_width="match_parent"
android:layout_height="match_parent" />

fragment_main.xml


<RelativeLayout 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"
tools:context="com.example.username.ncmbcamera.CameraFragment" >

<SurfaceView
android:id="@+id/surface_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />

</RelativeLayout>

追加したパーミッション( application タグの直前に追加 )

AndroidManifest.xml


<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

これでひとまずアプリ内でカメラを使えるようになりました。


撮影した画像をファイルストアに保存する

いよいよ先ほどのカメラアプリを改造してmBaaSと連携させていきます。

やることは以下の通り


  1. mBaaSのSDKを追加する

  2. パーミッションを追加する

  3. NCMBクラスにアプリケーションキーとクライアントキーを登録する

  4. SDカードに画像を保存する処理をファイルストアへの保存に置き換える


1. jarの追加

まず、jarファイルを追加していきます。

ドキュメントページ( http://mb.cloud.nifty.com/doc/current/ ) からSDKをダウンロードします。(左側にダウンロードボタンがあります。)

次に、ダウンロードしたzipファイルを解凍し、NCMB.jarをプロジェクトのapp->libsの中にコピーアンドペーストします。(ドラッグアンドドロップだとエラーになる場合があるようなので注意)

※libsディレクトリが見つからない場合、プロジェクトの表示(左上のプルダウンから選択できます。)をAndroid->projectに変更してください。

その後、追加したjarファイルを右クリックし、「Add As Library」で追加します。

これでSDKの追加は完了です。


2. パーミッションの追加

ファイルストアに画像を保存するためにインターネット通信をする必要があるので、以下のパーミッションを追加します。

AndroidManifest.xml( application タグの直前に追加 )


<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />


3. NCMBクラスにアプリケーションキーとクライアントキーを登録する

まず、ニフティクラウド mobile backendにログインし、「+新しいアプリ」を押してアプリを新規作成します。

作成したら、アプリケーションキーとクライアントキーが表示されるので、コピーします。

MainActivity.javaのonCreateメソッド内に以下のコードを追加し、「APP_KEY」「CLI_KEY」をそれぞれコピーした値に置き換えます。


NCMB.initialize(this, "APP_KEY", "CLI_KEY");

完成系のコードは次の4.とまとめて下に書きます。

自動でNCMBがインポートされない場合はそちらを参考にしてください。


4. SDカードに画像を保存する処理をファイルストアへの保存に置き換える

MainActivity.javaの画像をSDカードに保存する部分を、以下のファイルストアに保存するコードに置き換えます。


NCMBFile file = new NCMBFile("picture.jpg", data);

file.saveInBackground();

これによってカメラから取得されたdataが”picture.jpg”としてファイルストアに保存されます。

ただし、このままだとファイル名が固定で複数の画像を保存できないため、時間によってファイル名が設定されるようにして、3.と合わせて書き換えたMainActivity.javaは以下の通りです。

(エラー処理など、他にも少してを加えてあります。)

MainActivity.java

package com.example.username.ncmbcamera;

import android.hardware.Camera;
import android.hardware.Camera.Size;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;

import com.nifty.cloud.mb.NCMB;
import com.nifty.cloud.mb.NCMBException;
import com.nifty.cloud.mb.NCMBFile;
import com.nifty.cloud.mb.ProgressCallback;
import com.nifty.cloud.mb.SaveCallback;

import java.io.FileOutputStream;
import java.util.Calendar;

public class MainActivity extends AppCompatActivity {
final static private String TAG = "NCMB Camera";

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

//3.追加部分
NCMB.initialize(this, "APP_KEY", "CLI_KEY");

if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction().add(R.id.container, new CameraFragment()).commit();
}
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}

return super.onOptionsItemSelected(item);
}

/**
* カメラ撮影用フラグメント
*/

public static class CameraFragment extends Fragment {

// ------------------------------------------------------------
// メンバー変数
// ------------------------------------------------------------
private Camera camera_; // カメラインスタンス
View rootView_; // ルートView
SurfaceView surfaceView_; // プレビュー用SurfaceView

// ------------------------------------------------------------
// リスナー
// ------------------------------------------------------------

// Surfaceリスナー
private SurfaceHolder.Callback surfaceListener_ = new SurfaceHolder.Callback() {
// Surface作成
public void surfaceCreated(SurfaceHolder holder) {
// カメラインスタンスを取得
camera_ = Camera.open();
try {
camera_.setPreviewDisplay(holder);
} catch (Exception e) {
e.printStackTrace();
}
}

// Surface破棄時
public void surfaceDestroyed(SurfaceHolder holder) {
// カメラインスタンス開放
camera_.release();
camera_ = null;
}

// Surface変更時
// プレビューのパラメーターを設定し、プレビューを開始する
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
Log.d(TAG, "surfaceChanged width:" + width + " height:" + height);

Camera.Parameters parameters = camera_.getParameters();

// デバッグ用表示
Size size = parameters.getPictureSize();
Log.d(TAG, "getPictureSize width:" + size.width + " size.height:" + size.height);
size = parameters.getPreviewSize();
Log.d(TAG, "getPreviewSize width:" + size.width + " size.height:" + size.height);

// プレビューのサイズを変更
// parameters.setPreviewSize(width, height); // 画面サイズに合わせて変更しようとしたが失敗する
parameters.setPreviewSize(640, 480); // 使用できるサイズはカメラごとに決まっている

// パラメーターセット
camera_.setParameters(parameters);
// プレビュー開始
camera_.startPreview();
}
};

// シャッターが押されたときに呼ばれるコールバック
private Camera.ShutterCallback shutterListener_ = new Camera.ShutterCallback() {
public void onShutter() {
}
};

// JPEGイメージ生成後に呼ばれるコールバック
private Camera.PictureCallback pictureListener_ = new Camera.PictureCallback() {
// データ生成完了
public void onPictureTaken(byte[] data, Camera camera) {
// SDカードにJPEGデータを保存する
if (data != null) {
FileOutputStream fos = null;

// ここは削除してよい
// try {
// String pictPath = Environment.getExternalStorageDirectory().getPath()+ "/camera_test.jpg";
// fos = new FileOutputStream(pictPath);
// fos.write(data);
// fos.close();
//
// } catch (Exception e) {
// e.printStackTrace();
// }

// 4.追加部分
try {
// 日付と時間で数列を生成
Calendar calendar = Calendar.getInstance();
int year = calendar.get(Calendar.YEAR);
int month = calendar.get(Calendar.MONTH);
int day = calendar.get(Calendar.DAY_OF_MONTH);
int hour = calendar.get(Calendar.HOUR_OF_DAY);
int minute = calendar.get(Calendar.MINUTE);
int second = calendar.get(Calendar.SECOND);
String fileName = "" + year + (month + 1) + day + hour + minute + second + ".jpg";

NCMBFile file = new NCMBFile(fileName, data);
//ファイル送信の進捗状況を確認できるようアレンジ
file.saveInBackground(new SaveCallback() {
public void done(NCMBException e) {
// 完了 ...
}
}, new ProgressCallback() {
public void done(Integer percentDone) {
// 進捗状況。1 - 100 のpercentDoneを返す
}
});
}catch (Exception e){
System.out.println("error in saveInBackground");
e.printStackTrace();

}

// プレビューを再開する
camera.startPreview();
}
}
};

// 画面タッチ時のコールバック
OnTouchListener ontouchListener_ = new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
if (camera_ != null) {
// 撮影実行
camera_.takePicture(shutterListener_, null, pictureListener_);
}
}
return false;
}
};

// ------------------------------------------------------------
// Fragment
// ------------------------------------------------------------

// Fragmentコンストラクタ
public CameraFragment() {
}

// View作成
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// View作成
rootView_ = inflater.inflate(R.layout.fragment_main, container, false);

// View内のView取得
surfaceView_ = (SurfaceView) rootView_ .findViewById(R.id.surface_view);

// SurfaceHolder設定
SurfaceHolder holder = surfaceView_.getHolder();
holder.addCallback(surfaceListener_);
holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);

// タッチリスナー設定
rootView_.setOnTouchListener(ontouchListener_);

return rootView_;
}
}
}

コンパネのファイルストアを確認して、画像が保存されていれば完成です。

次回はさらに一手間加えて、画像を撮影したら自動でプッシュ通知で通知・確認できるアプリを作る予定です。

ーー

ニフティクラウド mobile backend:http://mb.cloud.nifty.com/