Handlerのブロックについて
9846 ワード
なぜHook Handler?Androidシステムには大量のHandlerが存在し、システムのいくつかの動作を変えるにはHookがキーノードのHandlerを落とす必要があります.そのため、Handlerの動作原理を理解する必要があります.
まず、Hook Handlerの効果を見てみましょう.以下のようにします.
2つのボタンを置いて、1つのボタンはメッセージを送信してToastを表示して、もう1つのボタンはHook Handlerで、送信したメッセージの内容を変更します.
ここでHookがやっていることは簡単ですが、実は反射でHanderのmCallbackを変えることです.注目すべきはこのCallbackのhandleMessageでMessageを変えてfalseに戻ったことです.HandlerのhandleMessageを呼び出し続けるためだが、Handlerが彼に送ったMessageがひそかに手足を動かしたことを知らない.
Handlerの内在的な実装を見てみましょう.Handlerは実際にはメッセージをカプセル化してMessageQueueに捨てるためのツールにすぎません.本当のコアはLooperとMessageQueueにあります.Looperを重点的に見てみましょう.loop関数があります.メッセージをループ処理しています.以下のようにします.
論理は簡単ですqueue.next()は、MessageQueueから順にMessageを取り出し、msgを呼び出す.targetのdispatchMessage、このtargetは実はHandlerで、HandlerがどのようにdispatchMessageしたのかを見てみましょう.
まずMessageのCallbackを優先的に呼び出す、HandlerのグローバルCallbackを再び呼び出さなければ、mCallbackがなければHandlerのhandleMessageを呼び出す、mCallbackがあればmCallbackがある.handleMessageはfalseを返し、HandlerのhandleMessageも呼び出され、trueを返すとそのまま返されます.私たちのHookの核心はここにあります.
では、ついでにMessageQueueからメッセージを取り出す方法を見てみましょう.次のようにします.
これは、終了フラグがセットされない限り、無限ループのプロセスです.メッセージ・キューからメッセージを取り出すと、キューが空であるためブロックされる可能性があります.ここでnativePollOnceは待ち行列にメッセージがあり,メッセージがあるとすぐに戻る.実装を見てみましょうnativeの関数です
真相が明らかになったのはepollだった.waitはfdが読み書きできるかどうかを監視します.このfdは通常パイプです.では、キューにメッセージを追加するときに、このfdにデータを書く必要があることを意味しますか.epoll_waitは呼び覚ますことができます.メッセージがキューに追加される方法を見てみましょう.
メッセージをキューに追加した後、nativeWakeを呼び出してキューを起動します.
案の定、ここで目覚めたのはfdにW文字を書いただけだ.
まず、Hook Handlerの効果を見てみましょう.以下のようにします.
public class MainActivity extends Activity {
private Button mBtnShow;
private Button mBtnHook;
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
Toast.makeText(MainActivity.this, (CharSequence) msg.obj,
Toast.LENGTH_SHORT).show();
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mBtnShow = (Button) findViewById(R.id.btn);
mBtnHook = (Button) findViewById(R.id.hook);
mBtnShow.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
mHandler.obtainMessage(0, "hello world").sendToTarget();
}
});
mBtnHook.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
try {
hookHandler();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
}
private void hookHandler() throws IllegalAccessException,
IllegalArgumentException {
Field field = FieldUtils.getDeclaredField(Handler.class, "mCallback",
true);
field.set(mHandler, new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
// TODO Auto-generated method stub
msg.obj = "hello china";
return false;
}
});
}
}
2つのボタンを置いて、1つのボタンはメッセージを送信してToastを表示して、もう1つのボタンはHook Handlerで、送信したメッセージの内容を変更します.
ここでHookがやっていることは簡単ですが、実は反射でHanderのmCallbackを変えることです.注目すべきはこのCallbackのhandleMessageでMessageを変えてfalseに戻ったことです.HandlerのhandleMessageを呼び出し続けるためだが、Handlerが彼に送ったMessageがひそかに手足を動かしたことを知らない.
Handlerの内在的な実装を見てみましょう.Handlerは実際にはメッセージをカプセル化してMessageQueueに捨てるためのツールにすぎません.本当のコアはLooperとMessageQueueにあります.Looperを重点的に見てみましょう.loop関数があります.メッセージをループ処理しています.以下のようにします.
public static void loop() {
final Looper me = myLooper();
final MessageQueue queue = me.mQueue;
for (;;) {
Message msg = queue.next();
msg.target.dispatchMessage(msg);
msg.recycle();
}
}
論理は簡単ですqueue.next()は、MessageQueueから順にMessageを取り出し、msgを呼び出す.targetのdispatchMessage、このtargetは実はHandlerで、HandlerがどのようにdispatchMessageしたのかを見てみましょう.
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
まずMessageのCallbackを優先的に呼び出す、HandlerのグローバルCallbackを再び呼び出さなければ、mCallbackがなければHandlerのhandleMessageを呼び出す、mCallbackがあればmCallbackがある.handleMessageはfalseを返し、HandlerのhandleMessageも呼び出され、trueを返すとそのまま返されます.私たちのHookの核心はここにあります.
では、ついでにMessageQueueからメッセージを取り出す方法を見てみましょう.次のようにします.
Message next() {
for (;;) {
..........
nativePollOnce(mPtr, nextPollTimeoutMillis);
..........
if (mQuitting) {
return null;
}
}
}
これは、終了フラグがセットされない限り、無限ループのプロセスです.メッセージ・キューからメッセージを取り出すと、キューが空であるためブロックされる可能性があります.ここでnativePollOnceは待ち行列にメッセージがあり,メッセージがあるとすぐに戻る.実装を見てみましょうnativeの関数です
static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jclass clazz,
jint ptr, jint timeoutMillis) {
NativeMessageQueue* nativeMessageQueue = reinterpret_cast(ptr);
nativeMessageQueue->pollOnce(env, timeoutMillis);
}
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
..........
pollInner(timeoutMillis);
}
int Looper::pollInner(int timeoutMillis) {
..........
int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
for (int i = 0; i < eventCount; i++) {
..........
}
}
真相が明らかになったのはepollだった.waitはfdが読み書きできるかどうかを監視します.このfdは通常パイプです.では、キューにメッセージを追加するときに、このfdにデータを書く必要があることを意味しますか.epoll_waitは呼び覚ますことができます.メッセージがキューに追加される方法を見てみましょう.
boolean enqueueMessage(Message msg, long when) {
..........
if (needWake) {
nativeWake(mPtr);
}
return true;
}
メッセージをキューに追加した後、nativeWakeを呼び出してキューを起動します.
static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jint ptr) {
NativeMessageQueue* nativeMessageQueue = reinterpret_cast(ptr);
return nativeMessageQueue->wake();
}
void NativeMessageQueue::wake() {
mLooper->wake();
}
void Looper::wake() {
ssize_t nWrite;
do {
nWrite = write(mWakeWritePipeFd, "W", 1);
} while (nWrite == -1 && errno == EINTR);
if (nWrite != 1) {
if (errno != EAGAIN) {
ALOGW("Could not write wake signal, errno=%d", errno);
}
}
}
案の定、ここで目覚めたのはfdにW文字を書いただけだ.