woshidan's loose leaf

ぼんやり勉強しています

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表示を動かすことができる
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>

ボタンのタッチ領域を広げる

iphone - How to expand the hitTest area of a UIButton without extruding it's background image? - Stack Overflow

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'

こうなってたのを戻した。余計なことしないでくりー。

現場からは以上です。

stackoverflow.com

ブロックの中でローカル変数を変更したい

ローカル変数は何も修飾子をつけていないとブロックの中で参照はできても変更はできない。

// ダメな例
int count = 0;
[Util executeSomeBlock:^{
  count += 1; // ビルドできない
}];

これだとちょっとしたことを他のスレッドに投げてその結果を受け取りたい、という時不便。 staticつけて静的変数にしたらブロックの中で変更できるようになるが、それでは変数のスコープが必要以上に大きくなってしまう。 こういう、ブロックの中でローカル変数を変更したいときは、変数に __block 修飾子をつけるとできます。

// いける例
__block int count = 0;
[Util executeSomeBlock:^{
  count += 1; // 変更できる!
}];

マルチスレッドプログラミングのレビューにて

先輩に言われてひぎゃーって叫んだ本日のNG集的な。

  • 別スレッドの処理を待って次の処理を実行する方法
    • コールバック
      • コールバック地獄とはなんたるや
    • GCDの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のスレッドから実行される
  });
}

setupconfigure の順序を保証するためにコールバックを使って考えるとすると、 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が完了したことを前提としている
}

となっていたとします。

すると、先ほどの setupconfigure の順序を保証するために、今度は 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

となります。

現場からは以上です。

*1:でもだいたい通信をバックグラウンドで実行してからUIスレッドに戻すという処理が大半なのであまり意識しなかったのが私だ

*2:まぁ、同期実行したいといってるのにasync使ってるのどうなんだという話ですが、JavaのRunnable投げて云々的な初心者な発想だとそうなります

*3:上記投げたメソッドがまだ終わっていないなど. dispatch_syncでタスク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インタフェースみたいに一部のソート系メソッドで、ソートの基準を引数として受け取りたいとき、ブロックとして受け取らせる、みたいなことするみたいです。

詳解 Objective-C 2.0 改訂版

詳解 Objective-C 2.0 改訂版

とりあえず今日はセマフォを使ってみたい

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のメモリ管理とマルチスレッド?

エキスパートObjective-Cプログラミング ?iOS/OS Xのメモリ管理とマルチスレッド?

*1:そしてJavaでもセマフォとかその辺の話はあり、Java並行処理プログラミング積んでてすまんな… って感じ