2011年9月13日火曜日

Android AIDL でservice へアクセスする

Google Developer Day 2011 の参加のためのクイズで、AIDLでサービスへアクセスしてコードを取得してくださいという問題があったので、AIDLについて勉強してみました。




ActivityとServiceの間の通信方法です。






こちらの記事を非常に参考にさせて頂きました。ありがとうございます。




手順は下記です。

1. AIDLファイルにIPCのインターフェースを記述
2. Serviceにインターフェースを実装
3. ActivityからServiceへアクセス
4. Manifest.xmlにServiceを宣言



<手順1>
インターフェースを記述します。

Activityのあるところと同じ階層で、新規ファイルでFileを追加し、拡張子をaidlで保存します。今回のaidlはこのような感じです。



package com.google.android.apps.gddquiz;

interface IQuizService {
  String getCode();
}


ファイルを追加し保存すると、gen以下にR.javaと同じ階層に保存したファイル名と同じ名前で、.javaファイルが自動生成されます。
※packeageのパスがあっているかどうか確認してください。



<手順2>
次に、Serviceにインターフェースを実装します。

既に存在するActivityと同じ名前で、……Service.javaでファイルを追加します。


package com.google.android.apps.gddquiz;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;

import com.google.android.apps.gddquiz.IQuizService;;

public class Gdd2011Service extends Service {

@Override
public IBinder onBind(Intent intent) {
System.out.println("Service::onBind");
return quizService;
//return null;
}

private IQuizService.Stub quizService = new IQuizService.Stub(){
public String getCode() throws RemoteException {
System.out.println("test!!!!!");
return getCode();
}
};
}


こんな感じです。Service.onBindをオーバーライドしてインターフェースを実装したクラスインスタンスを返します。


今のところファイル構成はこのような感じです。







<手順3>
ActivityからServiceにアクセスします。
……Activity.javaを開きます。



package com.google.android.apps.gddquiz;

import com.google.android.apps.gddquiz.IQuizService;

import android.app.Activity;
import android.os.Bundle;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.widget.TextView;

public class Gdd2011Activity extends Activity {

private IQuizService quizService;
private TextView tv;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
     
tv = new TextView(this);

     
        Intent intent = new Intent(IQuizService.class.getName());
        bindService(intent, _serviceConnection, BIND_AUTO_CREATE);

       /* ここで実行してましたが、起動できないようだったので、Connection後取得するように☆印の下記に追加しました。
        try {
String code = quizService.getCode();
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
*/
    }
 
private ServiceConnection _serviceConnection = new ServiceConnection() {

public void onServiceConnected(ComponentName name, IBinder service) {
System.out.println("onServiceConnected!!");
quizService = IQuizService.Stub.asInterface(service);

                        // ☆ ここに移動
try {
String code = quizService.getCode();

            tv.setText(code);
                setContentView(tv);

} catch (RemoteException e) {
System.out.println("Connected false!!");

e.printStackTrace();
}
}
public void onServiceDisconnected(ComponentName name) {
quizService = null;
}
};
}


 Intent intent = new Intent(IQuizService.class.getName());
でサービスのインテントを作成し、bindServiceより、アクセスします。


<手順4>
Manifestにサービス追加





       <service android:name=".Gdd2011Service"
                  android:label="@string/app_name"
                  android:process=":remote">
            <intent-filter>
                <action android:name="com.google.android.apps.gddquiz.IQuizService" />
            </intent-filter>
        </service>  

               
           
       



これをActivityタグの後に追加します。


以上です。


間違っていたり、もっといいやりかたがあったら教えて下さい。

よろしくお願いしますー




2011年9月9日金曜日

iPhone Cocos2D 背景をカメラに!





Cocos2D は、iPhoneのゲームアプリでよく使われています。
ゲームアプリを作るならこれを勉強しましょう!


Cocos2D iPhone






まず、これを使うとどんないいことがあるかというと、
・ページの繊維がわかりやすい
・アニメーション豊富でわかりやすく使いやすい
・OpenGLESを普通に使うなら、絶対Cocos2Dを使ったほうが簡単
・フレームアニメーションが気持ちよくできる
・インターフェースビルダーが嫌いな人にはもってこい。インターフェースビルダーは一切使いません。




などなど、他にもたくさんありますが、とりあえず、ゲームっぽい動きのあるアプリを作るならば、使ったほうがいいです。




まずCocos2Dのインストールは、Cocos2dのインストールをどうぞ。










cocos2dで新規プロジェクトを作成すると、下記のようなファイルがあります。
※バージョンは、1.0.1です。




・GameConfig.h
・AppDelegate.h
・AppDelegate.m
・RootViewController.h
・RootViewController.m
・HellowWorldLayer.h
・HellowWorldLayer.m


※あとライブラリがごっそりあります。




通常のiPhoneアプリでは、Viewという概念で、ViewControllerがViewを表示して、NavigationControllerがViewControllerたちを遷移させてページを切り替えたりなど、Viewという概念の元動いています。


この方の記事がわかりやすいと思います。
UIViewとUIVIewControllerの違いについて




しかし、Cocos2Dではノードやシーン、レイヤーという概念になります。
cocos2dの基本的な概念




では、もともとのWindowや、UIViewControllerからどのような仕組みでcocos2Dの概念になっているのでしょうか。




こんな感じです↓↓↓




下から順番に


WIndow → RootViewController → EAGLView




Ditectorというのが、Cocos2Dでベースになる部分ですが、普通にやるならば、上記のことは一切気にしなくても問題ありません。


Ditectorというのに、シーンやレイヤーが入れ替わって画面遷移したり、レイヤーを重ねたりなどというような形になります。


