本篇文章已授權微信公眾號 guolin_blog (郭霖)獨家發布
本人小楠——一位勵志的Android開發者。
前言
在分析Application Framework的時候,經常會看到Handler的使用,尤其見得最多的是“H”這個系統Handler的使用。因此有必要先學習Android中的消息機制。
應用程序的入口分析
應用程序的入口是在ActivityThread的main方法中的(當應用程序啟動的時候,會通過底層的C/C++去調用main方法),這個方法在ActivityThread類的最后一個函數里面,核心代碼如下:
public static void main(String[] args) {
Environment.initForCurrentUser();
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
Looper.loop();
}
在分析源碼的時候,你可能會發現一些if(false){}之類的語句,這種寫法是方便調試的,通過一個標志就可以控制某些代碼是否執行,比如說是否輸出一些系統的Log。
在main方法里面,首先初始化了我們的Environment對象,然后創建了Looper,然后開啟消息循環。根據我們的常識知道,如果程序沒有死循環的話,執行完main函數(比如構建視圖等等代碼)以后就會立馬退出了。之所以我們的APP能夠一直運行著,就是因為Looper.loop()里面是一個死循環:
public static void loop() {
for (;;) {
}
}
這里有一個小小的知識,就是之所以用for (;;)而不是用while(true)是因為防止一些人通過黑科技去修改這個循環的標志(比如通過反射的方式)
在非主線程里面我們也可以搞一個Handler,但是需要我們主動去為當前的子線程綁定一個Looper,并且啟動消息循環。
Looper主要有兩個核心的方法,一是prepare,而是開始loop循環。
通過Looper、Handler、Message、MessageQueue等組成了Android的消息處理機制,也叫事件、反饋機制。
為什么需要這樣一個消息機制?
我們知道每一個應用程序都有一個主線程,主線程一直循環的話,那么我們的自己的代碼就無法執行了。而系統在主線程綁定一個Looper循環器以及消息隊列,Looper就像是一個水泵一樣不斷把消息發送到主線程。如果沒有消息機制,我們的代碼需要直接與主線程進行訪問,操作,切換,訪問主線程的變量等等,這樣做會帶來不安全的問題,另外APP的開發的難度也會提高,同時也不利于整個Android系統的運作。有了消息機制,我們可以簡單地通過發送消息,然后Looper把消息發送到主線程,然后就可以執行了。
消息其中包括:
我們自己的操作消息(客戶端的Handler)
系統的操作消息(系統Handler):比如啟動Activity等四大組件(例如突然來電話的時候跳轉到電話界面)
我們的思路是先分析系統的Handler,然后再去深入理解消息機制里面各個部件。
舉個例子,廣播:AMS發送消息到MessageQueue,然后Looper循環,系統的Handler取出來以后才處理。(AMS是處理四大組件的生命周期的一個比較重要的類,在以后我們分析IPC機制以及Activity啟動流程的時候會提到)
系統的Handler在哪里?
在ActivityThread的成員變量里面有一個這樣的大H(繼承Handler),這個就是系統的Handler:
final H mH = new H();
回顧一下ActivityThread的main方法可以知道,在new ActivityThread的時候,系統的Handler就就初始化了,這是一種餓加載的方法,也就是在類被new的時候就初始化成員變量了。另外還有一種懶加載,就是在需要的時候才去初始化,這兩種方式在單例設計模式里面比較常見。
public static void main(String[] args) {
Environment.initForCurrentUser();
Looper.prepareMainLooper();
//new 的時候已經把成員變量Handler初始化了
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
Looper.loop();
}
下面看系統Handler的定義(看的時候可以跳過一些case,粗略地看即可):
private class H extends Handler {
public static final int LAUNCH_ACTIVITY = 100;
public static final int PAUSE_ACTIVITY = 101;
public static final int PAUSE_ACTIVITY_FINISHING= 102;
public static final int STOP_ACTIVITY_SHOW = 103;
public static final int STOP_ACTIVITY_HIDE = 104;
public static final int SHOW_WINDOW = 105;
public static final int HIDE_WINDOW = 106;
public static final int RESUME_ACTIVITY = 107;
public static final int SEND_RESULT = 108;
public static final int DESTROY_ACTIVITY = 109;
public static final int BIND_APPLICATION = 110;
public static final int EXIT_APPLICATION = 111;
public static final int NEW_INTENT = 112;
public static final int RECEIVER = 113;
public static final int CREATE_SERVICE = 114;
public static final int SERVICE_ARGS = 115;
public static final int STOP_SERVICE = 116;
public static final int CONFIGURATION_CHANGED = 118;
public static final int CLEAN_UP_CONTEXT = 119;
public static final int GC_WHEN_IDLE = 120;
public static final int BIND_SERVICE = 121;
public static final int UNBIND_SERVICE = 122;
public static final int DUMP_SERVICE = 123;
public static final int LOW_MEMORY = 124;
public static final int ACTIVITY_CONFIGURATION_CHANGED = 125;
public static final int RELAUNCH_ACTIVITY = 126;
public static final int PROFILER_CONTROL = 127;
public static final int CREATE_BACKUP_AGENT = 128;
public static final int DESTROY_BACKUP_AGENT = 129;
public static final int SUICIDE = 130;
public static final int REMOVE_PROVIDER = 131;
public static final int ENABLE_JIT = 132;
public static final int DISPATCH_PACKAGE_BROADCAST = 133;
public static final int SCHEDULE_CRASH = 134;
public static final int DUMP_HEAP = 135;
public static final int DUMP_ACTIVITY = 136;
public static final int SLEEPING = 137;
public static final int SET_CORE_SETTINGS = 138;
public static final int UPDATE_PACKAGE_COMPATIBILITY_INFO = 139;
public static final int TRIM_MEMORY = 140;
public static final int DUMP_PROVIDER = 141;
public static final int UNSTABLE_PROVIDER_DIED = 142;
public static final int REQUEST_ASSIST_CONTEXT_EXTRAS = 143;
public static final int TRANSLUCENT_CONVERSION_COMPLETE = 144;
public static final int INSTALL_PROVIDER = 145;
public static final int ON_NEW_ACTIVITY_OPTIONS = 146;
public static final int CANCEL_VISIBLE_BEHIND = 147;
public static final int BACKGROUND_VISIBLE_BEHIND_CHANGED = 148;
public static final int ENTER_ANIMATION_COMPLETE = 149;
public static final int START_BINDER_TRACKING = 150;
public static final int STOP_BINDER_TRACKING_AND_DUMP = 151;
public static final int MULTI_WINDOW_MODE_CHANGED = 152;
public static final int PICTURE_IN_PICTURE_MODE_CHANGED = 153;
public static final int LOCAL_VOICE_INTERACTION_STARTED = 154;
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
case LAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
r.packageInfo = getPackageInfoNoCheck(
r.activityInfo.applicationInfo, r.compatInfo);
handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
} break;
case RELAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityRestart");
ActivityClientRecord r = (ActivityClientRecord)msg.obj;
handleRelaunchActivity(r);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
} break;
case PAUSE_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
SomeArgs args = (SomeArgs) msg.obj;
handlePauseActivity((IBinder) args.arg1, false,
(args.argi1 & USER_LEAVING) != 0, args.argi2,
(args.argi1 & DONT_REPORT) != 0, args.argi3);
maybeSnapshot();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
} break;
case PAUSE_ACTIVITY_FINISHING: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
SomeArgs args = (SomeArgs) msg.obj;
handlePauseActivity((IBinder) args.arg1, true, (args.argi1 & USER_LEAVING) != 0,
args.argi2, (args.argi1 & DONT_REPORT) != 0, args.argi3);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
} break;
case STOP_ACTIVITY_SHOW: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStop");
SomeArgs args = (SomeArgs) msg.obj;
handleStopActivity((IBinder) args.arg1, true, args.argi2, args.argi3);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
} break;
case STOP_ACTIVITY_HIDE: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStop");
SomeArgs args = (SomeArgs) msg.obj;
handleStopActivity((IBinder) args.arg1, false, args.argi2, args.argi3);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
} break;
case SHOW_WINDOW:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityShowWindow");
handleWindowVisibility((IBinder)msg.obj, true);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case HIDE_WINDOW:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityHideWindow");
handleWindowVisibility((IBinder)msg.obj, false);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case RESUME_ACTIVITY:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityResume");
SomeArgs args = (SomeArgs) msg.obj;
handleResumeActivity((IBinder) args.arg1, true, args.argi1 != 0, true,
args.argi3, "RESUME_ACTIVITY");
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case SEND_RESULT:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityDeliverResult");
handleSendResult((ResultData)msg.obj);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case DESTROY_ACTIVITY:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityDestroy");
handleDestroyActivity((IBinder)msg.obj, msg.arg1 != 0,
msg.arg2, false);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case BIND_APPLICATION:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
AppBindData data = (AppBindData)msg.obj;
handleBindApplication(data);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case EXIT_APPLICATION:
if (mInitialApplication != null) {
mInitialApplication.onTerminate();
}
Looper.myLooper().quit();
break;
case NEW_INTENT:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityNewIntent");
handleNewIntent((NewIntentData)msg.obj);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case RECEIVER:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastReceiveComp");
handleReceiver((ReceiverData)msg.obj);
maybeSnapshot();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case CREATE_SERVICE:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, ("serviceCreate: " + String.valueOf(msg.obj)));
handleCreateService((CreateServiceData)msg.obj);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case BIND_SERVICE:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceBind");
handleBindService((BindServiceData)msg.obj);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case UNBIND_SERVICE:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceUnbind");
handleUnbindService((BindServiceData)msg.obj);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case SERVICE_ARGS:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, ("serviceStart: " + String.valueOf(msg.obj)));
handleServiceArgs((ServiceArgsData)msg.obj);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case STOP_SERVICE:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceStop");
handleStopService((IBinder)msg.obj);
maybeSnapshot();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case CONFIGURATION_CHANGED:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "configChanged");
mCurDefaultDisplayDpi = ((Configuration)msg.obj).densityDpi;
handleConfigurationChanged((Configuration)msg.obj, null);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case CLEAN_UP_CONTEXT:
ContextCleanupInfo cci = (ContextCleanupInfo)msg.obj;
cci.context.performFinalCleanup(cci.who, cci.what);
break;
case GC_WHEN_IDLE:
scheduleGcIdler();
break;
case DUMP_SERVICE:
handleDumpService((DumpComponentInfo)msg.obj);
break;
case LOW_MEMORY:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "lowMemory");
handleLowMemory();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case ACTIVITY_CONFIGURATION_CHANGED:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityConfigChanged");
handleActivityConfigurationChanged((ActivityConfigChangeData) msg.obj,
msg.arg1 == 1 ? REPORT_TO_ACTIVITY : !REPORT_TO_ACTIVITY);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case PROFILER_CONTROL:
handleProfilerControl(msg.arg1 != 0, (ProfilerInfo)msg.obj, msg.arg2);
break;
case CREATE_BACKUP_AGENT:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backupCreateAgent");
handleCreateBackupAgent((CreateBackupAgentData)msg.obj);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case DESTROY_BACKUP_AGENT:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backupDestroyAgent");
handleDestroyBackupAgent((CreateBackupAgentData)msg.obj);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case SUICIDE:
Process.killProcess(Process.myPid());
break;
case REMOVE_PROVIDER:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "providerRemove");
completeRemoveProvider((ProviderRefCount)msg.obj);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case ENABLE_JIT:
ensureJitEnabled();
break;
case DISPATCH_PACKAGE_BROADCAST:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastPackage");
handleDispatchPackageBroadcast(msg.arg1, (String[])msg.obj);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case SCHEDULE_CRASH:
throw new RemoteServiceException((String)msg.obj);
case DUMP_HEAP:
handleDumpHeap(msg.arg1 != 0, (DumpHeapData)msg.obj);
break;
case DUMP_ACTIVITY:
handleDumpActivity((DumpComponentInfo)msg.obj);
break;
case DUMP_PROVIDER:
handleDumpProvider((DumpComponentInfo)msg.obj);
break;
case SLEEPING:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "sleeping");
handleSleeping((IBinder)msg.obj, msg.arg1 != 0);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case SET_CORE_SETTINGS:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "setCoreSettings");
handleSetCoreSettings((Bundle) msg.obj);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case UPDATE_PACKAGE_COMPATIBILITY_INFO:
handleUpdatePackageCompatibilityInfo((UpdateCompatibilityData)msg.obj);
break;
case TRIM_MEMORY:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "trimMemory");
handleTrimMemory(msg.arg1);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case UNSTABLE_PROVIDER_DIED:
handleUnstableProviderDied((IBinder)msg.obj, false);
break;
case REQUEST_ASSIST_CONTEXT_EXTRAS:
handleRequestAssistContextExtras((RequestAssistContextExtras)msg.obj);
break;
case TRANSLUCENT_CONVERSION_COMPLETE:
handleTranslucentConversionComplete((IBinder)msg.obj, msg.arg1 == 1);
break;
case INSTALL_PROVIDER:
handleInstallProvider((ProviderInfo) msg.obj);
break;
case ON_NEW_ACTIVITY_OPTIONS:
Pair<IBinder, ActivityOptions> pair = (Pair<IBinder, ActivityOptions>) msg.obj;
onNewActivityOptions(pair.first, pair.second);
break;
case CANCEL_VISIBLE_BEHIND:
handleCancelVisibleBehind((IBinder) msg.obj);
break;
case BACKGROUND_VISIBLE_BEHIND_CHANGED:
handleOnBackgroundVisibleBehindChanged((IBinder) msg.obj, msg.arg1 > 0);
break;
case ENTER_ANIMATION_COMPLETE:
handleEnterAnimationComplete((IBinder) msg.obj);
break;
case START_BINDER_TRACKING:
handleStartBinderTracking();
break;
case STOP_BINDER_TRACKING_AND_DUMP:
handleStopBinderTrackingAndDump((ParcelFileDescriptor) msg.obj);
break;
case MULTI_WINDOW_MODE_CHANGED:
handleMultiWindowModeChanged((IBinder) msg.obj, msg.arg1 == 1);
break;
case PICTURE_IN_PICTURE_MODE_CHANGED:
handlePictureInPictureModeChanged((IBinder) msg.obj, msg.arg1 == 1);
break;
case LOCAL_VOICE_INTERACTION_STARTED:
handleLocalVoiceInteractionStarted((IBinder) ((SomeArgs) msg.obj).arg1,
(IVoiceInteractor) ((SomeArgs) msg.obj).arg2);
break;
}
Object obj = msg.obj;
if (obj instanceof SomeArgs) {
((SomeArgs) obj).recycle();
}
if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what));
}
}
從系統的Handler中,在handleMessage我們可以看到很多關于四大組件的生命周期操作,比如創建、銷毀、切換、跨進程通信,也包括了整個Application進程的銷毀等等。
比如說我們有一個應用程序A通過Binder去跨進程啟動另外一個應用程序B的Service(或者同一個應用程序中不同進程的Service):
如圖:
最后是AMS接收到消息以后,發送消息到MessageQueue里面,最后由系統的Handler處理啟動Service的操作:
case CREATE_SERVICE:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, ("serviceCreate: " + String.valueOf(msg.obj)));
handleCreateService((CreateServiceData)msg.obj);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
在handleCreateService里通過反射的方式去newInstance(),并且回調了Service的onCreate方法:
private void handleCreateService(CreateServiceData data) {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
unscheduleGcIdler();
LoadedApk packageInfo = getPackageInfoNoCheck(
data.info.applicationInfo, data.compatInfo);
Service service = null;
try {
//通過反射的方式去創建Service
java.lang.ClassLoader cl = packageInfo.getClassLoader();
service = (Service) cl.loadClass(data.info.name).newInstance();
} catch (Exception e) {
if (!mInstrumentation.onException(service, e) {
throw new RuntimeE)xception(
"Unable to instantiate service " + data.info.name
+ ": " + e.toString(), e);
}
}
try {
if (localLOGV) Slog.v(TAG, "Creating service " + data.info.name);
ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
context.setOuterContext(service);
Application app = packageInfo.makeApplication(false, mInstrumentation);
service.attach(context, this, data.info.name, data.token, app,
ActivityManagerNative.getDefault());
//回調了Service的onCreate方法
service.onCreate();
mServices.put(data.token, service);
try {
ActivityManagerNative.getDefault().serviceDoneExecuting(
data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
} catch (Exception e) {
if (!mInstrumentation.onException(service, e)) {
throw new RuntimeException(
"Unable to create service " + data.info.name
+ ": " + e.toString(), e);
}
}
}
又例如我們可以通過發SUICIDE消息可以自殺,這樣來退出應用程序。
case SUICIDE:
Process.killProcess(Process.myPid());
break;
應用程序的退出過程
實際上我們要退出應用程序的話,就是讓主線程結束,換句話說就是要讓Looper的循環結束。這里是直接結束Looper循環,因此我們四大組件的生命周期方法可能就不會執行了,因為四大組件的生命周期方法就是通過Handler去處理的,Looper循環都沒有了,四大組件還玩毛線!因此我們平常寫程序的時候就要注意了,onDestroy方法是不一定能夠回調的。
case EXIT_APPLICATION:
if (mInitialApplication != null) {
mInitialApplication.onTerminate();
}
//退出Looper的循環
Looper.myLooper().quit();
break;
這里實際上是調用了MessageQueue的quit,清空所有Message。
public void quit() {
mQueue.quit(false);
}
tips:看源碼一定不要慌,也不要一行一行看,要抓住核心的思路去看即可。
消息機制的分析
消息對象Message的分析
提到消息機制,在MessageQueue里面存在的就是我們的Message對象:
public final class Message implements Parcelable {
public int what;
public int arg1;
public int arg2;
public Object obj;
long when;
Bundle data;
Handler target;
Runnable callback;Message next;
private static final Object sPoolSync = new Object();
private static Message sPool;
private static int sPoolSize = 0;
private static final int MAX_POOL_SIZE = 50;
private static boolean gCheckRecycle = true;
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
public void recycle() {
if (isInUse()) {
if (gCheckRecycle) {
throw new IllegalStateException("This message cannot be recycled because it "
+ "is still in use.");
}
return;
}
recycleUnchecked();
}
void recycleUnchecked() {
// Mark the message as in use while it remains in the recycled object pool.
// Clear out all other details.
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = -1;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
}
首先我們可以看到Message對象是實現了Parcelable接口的,因為Message消息可能需要跨進程通信,這時候就需要進程序列化以及反序列化操作了。
Message里面有一些我們常見的參數,arg1 arg2 obj callback when等等。這里要提一下的就是這個target對象,這個對象就是發送這個消息的Handler對象,最終這條消息也是通過這個Handler去處理掉的。
Message Pool消息池的概念——重復利用Message
Message里面中一個非常重要的概念,就是消息池Pool:
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
我們通過obtain方法取出一條消息的時候,如果發現當前的消息池不為空,那就直接重復利用Message(已經被創建過和handle過的);如果為空就重新new 一個消息。這就是一種享元設計模式的概念。例如在游戲里面,發子彈,如果一個子彈是一個對象,一按下按鍵就發很多個子彈,那么這時候就需要利用享元模式去循環利用了。
這個消息池是通過鏈表的實現的,通過上面的代碼可以知道,sPool永遠指向這個消息池的頭,取消息的時候,先拿到當前的頭sPool,然后使得sPool指向下一個結點,最后返回剛剛取出來的結點,如下圖所示:
上面我們知道了消息可以直接創建,也可以通過obtain方法循環利用。所以我們平常編程的時候就要養成好的習慣,循環利用。
消息的回收機制
有消息的創建,必然有回收利用,下面兩個是Message的回收相關的核心方法:
public void recycle() {
if (isInUse()) {
if (gCheckRecycle) {
throw new IllegalStateException("This message cannot be recycled because it "
+ "is still in use.");
}
return;
}
recycleUnchecked();
}
void recycleUnchecked() {
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = -1;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
recycleUnchecked中拿到消息池,清空當前的消息,next指向當前的頭指針,頭指針指向當前的Message對象,也就是在消息池頭部插入當前的消息。
關于消息的回收還有一點需要注意的就是,我們平時寫Handler的時候不需要我們手動回收,因為谷歌的工程師已經有考慮到這方面的問題了。消息是在Handler分發處理之后就會被自動回收的:
我們回到Looper的loop方法里面:
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
//處理消息
try {
msg.target.dispatchMessage(msg);
} finally {
}
}
msg.recycleUnchecked();//回收消息
}
}
msg.target.dispatchMessage(msg)就是處理消息,緊接著在loop方法的最后調用了msg.recycleUnchecked()這就是回收了Message。
消息的循環過程分析
下面我們繼續分析這個死循環:
1、首先拿到Looper對象(me),如果當前的線程沒有Looper,那么就會拋出異常,這就是為什么在子線程里面創建Handler如果不手動創建和啟動Looper會報錯的原因。
2、然后拿到Looper的成員變量MessageQueue,在MessageQueue里面不斷地去取消息,關于MessageQueue的next方法如下:
這里可以看到消息的取出用到了一些native方法,這樣做是為了獲得更高的效率,消息的去取出并不是直接就從隊列的頭部取出的,而是根據了消息的when時間參數有關的,因為我們可以發送延時消息、也可以發送一個指定時間點的消息。因此這個函數有點復雜,我們點到為止即可。
Message next() {
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// Try to retrieve the next message. Return if found.
//拿到當前的時間戳
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
//判斷頭指針的Target(Handler是否為空(因為頭指針只是一個指針的作用))
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
//遍歷下一條Message
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
//還沒有到執行的時間
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
//到了執行時間,直接返回
mBlocked = false;
if (prevMsg != null) {
//拿出消息,斷開鏈表
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
// If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}
3、繼續分析loop方法:如果已經沒有消息了,那么就可以退出循環,那么整個應用程序就退出了。什么情況下會發生呢?還記得我們分析應用退出嗎?
在系統Handler收到EXIT_APPLICATION消息的時候,就會調用Looper的quit方法:
case EXIT_APPLICATION:
if (mInitialApplication != null) {
mInitialApplication.onTerminate();
}
Looper.myLooper().quit();
break;
Looper的quit方法如下,實際上就是調用了消息隊列的quit方法:
public void quit() {
mQueue.quit(false);
}
而消息隊列的quit方法實際上就是執行了消息的清空操作,然后在Looper循環里面如果取出消息為空的時候,程序就退出了:
void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
if (mQuitting) {
return;
}
//置位正在退出的標志
mQuitting = true;
//清空所有消息
if (safe) {
//安全的(系統的),未來未處理的消息都移除
removeAllFutureMessagesLocked();
} else {
//如果是不安全的,例如我們自己定義的消息,就一次性全部移除掉
removeAllMessagesLocked();
}
// We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr);
}
}
removeAllFutureMessagesLocked方法如下:
private void removeAllFutureMessagesLocked() {
final long now = SystemClock.uptimeMillis();
Message p = mMessages;
if (p != null) {
if (p.when > now) {
//如果所有消息都處理完了,就一次性把全部消息移除掉
removeAllMessagesLocked();
} else {
//否則就通過for循環拿到還沒有把還沒有執行的Message,利用do循環
//把這些未處理的消息通過recycleUnchecked方法回收,放回到消息池里面
Message n;
for (;;) {
n = p.next;
if (n == null) {
return;
}
if (n.when > now) {
break;
}
p = n;
}
p.next = null;
do {
p = n;
n = p.next;
p.recycleUnchecked();
} while (n != null);
}
}
}
4、msg.target.dispatchMessage(msg)就是處理消息,這里就會調用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是否為空,這個callback是在Message類里面定義的:
Runnable callback;
這是一個Runnable對象,handleCallback方法里面做的事情就是拿到這個Runnable對象,然后在Handler所創建的線程(例如主線程)執行run方法:
private static void handleCallback(Message message) {
message.callback.run();
}
Handler(Looper)在哪個線程創建的,就在哪個線程回調,沒毛病,哈哈!
這就是我們平常使用post系列的方法:
post、postAtFrontOfQueue、postAtTime、postDelayed
其實最終也是通過Message包裝一個Runnable實現的,我們看其中一個即可:
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
通過post一個Runnable的方式我們可以很簡單地做一個循環,比如無限輪播的廣告條Banner:
Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
}
};
Runnable run = new Runnable() {
@Override
public void run() {
//廣告條切換
mBanner.next();
//兩秒鐘之后繼續下一次的輪播,其中tihs代表自身,也就是Runnable對象
mHandler.postDelayed(this, 2000);
}
};
//在需要的地方開始廣播條的輪播
mHandler.postDelayed(run, 1000);
//在需要的地方停止廣播條的輪播
mHandler.removeCallbacks(run);
當然,我們的Handler自己也可以有一個mCallback對象:
public interface Callback {
public boolean handleMessage(Message msg);
}
final Callback mCallback;
如果自身的Callback不為空的話,就會回調Callback的方法。例如我們創建Handler的時候可以帶上Callback:
Handler mHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
//處理些東西,這種一般用于一些預處理,每次有消息來都需要執行的代碼
//返回值代表是否攔截消息的下面寫的handleMessage,從源碼里面可以看出來
return false;
}
}) {
@Override
public void handleMessage(Message msg) {
}
};
如果自身的Callback執行之后沒有返回true(沒有攔截),那么最后才會回調我們經常需要復寫的handleMessage方法,這個方法的默認實現是空處理:
public void handleMessage(Message msg) {
}
5、最后是回收消息:msg.recycleUnchecked()。所以說:我們平時在處理完handleMessage之后并不需要我們程序員手動去進行回收哈!系統已經幫我們做了這一步操作了。
Message msg = Message.obtain();
//不需要我們程序員去回收,這樣反而會更加耗性能
msg.recycle();
6、通過上面就完成了一次消息的循環。
消息的發送
分析完消息的分發與處理,最后我們來看看消息的發送:
消息的發送有這一系列方法,甚至我們的一系列post方法(封裝了帶Runnable的Message),最終都是調用sendMessageAtTime方法,把消息放到消息隊列里面:
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
MessageQueue的進入隊列的方法如下,核心思想就是時間比較小的(越是需要馬上執行的消息)就越防到越靠近頭指針的位置:
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
消息并不是一直在隊列的尾部添加的,而是可以指定時間,如果是立馬需要執行的消息,就會插到隊列的頭部,就會立馬處理,如此類推。
關于這一點這里我們可以從MessageQueue的next方法知道,next是考慮消息的時間when變量的,下面回顧一下MessageQueue的next方法里面的一些核心代碼:next方法并不是直接從頭部取出來的,而是會去遍歷所有消息,根據時間戳參數等信息來取消息的。
Message next() {
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
if (msg != null) {
if (now < msg.when) {
//如果當前的時間還沒到達消息指定的時間,先計算出下一次需要處理的時間戳,然后保存起來
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
//否則的話直接從消息隊列的頭部拿出一條消息
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
//返回取出來的消息
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
}
}
}
線程與Looper的綁定
線程里面默認情況下是沒有Looper循環器的,因此我們需要調用prepare方法來關聯線程和Looper:
//Looper的prepare方法,并且關聯到主線程
public static void prepareMainLooper() {
//false意思不允許我們程序員退出(面向我們開發者),因為這是在主線程里面
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
//把Looper設置為主線程的Looper
sMainLooper = myLooper();
}
}
//Looper一般的prepare方法
private static void prepare(boolean quitAllowed) {
//一個線程只能綁定一個Looper,否則的話就會拋出如下的異常
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
此處調用了ThreadLocal的set方法,并且new了一個Looper放進去。
Looper的成員變量sThreadLocal
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
在prepare方法中new了一個Looper并且設置到sThreadLocal里面
sThreadLocal.set(new Looper(quitAllowed));
可以看到Looper與線程的關聯是通過ThreadLocal來進行的,如下圖所示:
ThreadLocal是JDK提供的一個解決線程不安全的類,線程不安全問題歸根結底主要涉及到變量的多線程訪問問題,例如變量的臨界問題、值錯誤、并發問題等。這里利用ThreadLocal綁定了Looper以及線程,就可以避免其他線程去訪問當前線程的Looper了。
ThreadLocal通過get以及set方法就可以綁定線程和Looper了,這里只需要傳入Value即可,因為線是可以通過Thread.currentThread()去拿到的:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
為什么可以綁定線程了呢?
map.set(this, value)通過把自身(ThreadLocal以及值(Looper)放到了一個Map里面,如果再放一個的話,就會覆蓋,因為map不允許鍵值對中的鍵是重復的)
因此ThreadLocal綁定了線程以及Looper。
因為這里實際上把變量(這里是指Looper)放到了Thread一個成員變量Map里面,關鍵的代碼如下:
//這是ThreadLocal的getMap方法
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
//這是Thread類中定義的MAP
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal的getMap方法實際上是拿到線程的MAP,底層是通過數組(實際上數據結構是一種散列列表)實現的,具體的實現就點到為止了。
如果android系統主線程Looper可以隨隨便便被其他線程訪問到的話就會很麻煩了,啊哈哈,你懂的。
Handler、Looper是怎么關聯起來的呢?
我們知道,Looper是與線程相關聯的(通過ThreadLocal),而我們平常使用的Handler是這樣的:
Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
}
};
其實Handler在構造的時候,有多個重載方法,根據調用關系鏈,所以最終會調用下面這個構造方法:
public Handler(Callback callback, boolean async) {
mLooper = Looper.myLooper();
//如果當前線程(子線程)沒有Looper,就需要我們程序要去手動prepare以及啟動loop方法了
//子線程里面默認沒有Looper循環器
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
這里只給出了核心的代碼,可以看到我們在構造Handler的時候,是通過Looper的靜態方法myLooper()去拿到一個Looper對象的:
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
看,我們的又出現了ThreadLocal,這里就是通過ThreadLocal的get方法去拿到當前線程的Looper,因此Handler就跟線程綁定在一起了,在一起,在一起,啊哈哈。
一般們是在Activity里面使用Handler的,而Activity的生命周期是在主線程回調的,因此我們一般使用的Handler是跟主線程綁定在一起的。
主線程一直在循環,為什么沒有卡死,還能響應我們的點擊之類的呢?
- 通過子線程去訪問主線程的代碼,有代碼注入、回調機制嘛。
- 切入到消息隊列里面的消息去訪問主線程,例如傳消息,然后回調四大組件的生命周期等等。
- IPC跨進程的方式也可以實現。
雖然主線程一直在執行,但是我們可以通過外部條件、注入的方法來執行自己的代碼,而不是一直死循環。
總結
如圖所示,在主線程ActivityThread中的main方法入口中,先是創建了系統的Handler(H),創建主線程的Looper,將Looper與主線程綁定,調用了Looper的loop方法之后開啟整個應用程序的主循環。Looper里面有一個消息隊列,通過Handler發送消息到消息隊列里面,然后通過Looper不斷去循環取出消息,交給Handler去處理。通過系統的Handler,或者說Android的消息處理機制就確保了整個Android系統有條不紊地運作,這是Android系統里面的一個比較重要的機制。
我們的APP也可以創建自己的Handler,可以是在主線程里面創建,也可以在子線程里面創建,但是需要手動創建子線程的Looper并且手動啟動消息循環。
花了一天的時間,整個Android消息機制源碼分析就到這里結束了,今天的天氣真不錯,但是我選擇了在自己的房間學習Android的消息機制,我永遠相信,付出總會有所收獲的!
擴展閱讀:論Handler的正確使用姿勢
典型錯誤的使用示例:
public class LeakActivity extends AppCompatActivity {
private int a = 10;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_leak);
mHandler.sendEmptyMessageDelayed(0, 5000);
}
//也是匿名內部類,也會引用外部
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 0:
a = 20;
break;
}
}
};
}
分析:這是我們用得最多的用法,Handler隱式地引用了Activity(通過變量a)。Handler的生命周期有可能與Activity的生命周期不一致,比如栗子中的sendEmptyMessageDelayed,在5000毫秒之后才發送消息,但是很有可能這時候Activity被返回了,這樣會造成Handler比Activity還要長壽,這樣會導致Activity發生暫時性的內存泄漏。
姿勢一:
為了解決這個問題,我們可以把Handler改為static的,但是這樣會造成Handler無法訪問Activity的非靜態變量a,但是實際開發中我們
private static Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 0:
//a = 20; //不能訪問得到
break;
}
}
};
姿勢二:
通過把Activity作為Handler成員變量,在Handler構造的時候傳進來即可。這時候我們不能使用匿名內部類了,需要把Handler單獨抽取成一個類,這樣就可以訪問Activity的非靜態變量了。但是我們的問題又回來了,這時候Handler持有了Activity的強引用了,這樣不就是回到我們的原點了嗎?(內存泄漏問題依然沒有解決)
private static class UIHandler extends Handler {
private LeakActivity mActivity;//外部類的強引用
public UIHandler(LeakActivity activity) {
mActivity = activity;
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
mActivity.a = 20;
}
}
姿勢三(最終版本):把Activity通過弱引用來作為成員變量。雖然我們把Activity作為弱引用,但是Activity不一定就是會在GC的時候被回收,因為可能還有其他對象引用了Activity。在處理消息的時候就要注意了,當Activity回收或者正在finish的時候,就不能繼續處理消息了,再說了,Activity都回收了,Handler還玩個屁!
private static class UIHandler extends Handler {
private WeakReference<LeakActivity> mActivityRef;//GC的時候會回收
public UIHandler(LeakActivity activity) {
mActivityRef = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
//當使用弱引用的時候,會回收Activity嗎?
//雖然用的是弱引用,但是并不代表不存在其他的對象沒有引用Activity,因此不一定會被回收
//Activity都回收了,Handler還玩個屁!
LeakActivity activity = mActivityRef.get();
if (activity == null || activity.isFinishing()) {
return;
}
mActivityRef.get().a = 20;
}
}
關于更多的Handler使用,請參考我朋友寫的文章:
說說Handler的一些使用姿勢
如果覺得我的文字對你有所幫助的話,歡迎關注我的公眾號:
我的群歡迎大家進來探討各種技術與非技術的話題,有興趣的朋友們加我私人微信huannan88,我拉你進群交(♂)流(♀)。