コアサービス開発に必要な情報を整理3
こちらによくまとめられたスライドがありました。参考にさせて頂きました。
http://blog.kmckk.com/archives/3676340.html#more
以下自分用メモメモ
メモ1 androidの素の様子
デバイスのプロセスツリー
init |-zygote-+-system_server-+- |-android.process.acore |-android.process.media |-com.android.phone |-com.adnroid.alarmclock |-com.android.music |-com.android.mms |-com.google.process.gapps |-com.google.android.apps.maps |-mediaserver-+-{mediaserver} | |-{mediaserver} | |-rild-+-{rild} | |-{rild} | `-{rild} | |-servicemanager
chiaki@ubuntu:~/mydroid$ vi frameworks/base/cmds/servicemanager/service_manager.c chiaki@ubuntu:~/mydroid$ vi ./frameworks/base/cmds/servicemanager/binder.c struct binder_state *binder_open(unsigned mapsize) { struct binder_state *bs; bs = malloc(sizeof(*bs)); if (!bs) { errno = ENOMEM; return 0; } bs->fd = open("/dev/binder", O_RDWR); int binder_write(struct binder_state *bs, void *data, unsigned len) { …略 res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr); …略
その他で/dev/binderを使っているところ
chiaki@ubuntu:~/mydroid$ view frameworks/base/libs/binder/ProcessState.cpp static int open_driver() { if (gSingleProcess) { return -1; } int fd = open("/dev/binder", O_RDWR); ||> Javaから使う >|cs| frameworks/base/core/java/android/os/ServiceManager.java frameworks/base/core/java/android/os/ServiceManagerNative.java frameworks/base/core/java/android/os/IServiceManager.java android.os.ServiceManager: IBinder getService(String name) IBinder checkService(String name) Binder DriverにaddServiceを加える void addService(String name, IBinder service) String[] listServices()
メモ2 素の実装
1.IServiceManager、IPCThreadStateを使ってコアサービスをBinderDriverに加える(BBinder::onTransact())ためのクラスを作る
2.1.のオブジェクトを作成してBinderDriverに実際にコアサービスを追加する
3.2を利用するネイティブクラスでは、ServiceManagerのインターフェース(BpBinderのIBinderインターフェース)を使ってサービス2を使う
4.Nativeコードからサービス2を呼び出せる
5.JavaからはNativeコード4を呼び出してサービス2の結果を得られる
Java→JNINativeメソッド→クラス3(のbinder)→IBinderインターフェース→BpBinder→IBinderインターフェース→サービス2
メモ3 メモ2+Proxyパターン
BpBinderを触らず、Proxyを作ってBinderに異なるインターフェースを持たせる
Java→JNINativeメソッド→Nativeのクラス3→クラス3のインターフェース→プロキシクラス→IBinderインターフェース→BpBinder→IBinderインターフェース→サービス2
メモ4 メモ3+Stubパターン
onTransact()をStubで実装する。
Java→JNINativeメソッド→Nativeのクラス3→クラス3のインターフェース→プロキシクラス→IBinderインターフェース→Stub(onTransact)→(インターフェースを介して)→サービス2
コアサービス開発に必要な情報を整理2
onTransact()(IBinder.transact())を直接扱う実装が必要で煩雑になるところを、Proxy-Stubパターンでより簡単に扱える。というC++とJavaの実装例をまとめるのがこのテーマのゴール。で、これは自分用のメモメモ。
chiaki@ubuntu:~/mydroid/frameworks/base/libs/binder$ view ProcessState.cpp sp<ProcessState> ProcessState::self() { if (gProcess != NULL) return gProcess; AutoMutex _l(gProcessMutex); if (gProcess == NULL) gProcess = new ProcessState; return gProcess; } void ProcessState::startThreadPool() { AutoMutex _l(mLock); if (!mThreadPoolStarted) { mThreadPoolStarted = true; spawnPooledThread(true); } } void ProcessState::spawnPooledThread(bool isMain) { if (mThreadPoolStarted) { int32_t s = android_atomic_add(1, &mThreadPoolSeq); char buf[32]; sprintf(buf, "Binder Thread #%d", s); LOGV("Spawning new pooled thread, name=%s\n", buf); sp<Thread> t = new PoolThread(isMain); t->run(buf); } }
chiaki@ubuntu:~/mydroid/frameworks/base/libs/binder$ vi IPCThreadState.cpp void IPCThreadState::joinThreadPool(bool isMain) { } status_t IPCThreadState::transact(int32_t handle, uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) { } status_t IPCThreadState::talkWithDriver(bool doReceive) { } status_t IPCThreadState::executeCommand(int32_t cmd) { …略 case BR_TRANSACTION: { …略 if (tr.target.ptr) { sp<BBinder> b((BBinder*)tr.cookie); const status_t error = b->transact(tr.code, buffer, &reply, tr.flags); …略 } } }
chiaki@ubuntu:~/mydroid/frameworks$ vi base/core/jni/android_util_Binder.cpp virtual status_t onTransact( uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags = 0) { …略 jboolean res = env->CallBooleanMethod(mObject, gBinderOffsets.mExecTransact, code, (int32_t)&data, (int32_t)reply, flags); chiaki@ubuntu:~/mydroid/frameworks$ vi base/core/java/android/os/Binder.java private boolean execTransact(int code, int dataObj, int replyObj, int flags) { …略 try { res = onTransact(code, data, reply, flags); } catch (RemoteException e) { …略
のあたり。
ProcessState::self()->startThreadPool(); IPCThreadState::self()->joinThreadPool();
コアサービス開発に必要な情報を整理
SDKベースサービス実装だけならAIDLの使い方だけでいいかもしれませんが、Systemレベルのコアサービス開発に必要な整理のメモ。
IBinderインターフェース、BinderDriver、ServiceManager、Stub、Proxyあたりを順々と。何回かに分けて、javaベースから、最終的にNative(C++)の実装整理を。
一回目はandroidのプロセス間通信を改めて追いかけるところから。
frameworkを見ると色々なところで類似な実装がされているが、ここではまず、別のアプリケーションを起動するのにおなじみのIntent発行に用いるActivityを追ってみる。最後にAIDLの生成物と比較します。
chiaki@ubuntu:~/mydroid/frameworks/base/core$ vi java/android/app/Activity.java public void startActivityForResult(Intent intent, int requestCode) { if (mParent == null) { Instrumentation.ActivityResult ar = mInstrumentation.execStartActivity( this, mMainThread.getApplicationThread(), mToken, this, intent, requestCode); …略
ふむ、Instrumentation.ActivityResult execStartActivityを追っかける。
chiaki@ubuntu:~/mydroid/frameworks/base/core$ vi java/android/app/Instrumentation.java public ActivityResult execStartActivity( …略 try { int result = ActivityManagerNative.getDefault() .startActivity(whoThread, intent, intent.resolveTypeIfNeeded(who.getContentResolver()), null, 0, token, target != null ? target.mEmbeddedID : null, requestCode, false, false); …略
ふむ、ActivityManagerNative.getDefault()を追っかける。
chiaki@ubuntu:~/mydroid/frameworks/base/core$ vi java/android/app/ActivityManagerNative.java static public IActivityManager getDefault() { if (gDefault != null) {//プロセスの中でサービスを提供できる場合 //if (Config.LOGV) Log.v( // "ActivityManager", "returning cur default = " + gDefault); return gDefault; } //提供できない場合 IBinder b = ServiceManager.getService("activity"); if (Config.LOGV) Log.v( "ActivityManager", "default service binder = " + b); gDefault = asInterface(b); if (Config.LOGV) Log.v( "ActivityManager", "default service = " + gDefault); return gDefault; } //asInterfaceで、Proxyメソッドを使ってBinder経由でサービスを呼び出す /** * Cast a Binder object into an activity manager interface, generating * a proxy if needed. */ static public IActivityManager asInterface(IBinder obj) { if (obj == null) { return null; } IActivityManager in = (IActivityManager)obj.queryLocalInterface(descriptor); if (in != null) { return in; } return new ActivityManagerProxy(obj); }
つまりプロセス間通信は、
他のプロセスからのリクエストをメソッドを実行するStubと、binderを通して他のプロセスに実行を要求するProxyで構成される。
ふむ、ついでなので続けてIActivityManagerを追っかける。
chiaki@ubuntu:~/mydroid/frameworks/base/core$ vi java/android/app/IActivityManager.java public interface IActivityManager extends IInterface { …略 public int startActivity(IApplicationThread caller, Intent intent, String resolvedType, Uri[] grantedUriPermissions, int grantedMode, IBinder resultTo, String resultWho, int requestCode, boolean onlyIfNeeded, boolean debug) throws RemoteException;
ふむ、interfaceで、startActivityが定義されている。これを実装している箇所を追っかける。
chiaki@ubuntu:~/mydroid/frameworks/base/core/java/android/app$ cd java/android/app chiaki@ubuntu:~/mydroid/frameworks/base/core/java/android/app$ grep -r 'implements.*IActivityManager' * 2>/dev/null ActivityManagerNative.java:public abstract class ActivityManagerNative extends Binder implements IActivityManager ActivityManagerNative.java:class ActivityManagerProxy implements IActivityManager
ふむ、ActivityManagerNative.javaに、2箇所。追っかける。
chiaki@ubuntu:~/mydroid/frameworks/base/core/java/android/app$ vi ActivityManagerNative.java public abstract class ActivityManagerNative extends Binder implements IActivityManager { …略 class ActivityManagerProxy implements IActivityManager { public ActivityManagerProxy(IBinder remote) { mRemote = remote; } public IBinder asBinder() { return mRemote; } public int startActivity(IApplicationThread caller, Intent intent, String resolvedType, Uri[] grantedUriPermissions, int grantedMode, IBinder resultTo, String resultWho, int requestCode, boolean onlyIfNeeded, boolean debug) throws RemoteException { //Parcelクラスでシリアライズ Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); data.writeStrongBinder(caller != null ? caller.asBinder() : null); intent.writeToParcel(data, 0); data.writeString(resolvedType); data.writeTypedArray(grantedUriPermissions, 0); data.writeInt(grantedMode); data.writeStrongBinder(resultTo); data.writeString(resultWho); data.writeInt(requestCode); data.writeInt(onlyIfNeeded ? 1 : 0); data.writeInt(debug ? 1 : 0); //シリアライズした引数をIBinderのmRemoteを使ってプロセス間通信を投げてる mRemote.transact(START_ACTIVITY_TRANSACTION, data, reply, 0); reply.readException(); int result = reply.readInt(); reply.recycle(); data.recycle(); return result; }
ふむ、このプロセス間通信の受信側を追っかける。
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { switch (code) { case START_ACTIVITY_TRANSACTION://ここ { data.enforceInterface(IActivityManager.descriptor); IBinder b = data.readStrongBinder(); IApplicationThread app = ApplicationThreadNative.asInterface(b); Intent intent = Intent.CREATOR.createFromParcel(data); String resolvedType = data.readString(); Uri[] grantedUriPermissions = data.createTypedArray(Uri.CREATOR); int grantedMode = data.readInt(); IBinder resultTo = data.readStrongBinder(); String resultWho = data.readString(); int requestCode = data.readInt(); boolean onlyIfNeeded = data.readInt() != 0; boolean debug = data.readInt() != 0; //受け取った引数をデシリアライズしてstartActivityメソッドを呼んでる int result = startActivity(app, intent, resolvedType, grantedUriPermissions, grantedMode, resultTo, resultWho, requestCode, onlyIfNeeded, debug); reply.writeNoException(); reply.writeInt(result); return true; }
ふむ、ここにはstartActivityメソッドがない。上を見るとabstractなので、継承してるクラスを追っかける。
chiaki@ubuntu:~/mydroid/frameworks/base$ vi services/java/com/android/server/am/ActivityManagerService.java public final int startActivity(IApplicationThread caller, Intent intent, String resolvedType, Uri[] grantedUriPermissions, int grantedMode, IBinder resultTo, String resultWho, int requestCode, boolean onlyIfNeeded, boolean debug) { return mMainStack.startActivityMayWait(caller, intent, resolvedType, grantedUriPermissions, grantedMode, resultTo, resultWho, requestCode, onlyIfNeeded, debug, null, null); }
以上、Activityを例に見てきたが、androidでのプロセス間通信で一般的に利用されるAIDLの内幕もつまるところこれと同じ感じ。
例:IThumbnailReceiver.aidlから自動生成されるjavaファイルを見てみると、基本構造は同じ。
public interface IThumbnailReceiver extends android.os.IInterface { …略 public Stub() { …略 } …略 @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException { …略 return super.onTransact(code, data, reply, flags); } private static class Proxy implements android.app.IThumbnailReceiver { private android.os.IBinder mRemote; Proxy(android.os.IBinder remote) { mRemote = remote; }
以下全掲
chiaki@ubuntu:~/mydroid$ vi ./out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/app/IThumbnailReceiver.java public interface IThumbnailReceiver extends android.os.IInterface { /** Local-side IPC implementation stub class. */ public static abstract class Stub extends android.os.Binder implements android.app.IThumbnailReceiver { private static final java.lang.String DESCRIPTOR = "android.app.IThumbnailReceiver"; /** Construct the stub at attach it to the interface. */ public Stub() { this.attachInterface(this, DESCRIPTOR); } /** * Cast an IBinder object into an android.app.IThumbnailReceiver interface, * generating a proxy if needed. */ public static android.app.IThumbnailReceiver asInterface(android.os.IBinder obj) { if ((obj==null)) { return null; } android.os.IInterface iin = (android.os.IInterface)obj.queryLocalInterface(DESCRIPTOR); if (((iin!=null)&&(iin instanceof android.app.IThumbnailReceiver))) { return ((android.app.IThumbnailReceiver)iin); } return new android.app.IThumbnailReceiver.Stub.Proxy(obj); } public android.os.IBinder asBinder() { return this; } @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException { switch (code) { case INTERFACE_TRANSACTION: { reply.writeString(DESCRIPTOR); return true; } case TRANSACTION_newThumbnail: { data.enforceInterface(DESCRIPTOR); int _arg0; _arg0 = data.readInt(); android.graphics.Bitmap _arg1; if ((0!=data.readInt())) { _arg1 = android.graphics.Bitmap.CREATOR.createFromParcel(data); } else { _arg1 = null; } java.lang.CharSequence _arg2; if ((0!=data.readInt())) { _arg2 = android.text.TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(data); } else { _arg2 = null; } this.newThumbnail(_arg0, _arg1, _arg2); return true; } case TRANSACTION_finished: { data.enforceInterface(DESCRIPTOR); this.finished(); return true; } } return super.onTransact(code, data, reply, flags); } private static class Proxy implements android.app.IThumbnailReceiver { private android.os.IBinder mRemote; Proxy(android.os.IBinder remote) { mRemote = remote; } public android.os.IBinder asBinder() { return mRemote; } public java.lang.String getInterfaceDescriptor() { return DESCRIPTOR; } public void newThumbnail(int id, android.graphics.Bitmap thumbnail, java.lang.CharSequence description) throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); try { _data.writeInterfaceToken(DESCRIPTOR); _data.writeInt(id); if ((thumbnail!=null)) { _data.writeInt(1); thumbnail.writeToParcel(_data, 0); } else { _data.writeInt(0); } if ((description!=null)) { _data.writeInt(1); android.text.TextUtils.writeToParcel(description, _data, 0); } else { _data.writeInt(0); } mRemote.transact(Stub.TRANSACTION_newThumbnail, _data, null, android.os.IBinder.FLAG_ONEWAY); } finally { _data.recycle(); } } public void finished() throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); try { _data.writeInterfaceToken(DESCRIPTOR); mRemote.transact(Stub.TRANSACTION_finished, _data, null, android.os.IBinder.FLAG_ONEWAY); } finally { _data.recycle(); } } } static final int TRANSACTION_newThumbnail = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); static final int TRANSACTION_finished = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); } public void newThumbnail(int id, android.graphics.Bitmap thumbnail, java.lang.CharSequence description) throws android.os.RemoteException; public void finished() throws android.os.RemoteException;
保守性とパフォーマンス
元々サーバ系の開発専門で、完全に趣味だけだったAndroid開発。
私のブログにサーバ系(Java,Perl,DB,Webアプリ)の記事が少ないのは、仕事に絡むだけになかなか書き辛いためだったのだが、最近はAndroidも仕事に入ってきて、ブログ更新しづらくなってきた。
最近多人数のチーム開発で感じるのは、これまでの一人ないしは少人数での開発ではあまり意識しなかった、アプリの保守性とパフォーマンスのトレードオフ。
多人数での開発やメンバーの入れ替わりが想定される開発の場合、コードの可読性やメンテナンスのしやすさは重要だ。
まず思いつくのは例えば、Webアプリケーション開発でこれまで定石だったMVC(SmallTalk MVC)やJ2EEのMVC2(MVCとMVC2とは少々異なるがここでは触れない)デザインパターンをAndroidのソースにマップして、ロジック(Model)を再利用できるようにつくり、UI(ViewやContoller)から切り離すことで、保守性・可読性を高くする方法。
かたや、個人的には最重要視したいパフォーマンス。これまでのJavaの定石を用いると、Androidではパフォーマンス劣化をまねくことがあり、保守性とパフォーマンスは実装上相反することが往々にしてある。
妥当なラインでトレードオフをとって、センスの良い設計をすることが、開発初期において大切なことだ。と、当たり前のことを再認識するこのごろである。
CPUエミュレータ
QEMU作者作、JavaScriptによるCPUエミュレータ。
http://bellard.org/jslinux/
に行くとlinuxのbootが始まる。なんと斬新な〜!
以下のデバイスをエミュレートしている。
・32 bit x86 compatible CPU
・8259 Programmble Interrupt Controller
・8254 Programmble Interrupt Timer
・16450 UART
Cで簡単なプログラムもできる。
http://bellard.org/jslinux/term.js でターミナルを、
Typed Arraysが使えるか使えないかで
http://bellard.org/jslinux/cpux86-ta.js か、
http://bellard.org/jslinux/cpux86.js かを切り替えてCPUを再現している。
下のほう http://bellard.org/jslinux/jslinux.js で、
http://bellard.org/jslinux/vmlinux26.bin
http://bellard.org/jslinux/root.bin
http://bellard.org/jslinux/linuxstart.bin
を読み込んで、linuxカーネルロードとかしてるっぽい。
麗ちゃんが最初に遊び始める言語は、簡単にエディットと実行確認できるJavaScriptかねーと、夫婦で話しているが、奥が深くて使い方次第でいろいろな可能性が広がりそうだ。