Posted at

撮影した画像を自動でクラウドに保存するアプリを新SDKで作ってみた【ソースコード有】

More than 3 years have passed since last update.

NIFTY Cloud Advent Calendar 2015の最終日が空いていたので、せっかくなので書こうと思います。

以前、撮影した画像を自動でmBaaSに保存するカメラアプリに関する記事を書きましたが、その後新しいSDKが出ていたので、今回はそのSDKを使って同様のアプリを作っていきたいと思います。

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

流れは以下の通り

(事前準備)mBaaSへの無料登録→http://mb.cloud.nifty.com/

1. 普通のカメラアプリの作成

2. mBaaSとの連携

3. 画像の保存部分の実装


カメラアプリの作成

今回も以下のサイトのコードを参考にさせていただいてベースのアプリを作成していきます。

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

この部分は前回と違いがないので、前回の記事を参考にして実装してください。


実装時の注意点

今回の実装でのプチハマりポイントは以下の通り。


  • android.support.v4.app.Fragmentとandroid.support.v7.app.AppCompatActivityのインポートでエラーが出る
     →何故かAndroidSupportRepositoryがインストールされていない状態になっていた。

  • Rが生成されない
     →大抵マニフェストファイル辺りでエラーが出ていることが多いので調べると、なんか出てました。
      コピペして調べたところ、build gradleのcompileSdkVersionが古いと言われたので、22から23にしたら解消された。

諸々のバージョンアップで少しエラーが出ましたが、完成したソースコードは基本的に前回と同じです。


mBaaSとの連携

はじめにGithubリリースページのZIPファイルをダウンロード・解凍し、中にあるNCMB.Jarをプロジェクトに追加します。


追加の仕方


  • app/libsフォルダにNCMB.jarをコピーします。(ファイル一覧上部のプルダウンが"android"になっていると目的のフォルダが見えないことがあるので、その場合は"project"に変更してください。)

  • app/build.gradleファイルのdependances内に以下を追加します。


compile 'com.google.code.gson:gson:2.3.1'
compile files('libs/NCMB.jar')


  • AndroidManifest.xmlのタグの直前に以下のpermissionを追加します。

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

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


  • MainActivity.javaのonCreateメソッド内に以下のコードを追加します。

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


  • 上記コードの"APP_KEY"と"CLIENT_KEY"にmBaaSアプリの「アプリケーションキー」と「クライアントキー」を書き込みます。ダッシュボードを開き、左メニューで「アプリ設定」を選択→「基本」の項目にAキーが表示されているのでコピーして文字列として置き換えてください。

これで連携は完了です。


画像保存部分の実装

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

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

file.saveInBackground(new DoneCallback() {
@Override
public void done(NCMBException e) {
if (e != null) {
//失敗
} else {
//成功
}
}
});

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

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

package com.example.USERNAME.ncmbcamerav2;

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.core.DoneCallback;
import com.nifty.cloud.mb.core.NCMB;
import com.nifty.cloud.mb.core.NCMBAcl;
import com.nifty.cloud.mb.core.NCMBException;
import com.nifty.cloud.mb.core.NCMBFile;

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);

if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction().add(R.id.container, new CameraFragment()).commit();
}
NCMB.initialize(this,"APP_KEY","CLIENT_KEY");
}

@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) {
if (data != null) {
FileOutputStream fos = null;
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, new NCMBAcl());
file.saveInBackground(new DoneCallback() {
@Override
public void done(NCMBException e) {
if (e != null) {
//失敗
} else {
//成功
}
}
});

}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_;
}
}
}

ファイルをアップロードするだけであれば、ほとんど以前のSDKと書き方を変えずに使えそうです。

どの辺りが違うのか、もう少し深堀りしてみたいと思います。

今回は以上です。