2011年7月24日日曜日

Cameraの機種依存 (1)

自作Androidアプリから、Camera撮影をする場合は、2つの方法があります。
1.Intentで端末のカメラアプリを呼び出す
2.SurfaceViewを使って、カメラスルー画面を直接呼び出す。
どちらもWebをちょっと調べると、サンプルコードが出てきます。

が。

Camera機能は、機種依存動作が強すぎるようで、結構はまりどころです。

やろうとしていたのは、カメラで写真をとって、FaceDetectorで顔認識をするアプリ。
まず、Intentでカメラアプリを呼び出す方法。

カメラにIntentを投げて、onActivityResult()で、結果を受け取る、というのが大体の流れです。
この、結果を受けとる、ところがクセモノで、機種によって受け取れ方が違います。

1.intent.getData()でUriを取得する。HTC Desire/Xperia acroなんかはこれっぽい。
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
    if (resultCode == RESULT_CANCELED)
    {
        return;
    }

    Uri uri = data.getData();
    getImage(uri);
}

2. 事前に、IntentのputExtraで書き出し先のUriを指定して、Intentを発行する。SHARP製のAndroidはこの動作。
public void launchCamera()
{
  // Create your own image Uri
  ...
  Intent intent = new Intent();
  intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
  intent.putExtra(MediaStore.EXTRA_OUTPUT, mImageUri);
  startActivityForResult(intent, 2);
}

protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
  if (resultCode == RESULT_CANCELED)
  {
    if (mImageUri == null)
    //delete file at mImageUri
    
     return;
  }
  getImage(mImageUri);
}

このmImageUriに、画像が書きだされてますので、onActivityResult()が呼ばれれば、mImageUriからそのまま取得できます。

3. 1も2もしてくれない・・・Galaxy Sがこのパターン
えっと・・・これが一番困りました。
intent.getData()はnullだし、2をやると、違うUriに書きだしてくるし、、、

結局、画像の中で最新の画像を取得するコードを書いています。
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
  if (resultCode == RESULT_CANCELED)
  {
    return;
  }

  Uri uri = data.getData();
  if (uri == null)
  {
    ContentResolver cr = getContentResolver();
    Cursor c = MediaStore.Images.Media.query(cr,
    MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
      null,null,
      MediaStore.Images.ImageColumns.DATE_TAKEN + " DESC");

    c.moveToFirst();
    String id =c.getString(c.getColumnIndexOrThrow(BaseColumns._ID);

    Uri uri = Uri.withAppendedPath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, + id);

    getImage(uri);
  } else {
    getImage(uri);
  }
}

もちろん、このコードは、最新の時刻の画像=今とった画像、ではないパターンで問題が出ます。
撮影直後に別のアプリが画像書き出してた、とか。タイムスタンプが未来の画像が入っちゃってる、とか。
エラーチェックを厳しくすればいいのでしょうが、、、

一応、1,2,3ともに共存可能なので、3は最終手段として使う予定です。

なお、Galaxyの場合、以下のような動作もあり。
・撮影が終わってもカメラアプリが終了しないという問題(仕様?)
・カメラ呼び出し元のActivityが終了させられてしまう

前者は、作ってるアプリの仕様上、撮影後はすぐに別画面に遷移させる必要があるので、
別画面を起動するIntentを投げてカメラを終了(中断)させています。

後者が厄介で、Activity::onDestroy()が呼ばれた上、GCまでされちゃいます。
カメラから呼び出し元Activityに戻ると、onCreate()からやり直しです。
つまり変数が全て初期化されます。スッゲ泣ける。

実は、SurfaceViewを使う方法でも、Galaxy Sは大変なんですが、、、それは次回にします。



Intentional Disciplemaking: Cultivating Spiritual Maturity in the Local Church
楽天ブックス
INTENTIONAL DISCIPLEMAKING Ron Bennett NAV PR2001

楽天市場 by Intentional Disciplemaking: Cultivating Spiritual Maturity in the Local Church の詳しい情報を見る / ウェブリブログ商品ポータル

4 件のコメント:

  1. こういう話があるようです。
    onConfigurationChangedを拾うようにすればonDestroyが起きないのでは。

    Galaxy Sでカメラアプリを使用する時の注意点
    http://relog.xii.jp/archives/2010/11/galaxy_s.html

    Configuration Changes
    http://developer.android.com/reference/android/app/Activity.html#ConfigurationChanges

    返信削除
  2. コメントありがとうございます。

    なるほど、横向きにされちゃうから、onDestroy()がくるとは・・・盲点・・・
    今のコードはこの現象が発生しなくなってるのですが、少し調べたところ、どうやら呼び出し元のActivity(onDestroyが呼ばれちゃってたもの)を縦固定にしたため、発生しなくなったようです。
    onConfigurationChanged()のほかにも、これでもいけるっぽいです。

    返信削除
  3. 大変有用な情報ありがとうございました.

    方法3をうまく使えば,各社独自仕様になってるカメラのインテント周りをこれ1つでこなすことができるかと思います.

    わざわざ端末毎に場合分けするのも大変になりますし...

    返信削除
    返信
    1. お役に立てたようで幸いです。

      ググってみても、いろんな人の苦労の跡が忍ばれます。

      ところで、今は(この記事を書いたときはなかったと思う)、公式にもCameraの利用方法が上がっているようです
      http://developer.android.com/intl/ja/guide/topics/media/camera.html

      せめてこれに従えば良いという状況になればいいのですが。

      削除