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

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

この部分は前回と違いがないので、[前回の記事](http://qiita.com/heppoko_dev/items/e2dc82d766c2804de391)を参考にして実装してください。

#### 実装時の注意点

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

* android.support.v4.app.Fragmentとandroid.support.v7.app.AppCompatActivityのインポートでエラーが出る
　→何故かAndroidSupportRepositoryがインストールされていない状態になっていた。
* Rが生成されない
　→大抵マニフェストファイル辺りでエラーが出ていることが多いので調べると、なんか出てました。
　　コピペして調べたところ、build gradleのcompileSdkVersionが古いと言われたので、22から23にしたら解消された。

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

### mBaaSとの連携

はじめに[Githubリリースページ](Chat.stg.devops.nifty.com/nifty-corp/channels/projectiot-microservi)の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の<application>タグの直前に以下の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と書き方を変えずに使えそうです。
どの辺りが違うのか、もう少し深堀りしてみたいと思います。

今回は以上です。