Camera API(カメラ関係の古い方)を使ってみる件
まとめ
- カメラ撮影中の写ってる範囲を表示している部分はプレビューと呼んで
SurfaceView
で扱う - Camera.setParametersでプレビューの時の画像サイズや、プレビュー後(撮影後)の画像サイズが指定できたはずなのだが、compileSDKVersion 25以上で新規プロジェクト作ったらその関数がAndroid Studioから見つからず。。
Camera.getParameters().setPreviewSize()
などだった- PreviewSize(プレビュー中に表示する画像のサイズ)やPictureSize(撮影後にコールバックに渡す画像のサイズ)は機種ごとにサポートしているサイズが決まっているので
- 撮影後、カメラで撮った画像を処理させるには
Camera.PictureCallback
を定義して、Camera.takePicture(ShutterCallback, PictureCallback, PictureCallback)
- ここで2つ目の
PictureCallback
に渡す方のクラスのonPictureTaken(byte[], Camera)
を定義することで撮影した画像の処理を書くことができる Camera.takePicture()
を呼び出すと、プレビュー部分の画像の更新が止まる.Camera.startPreview()
で再びPreview表示を動かすことができる
- ここで2つ目の
android { compileSdkVersion 25 buildToolsVersion "25.0.1" defaultConfig { applicationId "com.example.woshidan.cameratest" minSdkVersion 15 targetSdkVersion 25 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } }
のとき、Camera APII(カメラ関係の古い方)をちょっと触ってみた。 CameraPreview
はほぼ公式のチュートリアルのまま。
以下のコードでは、一回シャッターボタンを押すと画像がボタンを押したタイミングで止まり、もう一度押すとプレビューが再開される、みたいなもの。
public class MainActivity extends Activity { private Camera mCamera; private CameraPreview mPreview; private boolean isCaptured = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Create an instance of Camera mCamera = getCameraInstance(); // mCamera.getParameters().setXXX...でプレビュー画面の設定などのパラメータを設定できる List<Camera.Size> supportedSizes = mCamera.getParameters().getSupportedPreviewSizes(); mCamera.getParameters().setPreviewSize(supportedSizes.get(supportedSizes.size() - 1).width, supportedSizes.get(supportedSizes.size() - 1).height); // Create our Preview view and set it as the content of our activity. mPreview = new CameraPreview(this, mCamera); FrameLayout preview = (FrameLayout) findViewById(R.id.camera_preview); preview.addView(mPreview); Button captureButton = (Button) findViewById(R.id.button_capture); captureButton.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { // get an image from the camera if (isCaptured) { mCamera.startPreview(); // 撮影用のプレビューをもう一度動かす isCaptured = false; } else { mCamera.takePicture(null, null, mPicture); // 撮影結果を撮影結果をあれこれするコールバック(Camera.PictureCallback)へ渡す // この関数を呼び出すと、Preview部分の表示の更新は止まる isCaptured = true; } } } ); } /** A safe way to get an instance of the Camera object. */ public static Camera getCameraInstance(){ Camera c = null; try { c = Camera.open(0); // attempt to get a Camera instance } catch (Exception e){ // Camera is not available (in use or does not exist) } return c; // returns null if camera is unavailable } private Camera.PictureCallback mPicture = new Camera.PictureCallback() { @Override public void onPictureTaken(byte[] data, Camera camera) { // 結果をSDカードに書き込んだりする } };
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.woshidan.cameratest"> <uses-permission android:name="android.permission.CAMERA" /> <uses-feature android:name="android.hardware.camera" /><!-- カメラが使えない端末でプレイストアで検索したときこのアプリを表示させない --> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@android:style/Theme.Holo"> <activity android:name=".MainActivity" android:label="@string/app_name" android:screenOrientation="landscape" android:theme="@android:style/Theme.Holo.NoActionBar"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
ボタンのタッチ領域を広げる
CGRectInset - Core Graphics | Apple Developer Documentation
@implementation MyButton : UIButton - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { int expandedMargin = 10; CGRect expandedFrame = CGRectInset(self.bounds, - (expandedMargin * 2), - (expandedMargin * 2)); // Insetの返り値で広がった長方形が欲しいときは値をマイナスに return (CGRectContainsPoint(expandedFrame , point) == 1) ? self : nil; } @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. MyButton* button = [[MyButton alloc] init]; button.frame = CGRectMake(0, 0, 100, 100); button.backgroundColor = [UIColor redColor]; [button addTarget:self action:@selector(onClick:) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:button]; } - (void)onClick:(UIView *)view { NSLog(@"clicked"); }
Error:Could not get unknown property 'compile' for object of type org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler.
というエラーメッセージが出てビルドができなくなりました。
Android Studioを起動した時に、build.gradle
の文法に修正が入っていたようで、その修正を元に戻したらいけた。
具体的には、
compile'com.android.support:appcompat-v7:22.2.1' compile 'com.parse.bolts:bolts-tasks:1.3.0' compile 'com.parse:parse-android:1.11.0' compile 'com.android.support:design:22.2.1' compile 'com.android.support:design:22.2.1'
こう書いてたのが、
compile 'com.android.support:appcompat-v7:22.2.1' compile 'com.parse.bolts:bolts-tasks:1.3.0' compile 'com.parse:parse-android:1.11.0' compile 'com.android.support:design:22.2.1' compile 'com.android.support:design:22.2.1'
こうなってたのを戻した。余計なことしないでくりー。
現場からは以上です。
ブロックの中でローカル変数を変更したい
ローカル変数は何も修飾子をつけていないとブロックの中で参照はできても変更はできない。
// ダメな例 int count = 0; [Util executeSomeBlock:^{ count += 1; // ビルドできない }];
これだとちょっとしたことを他のスレッドに投げてその結果を受け取りたい、という時不便。
static
つけて静的変数にしたらブロックの中で変更できるようになるが、それでは変数のスコープが必要以上に大きくなってしまう。
こういう、ブロックの中でローカル変数を変更したいときは、変数に __block
修飾子をつけるとできます。
// いける例 __block int count = 0; [Util executeSomeBlock:^{ count += 1; // 変更できる! }];
マルチスレッドプログラミングのレビューにて
先輩に言われてひぎゃーって叫んだ本日のNG集的な。
- 別スレッドの処理を待って次の処理を実行する方法
- コールバック
- コールバック地獄とはなんたるや
- GCDの
dispatch_sync
使うdispatch_sync
はキューに入れたタスクのデッドロックに注意
- コールバック
別スレッドの処理を待って次の処理を実行する方法
コールバック
コールバックを使うと下記のような形で、別のスレッドで行いたい処理の後に行う処理を呼び出し元から指定できる。
- (void) executeSomethingAsyncWithCompletionHandler:(void (^)())block { dispatch_async(firstQueue, ^{ // firstQueueのスレッドで行いたいようなタスク block(); // blockの内容はfirstQueueのスレッドから実行される }); } // ここはメインスレッドなど他のスレッド [self executeAsyncSomethingWithCompletionHandler: ^ { NSLog(@"execute on firstQueue"); }];
また、指定した処理は確かに別スレッドで行いたい処理の後に実行される。
しかし、executeSomethingAsyncWithCompletionHandler:
のようなコールバックを引数に取るメソッドの後の行より、その処理が後に実行されるとは限らない。
// ここはメインスレッドなど他のスレッド [self executeSomethingAsyncWithCompletionHandler: ^ { NSLog(@"execute on firstQueue"); }]; NSLog(@"this may be executed before firstQueue log"); // 上と下のNSLogの行は同期実行されない。つまり、下のNSLogの行は上の行の実行を待たない。
なので、コールバックの処理の結果を期待している行が非同期実行されるため、その行以降にコールバック処理の結果を前提としている行がないか、メソッド内だけでなく呼び出し元まで辿って確認していく必要がある。
コールバック地獄とはなんたるや
これを呼び出し元まで辿って実行順序や実行されるスレッドを保証しようとしたいとします。
手始めに次の例。
static int seed = 0; - (void) setup { [self executeAsyncSomethingWithCompletionHandler: ^(int _rand) { NSLog(@"execute on firstQueue"); seed = _rand; }]; } - (void) configure { NSLog(@"seed %d", seed); [self requestAPIWithSeed: seed]; // configureはsetup内部で実行されるexecuteSomethingAsyncWithCompletionHandler:の結果を前提としている } - (void) start { [self setup]; [self configure]; // ここで順序が入れ替わっては困る } - (void) executeSomethingAsyncWithCompletionHandler:(void (^)(int))block { dispatch_async(firstQueue, ^{ sleep(10); block(rand() % 10); // blockの内容はfirstQueueのスレッドから実行される }); }
setup
と configure
の順序を保証するためにコールバックを使って考えるとすると、 setup
メソッドが引数としてコールバックを受け取り、setup
メソッドの最後の方で configure
メソッドが含まれるブロックが実行されるように書けばいいかもしれません。
static int seed = 0; - (void) setupWithCompletionBlock:(void (^)())block { [self executeAsyncSomethingWithCompletionHandler: ^(int _rand) { NSLog(@"execute on firstQueue"); seed = _rand; block(); }]; } - (void) configure { NSLog(@"seed %d", seed); [self requestAPIWithSeed: seed]; // configureはsetup内部で実行されるexecuteSomethingAsyncWithCompletionHandler:の結果を前提としている } - (void) start { [self setupWithCompletionBlock:^{ [self configure]; }]; } - (void) executeSomethingAsyncWithCompletionHandler:(void (^)(int))block { dispatch_async(firstQueue, ^{ sleep(10); block(rand() % 10); // blockの内容はfirstQueue以外のスレッドから実行される }); }
ここで、start
の呼び出し元を辿ってみたら、
- (void) initialization { [Configuration start]; [Configuration getCustomSettings]; // startの一連の処理でrequestAPIWithSeedが完了したことを前提としている }
となっていたとします。
すると、先ほどの setup
と configure
の順序を保証するために、今度は start
メソッドの中身が非同期実行になってしまっていたので…これは困りますね。
同じようにコールバックを使って解決しようとすると…
- (void) initialization { [Configuration startWithCompletionHandler:^ { [Configuration getCustomSettings]; // startの一連の処理でrequestAPIWithSeedが完了したことを前提としている }]; }
static int seed = 0; - (void) setupWithCompletionBlock:(void (^)())block { [self executeAsyncSomethingWithCompletionHandler: ^(int _rand) { NSLog(@"execute on firstQueue"); seed = _rand; block(); }]; } - (void) configure { NSLog(@"seed %d", seed); [self requestAPIWithSeed: seed]; // configureはsetup内部で実行されるexecuteSomethingAsyncWithCompletionHandler:の結果を前提としている } - (void) startWithCompletionBlock:(void (^)())block { [self setupWithCompletionBlock:^{ [self configure]; block(); }]; } - (void) executeSomethingAsyncWithCompletionHandler:(void (^)(int))block { dispatch_async(firstQueue, ^{ sleep(10); block(rand() % 10); // blockの内容はfirstQueueのスレッドから実行される }); }
となり、なかなかややこしくなってきましたね。さらに、よくある話ですが、 configure
も非同期処理だったら… ということを考えると非常にめんどくさいですね。処理を書いてるのかblockを渡す機械になってるのかよくわかりません。
また、コールバックの内容を別のスレッドで実行する必要のあるケースはさらにややこしくなりますね、インデントがいくらあっても足りないです。
こういうのをコールバック地獄といい、非同期に行われるが順序を保証したい処理を書くのに便利ということでモバイル界隈で Rx
が爆発的に広まったのでした*1。
GCDのdispatch_sync
を使う
上記のような場合、ObjCやSwiftならdispatch_syncを使って書くと随分シンプルに書けます*2。
static int seed = 0; - (void) setup { dispatch_sync(firstQueue, ^ { int result = [self executeSomething]; NSLog(@"execute on firstQueue"); seed = result; }); // 上のブロックの実行が終わるまでここの行にこないしreturnもしない } - (void) configure { NSLog(@"seed %d", seed); [self requestAPIWithSeed: seed]; // configureはsetup内のexecuteSomethingの結果を前提としている } - (void) start { [self setup]; // setupからreturnして次の行へ行く時、executeSomethingが終わっていることが保証されている [self configure]; // ここで順序が入れ替わらなくて嬉しい! } - (int) executeSomething { sleep(10); // いまさらですが重いタスクの大体のつもり return seed() % 10; }
- (void) initialization { [Configuration start]; // 連鎖的にstartからreturnした時は、requestAPIWithSeedが終わっている [Configuration getCustomSettings]; // startの一連の処理でrequestAPIWithSeedが完了しているので嬉しい! }
別スレッドで実行したい処理について、呼び出し元にややこしいのを伝播せずその場で処理できるので dispatch_sync
よさそうに見えますが、これはあくまでシンタックスやコードの見た目の話です。
処理が重たいので別スレッドで実行したいといった場合は、その重たい処理の実行を元のスレッドで待つことになるので乱用すると全体としてパフォーマンスが悪くなります。
プラットフォームの都合でめっちゃ軽い処理なんだけどどうしても他のスレッドで実行しなくちゃいけない、それを実行しても処理が重くならない、みたいな場合に利用は控えるべきです。
また、次の節みたいな話もあります。
dispatch_sync
はキューに入れたタスクのデッドロックに注意
二つのスレッドでお互いのスレッドのキューにタスクを投げ合って、お互いのキューにまだタスクが残っているうちに、両方が dispatch_sync
で新しくタスクを投げあってしまうとデッドロックが起こることがあります。
具体的に表にすると以下のような例です。
なお、mainQueueはメインスレッドのキュー、myQueueは独自スレッドのキューという感じでお願いします。
独自スレッド | 独自スレッドのキュー | メインスレッド | メインスレッドのキュー |
---|---|---|---|
dispatch_async(mainQueue, タスクA) | |||
タスクB未着手*3 | タスクA | ||
タスクB未着手 | dispatch_sync(myQueue, タスクB) | タスクA | |
タスクB未着手 | タスクB | タスクB消化待ち | タスクA |
dispatch_sync(mainQueue, タスクC) | タスクB | タスクB消化待ち | タスクA |
タスクC消化待ち | タスクB | タスクB消化待ち | タスクA / タスクC |
タスクC消化待ちなのでタスクB着手できず | タスクB | タスクB消化待ちなので、A, C消化できず | タスクA / タスクC |
タスクCの追加がなければ、
独自スレッド | 独自スレッドのキュー | メインスレッド | メインスレッドのキュー |
---|---|---|---|
dispatch_async(mainQueue, タスクA) | |||
タスクB未着手 | タスクA | ||
タスクB未着手 | dispatch_sync(myQueue, タスクB) | タスクA | |
タスクB未着手 | タスクB | タスクB消化待ち | タスクA |
タスクB消化開始 | タスクB | タスクB消化待ち | タスクA |
タスクB消化中 | タスクB | タスクB消化待ち | タスクA |
タスクB消化 | タスクB | タスクB消化待ちなので、A消化できず | タスクA |
A消化開始 | タスクA |
となります。
現場からは以上です。
ブロックを引数にして関数を書く素振り
ブロックはObjective-Cではなく、Mac OS X 10.6, iOS4 以降にC言語の機能として実装されたもの。
他の言語の機能でいうとクロージャ。
試しに書いてみる
// ブロックオブジェクトの定義 // ^(引数列) { 本体 } // int型の引数を一つ取り、値を返さない関数へのポインタを格納する変数f void (*f)(int); // 変数名が真ん中にくるよ!! // int型の引数を一つ取り、値を返さないブロックへのポインタを格納する変数b void (^b)(int); // 関数とほぼ同じ形だけど名前の前には^がつくよ
void (^b)(int) = ^(int i) { NSLog(@"integer %d", i); }; // ブロックオブジェクトの生成? b(1); // 実行 void (^b2)(int i); // 宣言の時に仮引数書いてもいいよ
ブロックの外で定義された変数がキャプチャできる。
int n = 8; void (^b)(int) = ^(int i) { NSLog(@"integer %d", i + n); }; b(1); // integer 9 n = 10; b(1); // integer 9 自動変数についてはブロックオブジェクト生成当時の値をキャプチャしているので、キャプチャ後にnが変更されてもbの結果は変わらない
static int n = 8; void (^b)(int) = ^(int i) { NSLog(@"integer %d", i + n); }; b(1); // integer 9 n = 10; b(1); // integer 11 ローカル変数はキャプチャ以降も値が更新される // ブロックの中からローカル変数を変更することも可能
関数の引数にしたい
void (^b)(int) = ^(int i) { NSLog(@"integer %d", i); }; [self executeBlock:b withArgument:100]; - (void) executeBlock: (void (^)(int))block withArgument:(int)i { NSLog(@"execute block"); block(i); }
2017-08-28 20:29:38.539 Sunaba[70921:6280358] execute block 2017-08-28 20:29:38.539 Sunaba[70921:6280358] integer 100
あとJavaのComparableインタフェースみたいに一部のソート系メソッドで、ソートの基準を引数として受け取りたいとき、ブロックとして受け取らせる、みたいなことするみたいです。
- 作者: 荻原剛志
- 出版社/メーカー: ソフトバンククリエイティブ
- 発売日: 2010/12/17
- メディア: 大型本
- 購入: 13人 クリック: 118回
- この商品を含むブログ (25件) を見る
とりあえず今日はセマフォを使ってみたい
Javaでよくあるインスタンスによるロックしか実はまだよくわからない感じなので*1、とりあえず叩いてみますね。
セマフォを使わない場合
// エキスパートObjCからの引用 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); NSMutableArray *array = [[NSMutableArray alloc] init]; for (int i = 0; i < 100000; ++i) { dispatch_async(queue, ^ { [array addObject:[NSNumber numberWithInt:i]]; }); }
// 実行すると i = 961くらいでクラッシュした Sunaba(69383,0x70000e7b2000) malloc: *** error for object 0x7fb575010400: pointer being freed was not allocated *** set a breakpoint in malloc_error_break to debug
セマフォを使う場合
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); // Dispatch Semaphoreはカウンタ0の時は待つ、1以上の時は減算して待たずに進む // 引数の値でカウンタを初期化した dispatch_semaphore_t semaphore = dispatch_semaphore_create(1); NSMutableArray *array = [[NSMutableArray alloc] init]; for (int i = 0; i < 100000; ++i) { dispatch_async(queue, ^ { // semaphoreのカウントが1以上になるまで dispatch_semaphore_wait の行で待つ // semaphoreのカウントが1以上の場合、1減算して先に進む。 // 二つ目の引数はsemaphoreのカウントが1以上になるまで、どの程度の時間を待つか // DISPATCH_TIME_FOREVER => 永遠になる dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); [array addObject:[NSNumber numberWithInt:i]]; // 排他制御したい場所が終わったので、セマフォのカウンタを1加算 // dispatch_semaphore_wait で待っているスレッドがあれば一番最初のスレッドから処理を再開する dispatch_semaphore_signal(semaphore); }); }
セマフォを使う場合(待ち時間が長かったら待つの中断して先に進みたい場合)
dispatch_semaphore_wait
では、セマフォのカウントが増えるまで待つ時間の上限を指定することができます。
そして、 dispatch_semaphore_wait
の戻り値からスマフォのカウンタが0であるものの時間の上限に達したために処理を進めたのか、スマフォのカウンタが1以上になったため処理を進めたのかを判断することができます。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_semaphore_t semaphore = dispatch_semaphore_create(1); dispatch_async(queue, ^ { NSLog(@"dispatch_semaphore_wait - 1"); dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); NSLog(@"start long task dummy"); sleep(10); NSLog(@"end long task dummy"); NSLog(@"dispatch_semaphore_signal - 1"); dispatch_semaphore_signal(semaphore); }); sleep(3); dispatch_async(queue, ^ { NSLog(@"dispatch_semaphore_wait - 2"); dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_USEC); long result = dispatch_semaphore_wait(semaphore, time); if (result == 0) { NSLog(@"dispatch_semaphore_wait wait success"); } else { NSLog(@"dispatch_semaphore_wait time out"); } NSLog(@"dispatch_semaphore_signal - 2"); dispatch_semaphore_signal(semaphore); });
2017-08-27 00:52:23.344 Sunaba[69701:6121419] dispatch_semaphore_wait - 1 2017-08-27 00:52:23.344 Sunaba[69701:6121419] start long task dummy 2017-08-27 00:52:26.345 Sunaba[69701:6121422] dispatch_semaphore_wait - 2 2017-08-27 00:52:26.345 Sunaba[69701:6121422] dispatch_semaphore_wait time out 2017-08-27 00:52:26.346 Sunaba[69701:6121422] dispatch_semaphore_signal - 2 2017-08-27 00:52:33.348 Sunaba[69701:6121419] end long task dummy 2017-08-27 00:52:33.349 Sunaba[69701:6121419] dispatch_semaphore_signal - 1
現場からは以上です。
エキスパートObjective-Cプログラミング ?iOS/OS Xのメモリ管理とマルチスレッド?
- 作者: 坂本一樹
- 出版社/メーカー: インプレス
- 発売日: 2011/11/18
- メディア: 単行本(ソフトカバー)
- 購入: 8人 クリック: 343回
- この商品を含むブログ (25件) を見る