HellowWorldLayerからNextLayerに切り替わるときは、例えばこんな感じです。




HellowWorldLayerにてボタンを押された際などに、










    id scene = [NextLayer scene];
    id transition = [CCTransitionFade transitionWithDuration:1.0f scene:scene];
    
    CCDirector *director = [CCDirector sharedDirector];
    
    [director replaceScene: transition];












これは、Fadeアニメーションをしつつ画面遷移するという内容です。
Ditectorに載っていたHellowWorldLayerからNextLayerに切り替えます。




このような形で、簡単に遷移できます。










大体、仕組みはこのようになりますが、もし、ARアプリのようなカメラを背景にしたい場合、ちょっと厄介です。




そもそも基本的に、UIView配下でしか使用できないものだからです。
他にもActionSheetや、UITextFiledなどのもともと使用できるものを使いたくても、Cocos2Dだと、ちょっと一工夫しなければ使用することができません。




その方法は、


EAGLView Ditector の下にあるRootViewControllerに貼ることです。


使いたいLayerで、




#import AppDelegate.h





※例えば、UITextField *textField = [UITextField alloc]initWith………];





AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
[appDelegate.viewController addSubView: textField];






という形で、AppDelegateクラスの中で、RootViewControllerを@property 宣言しておけば、上記のようにappDelegateを取得し、RootViewControllerをレイヤーから取得できます。






ですが、これらは上記で概要を説明した通り、DitectorやLayerに関係なく、もとに引いてあるRootViewControllerに貼るわけですから、シーンやレイヤーを移動しても、関係なく表示され続けますので、そのもとのViewControllerから削除剃る必要があります。




[textField remove removeFromSuperView];


で、削除できます。






では、本題のカメラ背景です!






これも上記とやはり概念は同じなので、上記と同じように、RootViewControllerへ貼ることになります。




が、Cocos2Dのlayerの背景をカメラにするわけですから、かなり厄介です。
つまりは、こうなります。




Window → RootViewController ここにカメラを載せる → EAGLView → Layerなど




このような形にするためには、まずは、上記のUITextFieldと同じように、RootViewControllerへカメラを貼ります。
※通常はRootViewに貼るのですが、カメラの場合WIndowに対してはらないと、表示さないようです。なので、Windowの上に乗っかっているRootViewControllerの背景も透明にする必要があります。


詳細はこちら


ですが貼っただけでは、背景はカメラになりません。
なぜかというと、RootViewController(ここではWIndow)の上には、EAGLViewが乗っているからです。


なので、Cocos2Dアニメーションの描画元である、EAGLViewの背景を透明にする必要があります。




<手順>
1. 背景をカメラにしたいシーンやレイヤーで、RootViewController(Windowです)にカメラを貼る
2. EAGLViewの背景を透明にする








カメラを表示するには、UIImagePickerControllerを使うの一般的ですが、重たいという噂だったので、今回は、AVCaptureSessionを使用しました。




1. 背景カメラを挿入したいLayerで


// EAGLView の色を透明宣言しとく。(これだけでは透明にならないです)


glClearColor(0,0,0,0);
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

// captureSession 作成
captureSession = [[AVCaptureSession alloc] init];
if ([captureSession canSetSessionPreset:AVCaptureSessionPresetMedium]) {
    captureSession.sessionPreset = AVCaptureSessionPresetMedium;
}
            
NSError *error = nil;
            
AVCaptureDevice *captureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:captureDevice error:&error];
 if (!input) {
    NSLog(@"input error");
 }
 [captureSession addInput:input];

// 実際に貼るvideoLayer作成
videoPreviewLayer = [AVCaptureVideoPreviewLayer layerWithSession:captureSession];
videoPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
            
CALayer *viewLayer = [[CCDirector sharedDirector] openGLView].layer;
//NSLog(@"viewLayer = %@", viewLayer);
            
videoPreviewLayer.frame = viewLayer.bounds;

// RootViewControllerへアクセス
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];

// windowに対してカメラの場合、なぜかWindowじゃないと貼れません。
[appDelegate.window.layer insertSublayer:videoPreviewLayer below:appDelegate.viewController.view.layer];

// カメラをWIndowにはらなければならいので、
//その上に乗っかているRootViewControllerの背景も透明にする。
appDelegate.viewController.background = [UIColor clearColor];

[captureSession startRunning];



これで、Windowの上にカメラ映像を貼り、RootViewControllerの背景を透明にすることができました。



次にEAGLViewの背景を透明にします。


2. AppDelega.m

// 下記でEAGLViewのピクセルフォーマットをRGB565からRGBA8へ変更
EAGLView *glView = [EAGLView viewWithFrame:[window bounds]
                                 pixelFormat:kEAGLColorFormatRGB565


                                 pixelFormat:kEAGLColorFormatRGBA8    // kEAGLColorFormatRGBA8に変更
                                 depthFormat:0
];



EAGLViewのレイヤーに対して、同じピクセルフォーマット数でプロパティの設定をする
    
CAEAGLLayer *layer = (CAEAGLLayer*)glView.layer;
layer.opaque = NO;
    
layer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
                                [NSNumber numberWithBool:YES], 
                                kEAGLDrawablePropertyRetainedBacking,
                                kEAGLColorFormatRGBA8, // kEAGLColorFormatRGBA8
                                kEAGLDrawablePropertyColorFormat, nil];



※ピクセルフォーマットをkEAGLColorFormatRGBA8にしないと、透明になりません。
しかし、kEAGLColorFormatRGBA8にすると、全体的な動作が重たくなりますのでご注意を。

これで実行すれば、背景がカメラになります。




以上です。

皆さんも是非試してみてください。

また内容がおかしかったり、もっといい方法がお分かりの方は、コメントください。