首發(fā)地址:微信 Mars Android Sample 源碼分析
注:為了避免看得懵逼(當然可能是我寫得不好~),建議最好先下載代碼,運行完看到效果圖后,再結(jié)合項目代碼看“詳細分析”部分……
零、前言
Mars 是微信官方開源的跨平臺跨業(yè)務的終端基礎組件,具有高質(zhì)量網(wǎng)絡連接模塊(長短連接、智能心跳機制)、高性能日志模塊和網(wǎng)絡監(jiān)測組件等。而整個 Android Sample 是基于 Mars 開發(fā)的一個 demo,包含提供了以下功能:
- 基于TCP長連接的聊天室體驗。
- 數(shù)據(jù)通信成功率、耗時、流量的展示。
- 網(wǎng)絡狀況檢測結(jié)果展示。
先下載 Mars 代碼:https://github.com/Tencent/mars
一、本地運行 Server 端
具體如何運行 Server 端,參照官方wiki:Mars Sample 使用說明
二、修改 Android Sample
下面說下Android 端該如何修改源碼連接到本地服務器
- 1.全局搜索
marsopen.cn,修改替換為localhost - 2.在保證
app/build.gradle下useLocalMarsWrapper = true的情況下,在 wrapper module 下修改com.tencent.mars.sample.wrapper.service. MarsServiceStub.java的 dns 解析的地址為本地主機的 IP 地址 (wiki 上沒寫清楚)
@Override
public String[] onNewDns(String host) {
// No default new dns support
return new String[]{"192.168.24.193"}; //替換為本地主機的 IP 地址
}
三、運行后聊天效果


四、整體概述
由項目結(jié)構(gòu)可知 sample 分為兩部分,app 和 wrapper(當然也可以用依賴的方式來使用),數(shù)據(jù)格式使用的是google開源的 protobuf,它具有高效、數(shù)據(jù)量小的特性(目前版本為 proto3,Android 開發(fā)推薦使用 protobuf-lite 版本,使用方法)
目前 gradle 接入支持兩種方式:mars-core 和 mars-wrapper。只是做個 sample 的話建議可以使用 mars-wrapper, 但是如果在實際 App 中使用 mars,建議使用 mars-core 或本地編譯。
怎么理解這句話?兩種接入有什么不同?
從 sample 中 mars-wrapper(下面簡稱為 wrapper) 的源碼可以看出 wrapper 只是對 mars-core 進行了再次封裝,wrapper 庫本質(zhì)還是依賴 mars-core 庫的,所以微信團隊不建議實際App 中接入 wrapper ( 它只是微信官方提供對 mars-core 使用的一種方式,不過具有很好的參考價值)
- 另外,有 wrapper 的 manifest 可知,IM 服務模塊是運行在獨立的進程的,所以同時會涉及到 AIDL 多進程通信方面技術。
<application>
<service
android:name=".service.MarsServiceNative"
android:process=":marsservice" />
<!--注冊一個網(wǎng)絡切換的廣播接收器(源碼上實現(xiàn)的)-->
<receiver
android:name="com.tencent.mars.BaseEvent$ConnectionReceiver"
android:process=":marsservice" />
</application>
五、詳細分析
1. 先從 ConversationActivity 獲取初始的會話列表的請求開始
private NanoMarsTaskWrapper<Main.ConversationListRequest, Main.ConversationListResponse> taskGetConvList = null;
private void updateConversationTopics() {
if (taskGetConvList != null) {
MarsServiceProxy.cancel(taskGetConvList);
}
textView.setVisibility(View.INVISIBLE);
progressBar.setVisibility(View.VISIBLE);
swipeRefreshLayout.setRefreshing(true);
taskGetConvList = new NanoMarsTaskWrapper<Main.ConversationListRequest, Main.ConversationListResponse>(
new Main.ConversationListRequest(),
new Main.ConversationListResponse()
) {
private List<Conversation> dataList = new LinkedList<>();
@Override
public void onPreEncode(Main.ConversationListRequest req) {
req.type = conversationFilterType;
req.accessToken = ""; // TODO:
}
@Override
public void onPostDecode(Main.ConversationListResponse response) {
}
@Override
public void onTaskEnd(int errType, int errCode) {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (response != null) {
for (Main.Conversation conv : response.list) {
dataList.add(new Conversation(conv.name, conv.topic, conv.notice));
}
}
if (!dataList.isEmpty()) {
progressBar.setVisibility(View.INVISIBLE);
conversationListAdapter.list.clear();
conversationListAdapter.list.addAll(dataList);
conversationListAdapter.notifyDataSetChanged();
swipeRefreshLayout.setRefreshing(false);
} else {
Log.i(TAG, "getconvlist: empty response list");
progressBar.setVisibility(View.INVISIBLE);
textView.setVisibility(View.VISIBLE);
}
}
});
}
};
MarsServiceProxy.send(taskGetConvList.setHttpRequest(CONVERSATION_HOST, "/mars/getconvlist"));
}
執(zhí)行步驟:
- 創(chuàng)建一個 NanoMarsTaskWrapper 對象,里面主要包含 onPreEncode,onPostDecode 和onTaskEnd 等方法,分別是編碼傳輸前,接收數(shù)據(jù)解碼后和任務結(jié)束后的回調(diào);
- 設置 NanoMarsTaskWrapper 的 http 地址
- 通過 MarsServiceProxy.send 方法執(zhí)行發(fā)送請求;
初步了解執(zhí)行步驟后,再詳細了解 MarServiceProxy 和 NanoMarTaskWrapper 的實現(xiàn),它們?yōu)槭裁磿羞@樣的功能。
2. NanoMarTaskWrapper
顧名思義,NanoMarTaskWrapper 是一個任務的包裝器
public abstract class NanoMarsTaskWrapper<T extends MessageNano, R extends MessageNano> extends AbstractTaskWrapper {
private static final String TAG = "Mars.Sample.NanoMarsTaskWrapper";
protected T request;
protected R response;
public NanoMarsTaskWrapper(T req, R resp) {
super();
this.request = req;
this.response = resp;
}
@Override
public byte[] req2buf() {
try {
onPreEncode(request);
final byte[] flatArray = new byte[request.getSerializedSize()];
final CodedOutputByteBufferNano output = CodedOutputByteBufferNano.newInstance(flatArray);
request.writeTo(output);
Log.d(TAG, "encoded request to buffer, [%s]", MemoryDump.dumpHex(flatArray));
return flatArray;
} catch (Exception e) {
e.printStackTrace();
}
return new byte[0];
}
@Override
public int buf2resp(byte[] buf) {
try {
Log.d(TAG, "decode response buffer, [%s]", MemoryDump.dumpHex(buf));
response = MessageNano.mergeFrom(response, buf);
onPostDecode(response);
return StnLogic.RESP_FAIL_HANDLE_NORMAL;
} catch (Exception e) {
Log.e(TAG, "%s", e);
}
return StnLogic.RESP_FAIL_HANDLE_TASK_END;
}
public abstract void onPreEncode(T request);
public abstract void onPostDecode(R response);
}
- 1)繼承自AbstractTaskWrapper
- 2)創(chuàng)建時會同時創(chuàng)建繼承自 MessageNano(protobuf 的消息數(shù)據(jù)類) 的 request 和 response 數(shù)據(jù)模型;
- 3)處理編解碼的兩個方法,req2buf 和 buf2resp,也就是字節(jié)流數(shù)組和 protobuf 對象的轉(zhuǎn)換,涉及到
MessageNano.writeTo和MessageNano.mergeFrom的使用,這方面的具體實現(xiàn)不需要我們?nèi)チ私?,Google 的 protobuf 已經(jīng)幫我們生成代碼完成好了
再看看 AbstractTaskWrapper 是怎樣實現(xiàn)的
public abstract class AbstractTaskWrapper extends MarsTaskWrapper.Stub {
private Bundle properties = new Bundle();
public AbstractTaskWrapper() {
// Reflects task properties 通過自身(繼承該類的類)標注的注解,用反射獲取任務的配置信息
final TaskProperty taskProperty = this.getClass().getAnnotation(TaskProperty.class);
if (taskProperty != null) {
setHttpRequest(taskProperty.host(), taskProperty.path());
setShortChannelSupport(taskProperty.shortChannelSupport());
setLongChannelSupport(taskProperty.longChannelSupport());
setCmdID(taskProperty.cmdID());
}
}
@Override
public Bundle getProperties() {
return properties;
}
@Override
public abstract void onTaskEnd(int errType, int errCode);
public AbstractTaskWrapper setHttpRequest(String host, String path) {
properties.putString(MarsTaskProperty.OPTIONS_HOST, ("".equals(host) ? null : host));
properties.putString(MarsTaskProperty.OPTIONS_CGI_PATH, path);
return this;
}
public AbstractTaskWrapper setShortChannelSupport(boolean support) {
properties.putBoolean(MarsTaskProperty.OPTIONS_CHANNEL_SHORT_SUPPORT, support);
return this;
}
public AbstractTaskWrapper setLongChannelSupport(boolean support) {
properties.putBoolean(MarsTaskProperty.OPTIONS_CHANNEL_LONG_SUPPORT, support);
return this;
}
public AbstractTaskWrapper setCmdID(int cmdID) {
properties.putInt(MarsTaskProperty.OPTIONS_CMD_ID, cmdID);
return this;
}
@Override
public String toString() {
return "AbsMarsTask: " + BundleFormat.toString(properties);
}
}
抽象的 AbstractTaskWrapper 繼承自 MarTaskWrapper.Stub (MarsTaskWrapper.aidl 生成的代碼),它主要通過注解類 TaskProperty 設置了任務的配置信息 properties(如主機名、url路徑、是否支持長短鏈接和指令 id),
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Inherited
public @interface TaskProperty {
String host() default "";
String path();
boolean shortChannelSupport() default true;
boolean longChannelSupport() default false;
int cmdID() default -1;
}
再回到剛開始的發(fā)送消息的 MarsServiceProxy 類
3. MarsServiceProxy
public class MarsServiceProxy implements ServiceConnection {
//……
private MarsService service = null;
public static MarsServiceProxy inst;
private LinkedBlockingQueue<MarsTaskWrapper> queue = new LinkedBlockingQueue<>();
private MarsServiceProxy() {
worker = new Worker();
worker.start();
}
public static void init(Context context, Looper looper, String packageName) {
if (inst != null) {
// TODO: Already initialized
return;
}
gContext = context.getApplicationContext();
gPackageName = (packageName == null ? context.getPackageName() : packageName);
gClassName = SERVICE_DEFAULT_CLASSNAME;
inst = new MarsServiceProxy();
}
@Override
public void onServiceConnected(ComponentName componentName, IBinder binder) {
Log.d(TAG, "remote mars service connected");
try {
service = MarsService.Stub.asInterface(binder);
service.registerPushMessageFilter(filter);
service.setAccountInfo(accountInfo.uin, accountInfo.userName);
} catch (Exception e) {
service = null;
}
}
//……
private static class Worker extends Thread {
@Override
public void run() {
while (true) {
inst.continueProcessTaskWrappers();
try {
Thread.sleep(50);
} catch (InterruptedException e) {
//
}
}
}
}
}
MarsServiceProxy 只是一個單例的 ServiceConnection,不過它應該具有代理某種 Service的功能(ServiceConnection會和 Service綁定),由定義的變量和onServiceConnected中的調(diào)用可知,它關聯(lián)了 MarsService,但是 MarsService 只是一個 AIDL ,不是真正的服務,這個稍后再說
MarsServiceProxy 是在SampleApplication 的 onCreate調(diào)用的時候初始化的
// NOTE: MarsServiceProxy is for client/caller
// Initialize MarsServiceProxy for local client, can be moved to other place
MarsServiceProxy.init(this, getMainLooper(), null);
當調(diào)用send 方法發(fā)送任務時,
public static void send(MarsTaskWrapper marsTaskWrapper) {
inst.queue.offer(marsTaskWrapper);
}
queue 是一個 LinkedBlockingQueue<MarsTaskWrapper>線程安全的隊列,緩存了所有的MarsTaskWrapper 任務。
那將任務放進隊列后,什么時候執(zhí)行呢?由上面代碼可以看到 MarsServiceProxy 創(chuàng)建時會啟動一個 Worker 線程,線程會每隔50ms 執(zhí)行調(diào)用inst.continueProcessTaskWrappers();方法
private void continueProcessTaskWrappers() {
try {
if (service == null) { //服務為 null時,重新開啟
Log.d(TAG, "try to bind remote mars service, packageName: %s, className: %s", gPackageName, gClassName);
Intent i = new Intent().setClassName(gPackageName, gClassName);
gContext.startService(i);
if (!gContext.bindService(i, inst, Service.BIND_AUTO_CREATE)) { //注意,此處有個 Service 綁定的判斷
Log.e(TAG, "remote mars service bind failed");
}
// Waiting for service connected
return;
}
MarsTaskWrapper taskWrapper = queue.take(); //取出隊列中的 MarsTask
if (taskWrapper == null) { //任務為空,跳出方法的執(zhí)行
// Stop, no more task
return;
}
try {
Log.d(TAG, "sending task = %s", taskWrapper);
final String cgiPath = taskWrapper.getProperties().getString(MarsTaskProperty.OPTIONS_CGI_PATH);
final Integer globalCmdID = GLOBAL_CMD_ID_MAP.get(cgiPath);
if (globalCmdID != null) {
taskWrapper.getProperties().putInt(MarsTaskProperty.OPTIONS_CMD_ID, globalCmdID);
Log.i(TAG, "overwrite cmdID with global cmdID Map: %s -> %d", cgiPath, globalCmdID);
}
final int taskID = service.send(taskWrapper, taskWrapper.getProperties());
// NOTE: Save taskID to taskWrapper here
taskWrapper.getProperties().putInt(MarsTaskProperty.OPTIONS_TASK_ID, taskID);
} catch (Exception e) { // RemoteExceptionHandler
e.printStackTrace();
}
} catch (Exception e) {
//
}
}
這個方法里面會從任務隊列取出一個任務,然后最終會通過 MarsService.send 方法發(fā)送消息任務,并保存任務 id
另外這個方法開始還有啟動服務的判斷,由gClassName = SERVICE_DEFAULT_CLASSNAME = "com.tencent.mars.sample.wrapper.service.MarsServiceNative"知道,啟動的服務是MarsServiceNative,那 MarsServiceNative 是怎樣和 MarsServiceProxy 關聯(lián)起來的呢?
再看 MarsServiceProxy 的 onServiceConnected 方法,MarsService 的初始化是通過MarsService.Stub.asInterface(binder)關聯(lián)了 MarsServiceProxy 的 IBinder
@Override
public void onServiceConnected(ComponentName componentName, IBinder binder) {
Log.d(TAG, "remote mars service connected");
try {
service = MarsService.Stub.asInterface(binder);
service.registerPushMessageFilter(filter);
service.setAccountInfo(accountInfo.uin, accountInfo.userName);
} catch (Exception e) {
service = null;
}
}
那哪個類實現(xiàn)了 MarsService 的方法呢?查看 MarsServiceNative 源碼就能明白了,它同時實現(xiàn) MarsService,間接和 MarsServiceProxy 形成強關聯(lián)
public class MarsServiceNative extends Service implements MarsService {
private static final String TAG = "Mars.Sample.MarsServiceNative";
private MarsServiceStub stub;
//mars服務配置工廠
private static MarsServiceProfileFactory gFactory = new MarsServiceProfileFactory() {
@Override
public MarsServiceProfile createMarsServiceProfile() {
return new DebugMarsServiceProfile();
}
};
//……
@Override
public IBinder asBinder() {
return stub;
}
/**
* 創(chuàng)建運行 Mars 的服務時就初始化 STN (建議在程序啟動時或者使用網(wǎng)絡之前調(diào)用)
*/
@Override
public void onCreate() {
super.onCreate();
final MarsServiceProfile profile = gFactory.createMarsServiceProfile();
stub = new MarsServiceStub(this, profile);
// set callback
AppLogic.setCallBack(stub);
StnLogic.setCallBack(stub);
SdtLogic.setCallBack(stub);
// Initialize the Mars PlatformComm
Mars.init(getApplicationContext(), new Handler(Looper.getMainLooper()));
// Initialize the Mars
StnLogic.setLonglinkSvrAddr(profile.longLinkHost(), profile.longLinkPorts());
StnLogic.setShortlinkSvrAddr(profile.shortLinkPort());
StnLogic.setClientVersion(profile.productID());
Mars.onCreate(true);
StnLogic.makesureLongLinkConnected();
//
Log.d(TAG, "mars service native created");
}
@Override
public IBinder onBind(Intent intent) {
return stub;
}
}
MarsServiceNative 是使用 mars-core 的關鍵類,同時它也是一個 Service 類,運行在獨立進程中,主要負責了以下功能:
- 創(chuàng)建配置信息類 MarsServiceProfile,并在 StnLogic 設置相關信息;
- 實例化一個 MarsServiceStub;
- 設置了 AppLogic, StnLogic, SdtLogic 的回調(diào);
- 初始化 Mars;
- 確定 StnLogic 的長連接
StnLogic.makesureLongLinkConnected();
既然 MarsServiceNative 設置了 AppLogic, StnLogic, SdtLogic 的回調(diào),那再看看 MarsServiceStub 是如何實現(xiàn)它們接口的
4. MarsServiceStub
先看發(fā)送消息的 send 方法
public class MarsServiceStub extends MarsService.Stub implements StnLogic.ICallBack, SdtLogic.ICallBack, AppLogic.ICallBack {
private static final String TAG = "Mars.Sample.MarsServiceStub";
private final MarsServiceProfile profile;
private AppLogic.AccountInfo accountInfo = new AppLogic.AccountInfo();
//……
private ConcurrentLinkedQueue<MarsPushMessageFilter> filters = new ConcurrentLinkedQueue<>();
private int clientVersion = 200;
public MarsServiceStub(Context context, MarsServiceProfile profile) {
this.context = context;
this.profile = profile;
}
private static final int FIXED_HEADER_SKIP = 4 + 2 + 2 + 4 + 4;
private static Map<Integer, MarsTaskWrapper> TASK_ID_TO_WRAPPER = new ConcurrentHashMap<>();
@Override
public int send(final MarsTaskWrapper taskWrapper, Bundle taskProperties) throws RemoteException {
final StnLogic.Task _task = new StnLogic.Task(StnLogic.Task.EShort, 0, "", null); //初始化為
// Set host & cgi path
final String host = taskProperties.getString(MarsTaskProperty.OPTIONS_HOST);
final String cgiPath = taskProperties.getString(MarsTaskProperty.OPTIONS_CGI_PATH);
_task.shortLinkHostList = new ArrayList<>();
_task.shortLinkHostList.add(host);
_task.cgi = cgiPath;
final boolean shortSupport = taskProperties.getBoolean(MarsTaskProperty.OPTIONS_CHANNEL_SHORT_SUPPORT, true);
final boolean longSupport = taskProperties.getBoolean(MarsTaskProperty.OPTIONS_CHANNEL_LONG_SUPPORT, false);
if (shortSupport && longSupport) {
_task.channelSelect = StnLogic.Task.EBoth;
} else if (shortSupport) {
_task.channelSelect = StnLogic.Task.EShort;
} else if (longSupport) {
_task.channelSelect = StnLogic.Task.ELong;
} else {
Log.e(TAG, "invalid channel strategy");
throw new RemoteException("Invalid Channel Strategy");
}
// Set cmdID if necessary
int cmdID = taskProperties.getInt(MarsTaskProperty.OPTIONS_CMD_ID, -1);
if (cmdID != -1) {
_task.cmdID = cmdID;
}
TASK_ID_TO_WRAPPER.put(_task.taskID, taskWrapper);
// Send 發(fā)送任務
Log.i(TAG, "now start task with id %d", _task.taskID);
StnLogic.startTask(_task);
if (StnLogic.hasTask(_task.taskID)) {
Log.i(TAG, "stn task started with id %d", _task.taskID);
} else {
Log.e(TAG, "stn task start failed with id %d", _task.taskID);
}
return _task.taskID;
}
//……
}
- 1)創(chuàng)建一個 StnLogic.Task,并通過 Bundle 傳過來的數(shù)據(jù)(主機名、路徑、長短連接和 cmdId),設置 task;
- 2)保存 taskID 和 MarsTaskWrapper 的映射關系;
- 3)調(diào)用 StnLogic.startTask(_task) 啟動任務執(zhí)行,最后返回 taskID;
具體的邏輯實現(xiàn)還是在 mars-core 和底層的 C++ 源碼中,這個以后研究到底層源碼再說。
在 mars-core 大概看下 StnLogic.ICallBack 接口有哪些方法:
/**
* Created by caoshaokun on 16/2/1.
*
* APP使用信令通道必須實現(xiàn)該接口
* 接口用于信令通道處理完后回調(diào)上層
*/
public interface ICallBack {
/**
* SDK要求上層做認證操作(可能新發(fā)起一個AUTH CGI)
* @return
*/
boolean makesureAuthed();
/**
* SDK要求上層做域名解析.上層可以實現(xiàn)傳統(tǒng)DNS解析,或者自己實現(xiàn)的域名/IP映射
* @param host
* @return
*/
String[] onNewDns(final String host);
/**
* 收到SVR PUSH下來的消息
* @param cmdid
* @param data
*/
void onPush(final int cmdid, final byte[] data);
/**
* SDK要求上層對TASK組包
* @param taskID 任務標識
* @param userContext
* @param reqBuffer 組包的BUFFER
* @param errCode 組包的錯誤碼
* @return
*/
boolean req2Buf(final int taskID, Object userContext, ByteArrayOutputStream reqBuffer, int[] errCode, int channelSelect);
/**
* SDK要求上層對TASK解包
* @param taskID 任務標識
* @param userContext
* @param respBuffer 要解包的BUFFER
* @param errCode 解包的錯誤碼
* @return int
*/
int buf2Resp(final int taskID, Object userContext, final byte[] respBuffer, int[] errCode, int channelSelect);
/**
* 任務結(jié)束回調(diào)
* @param taskID 任務標識
* @param userContext
* @param errType 錯誤類型
* @param errCode 錯誤碼
* @return
*/
int onTaskEnd(final int taskID, Object userContext, final int errType, final int errCode);
/**
* 流量統(tǒng)計
* @param send
* @param recv
*/
void trafficData(final int send, final int recv);
/**
* 連接狀態(tài)通知
* @param status 綜合狀態(tài),即長連+短連的狀態(tài)
* @param longlinkstatus 僅長連的狀態(tài)
*/
void reportConnectInfo(int status, int longlinkstatus);
/**
* SDK要求上層生成長鏈接數(shù)據(jù)校驗包,在長鏈接連接上之后使用,用于驗證SVR身份
* @param identifyReqBuf 校驗包數(shù)據(jù)內(nèi)容
* @param hashCodeBuffer 校驗包的HASH
* @param reqRespCmdID 數(shù)據(jù)校驗的CMD ID
* @return ECHECK_NOW(需要校驗), ECHECK_NEVER(不校驗), ECHECK_NEXT(下一次再詢問)
*/
int getLongLinkIdentifyCheckBuffer(ByteArrayOutputStream identifyReqBuf, ByteArrayOutputStream hashCodeBuffer, int[] reqRespCmdID);
/**
* SDK要求上層解連接校驗回包.
* @param buffer SVR回復的連接校驗包
* @param hashCodeBuffer CLIENT請求的連接校驗包的HASH值
* @return
*/
boolean onLongLinkIdentifyResp(final byte[] buffer, final byte[] hashCodeBuffer);
/**
* 請求做sync
*/
void requestDoSync();
String[] requestNetCheckShortLinkHosts();
/**
* 是否登錄
* @return true 登錄 false 未登錄
*/
boolean isLogoned();
void reportTaskProfile(String taskString);
}
總結(jié)一下:
1.NanoMarsTaskWrapper:涉及編碼前、解碼后和任務結(jié)束后的回調(diào),還有字節(jié)流數(shù)組和 protobuf 對象的轉(zhuǎn)換等;
2.MarsServiceProxy:是一個 ServiceConnection,涉及消息的發(fā)送和取消等,作為一個 API 的功能,本質(zhì)是 MarsServiceNative 代理服務類;
3.MarsServiceNative 是 wrapper 的核心類,里面涉及 Mars 的初始化,設置了 AppLogic, StnLogic 和 SdtLogic 的回調(diào)等;
4.而 MarsServiceStub 實現(xiàn)了AppLogic, StnLogic 和 SdtLogic 的回調(diào)接口,維護了和 mars-core 的調(diào)用等;
其余分析,可以到我 fork 的 mars 的地址GitHub - navyifanr/mars: (添加注釋分析)下載。
參考資料:
GitHub - Tencent/mars: Mars is a cross-platform network component developed by WeChat.
微信開源mars源碼分析1—上層samples分析 - ameise_w - SegmentFault