前言
- 一個app的程序是怎么啟動的迷雪?入口在哪里限书?
- 聽說ActivityManagerServices很屌,Why章咧?
- Activity生命周期到底是誰調(diào)用的倦西?
- Application又是在哪里初始化的?onCreate又是如何被調(diào)用的赁严?
- 面試官常常會問:為什么主線程使用looper.loop不會阻塞?
- 等等..
是不是一直有這樣的疑問扰柠?很懵逼對不對 - - 粉铐,那我們就站在巨人的丁丁上來解決一下這些問題,如果文中出現(xiàn)一些錯誤卤档,還望指正蝙泼,互相學(xué)習(xí)
主要流程
大家都知道 Android
是基于Linux
系統(tǒng)的,而在Linux
中劝枣,所有的進程都是由init
進程直接或者是間接fork
出來的汤踏,當(dāng)我開機的時候init
進程就會fork
出一個Android
的第一個新的進程
Zygote,中文翻譯過來要”受精卵”,一個很有意識的名字舔腾。為什么這么說呢溪胶,當(dāng)我們Zygote
進程跑起來后,Android
為了實現(xiàn)實現(xiàn)資源共用和更快的啟動速度琢唾,通過Zygote
進程直接去fork
出一些子進程载荔,這就是為什么要”受精卵”的原因采桃,也就是我們的app
全部都是基于Zygote
上的 ,沒有Zygote
就沒有我們丘损,當(dāng)Zygote
初始化完成之后普办,首先會fork
它的第一個子進程SystemServer,這個類非常的重要,為什么這么說呢徘钥?因為系統(tǒng)里面重要的服務(wù)都是在這個進程里面開啟的衔蹲,比如ActivityManagerService
、PackageManagerService
呈础、WindowManagerService
等等舆驶,有木有覺得似曾相識
當(dāng)SystemServer
跑起來后,這些重要的服務(wù)也會隨之創(chuàng)建,系統(tǒng)初始化完成之后我們就會進到系統(tǒng)桌面->Launcher而钞,其實Launcher
也是一個app
沙廉,它繼承自Activity
,當(dāng)我們點擊桌面上的app后臼节,系統(tǒng)就會為我們的app創(chuàng)建一個進程撬陵,然后啟動我們App的第一個類ActivityThread,其實說到底我們的app就是一個main
函數(shù),也就是啟動了ActivityThread.main()
。我們重點來看下這個類
App的程序入口
都說主線程更新ui网缝,主線程不能有耗時操作巨税,主線程是在哪里創(chuàng)建的呢?我們來看下ActivityThread.main()粉臊。
public static void main(String[] args) {
....
final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
TrustedCertificateStore.setDefaultUserDirectory(configDir);
Process.setArgV0("<pre-initialized>");
//創(chuàng)建主線程Looper
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
//主線程消息循環(huán)
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
為什么主線程使用Looper.loop()不會卡死界面
首先我們要明白一點草添,主線程也是一個線程,也是有他的生命周期的扼仲,當(dāng)我們new Thread()
后執(zhí)行完里面的代碼也就意味著這個線程的結(jié)束远寸,剛說了主線程也是線程促王,如果我們的代碼一下就執(zhí)行完了,那么我們這個app的功能還能執(zhí)行嗎而晒? 我還沒開始呢蝇狼,你咋就結(jié)束了?這樣多不持久倡怎,Android
為了解決這個問題迅耘,使用的Looper循環(huán),了解Handler
的機制的童鞋,會知道在處理消息的時候使用了Looper.loop()
方法监署,并且在該方法中進入了一個死循環(huán)
同時Looper.loop()
方法是在主線程中調(diào)用的颤专,那么為什么沒有卡死界面呢?
線程的生命周期
首先我們要明白線程它也是有生命周期的钠乏,它的生命周期在于這個線程里面所要執(zhí)行的代碼執(zhí)行完成栖秕,這個線程的使命也就完成了
主線程如何與子線程通信
其次我們要明白我們主線程是如何跟子線程通信(發(fā)消息)的
public class MainActivity extends AppCompatActivity {
Handler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new Thread(new Runnable() {
@Override
public void run() {
//初始化Looper對象 一個線程對應(yīng)一個looper
Looper.prepare();
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Log.i("test", "do somethings");
}
};
//開啟消息循環(huán)
Looper.loop();
}
}).start();
findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mHandler.sendEmptyMessage(1);
}
});
}
}
我們主線程跟子線程就是這樣通信的,可是為什么要 先Looper.prepare(),然后執(zhí)行處理邏輯晓避,最后Looper.loop();
我們先來看看Looper.prepare()
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
sThreadLocal你可以理解成一個hashmap鍵值對簇捍,key就是我們當(dāng)前的Thread線程,value就是new Lopper出來的對象俏拱,sThreadLocal.get()如果不等于空的話 表示直接調(diào)用了prepare已經(jīng)set進去了暑塑,就會拋出一個異常。也就是說一個線程只能對應(yīng)一個looper锅必,人家可是很專一的~事格,在來看看
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
public static void loop() {
//獲取當(dāng)前線程對應(yīng)的Looper對象
final Looper me = myLooper();
if (me == null) {
//沒有調(diào)用Looper.prepare()
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
//死循環(huán),不斷從消息隊列取消息搞隐,有消息就直接處理消息
for (;;) {
//取出下一個消息
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
//當(dāng)前沒有消息驹愚,直接返回
return;
}
// This must be in a local variable, in case a UI event sets the logger
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
final long traceTag = me.mTraceTag;
if (traceTag != 0) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
try {
//能走到這里,表示msg不為空劣纲,有消息要處理
//調(diào)用handler.dispatchMessage()處理消息
msg.target.dispatchMessage(msg);
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}
msg.recycleUnchecked();
}
}
代碼上注釋也寫的比較清楚了逢捺,我們剛說了線程也是有生命周期的,我們在線程里面創(chuàng)建了looper對象之后味廊,new Handler()然后去處理消息蒸甜,假如我們Looper.loop()沒有這個死循環(huán),這個線程任務(wù)就執(zhí)行完成了余佛,那Handler里面還能收到消息嗎柠新?所以我就需要調(diào)用Looper.loop()一個死循環(huán),不斷去消息隊列去取,然后我們主線程發(fā)了消息后辉巡,Looper取到了消息恨憎,子線程里面的Handler也就自然能夠處理消息這就是這個調(diào)用Looper.loop()的作用
主線程創(chuàng)建Handler的時候為什么不用調(diào)用Looper.prepare(),Loop.loop()?
我們在來看看AcivityThread源碼
ActivityThread.java
public static void main(String[] args) {
...
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();
...
}
看到木有,在我們app啟動的時候,ActivityThread已經(jīng)幫我們做好了Looper消息循環(huán),所以我們在主線程new Handler()的時候就不用創(chuàng)建Looper憔恳,反而你再次調(diào)用Looper.prepare()還會報錯瓤荔,因為主線程已經(jīng)創(chuàng)建了Looper對象了。
為什么主線程不會卡死界面
理解了上面幾個問題我們就比較好理解了钥组,首先输硝,主線程也是線程,只不過這個線程是被系統(tǒng)創(chuàng)建的(就好比我們自己創(chuàng)建了子線程一樣)程梦,其次 Looper是不斷的去消息隊列里面取点把,取到消息就去處理消息,只要處理消息的操作不是耗時操作屿附,就不會引起卡頓,其實Android
的交互都是基于消息機制的分發(fā)郎逃,handler
可以發(fā)送消息线罕,然后loop
里就分發(fā)消息然后就發(fā)給handler
, 然后就執(zhí)行到 H(Handler )
里的對應(yīng)代碼豪娜。所以這些代碼就不會卡死~,也就說循環(huán)消息并不會使起卡死厕妖,而真正會卡死的是我們的處理消息双霍,這也就是我們主線程為什么不能執(zhí)行耗時操作的原因。
舉個栗子
講到這里你可能還不是很能理解烘绽,那我們就來舉一個栗子~
我們?nèi)プ龅罔F或者做火車的時候都要去過安檢呢燥,這個安檢的機器就好比我們的Looper對象班缎,機器內(nèi)部檢測違禁品就好比是Handler探赫,因為我們要依次排隊去放到機器上去檢查型宙,我們一個人的行李物品就比作一個Message,多個人所以就組成了一個MessageQueue, 好,有了這些東西我們就可以腦補整個運行過程了伦吠,首先如果要運行機器首先你的插插頭吧?不然怎么通電->(Looper.prepare),通完點后你得按開關(guān)吧魂拦?(Looper.loop)毛仪,因為運行很早,這個時候還沒有人來安檢芯勘,此時消息隊列(MessageQueue)是空的箱靴,這個時候我過來安檢,因為我是出去玩荷愕,所以帶的東西比較少衡怀,這個時候我把我的包包(Message)放到了安檢機器的韌帶(MessageQueue)上面了,此時MessageQueue消息隊列里面就有我的包包了安疗,機器此時正在運轉(zhuǎn)抛杨,
然后機器自動檢測我的是否有違禁品(Handler.handleMessage),這個時候來了位大叔,帶著很大一包東西(Message)荐类,放到了MessageQueue里怖现,這個安檢機器還在循環(huán)(Looper),當(dāng)循環(huán)到這個大叔的大件后,大叔在另一頭等待了好久發(fā)現(xiàn)怎么都不出來,因為太大屈嗤,檢測需要時間(假設(shè)機器比較low)潘拨,而這個等待的時間就是我們所說的做了耗時操作導(dǎo)致ui卡頓。如果等了很久會導(dǎo)致很多乘客不滿(ANR)饶号。這個比喻可能不是很形象铁追,但是話粗理不粗,這個只是我的個人理解
如果大家還是不能理解的話茫船,可以參考Android事件機制詳細解讀里面有更深入的解讀
ActivityThread.attach()
回歸上一個話題琅束,執(zhí)行main函數(shù)的時候new ActivityThread(),執(zhí)行了它的attach方法,我們來看下attach方法
private void attach(boolean system) {
sCurrentActivityThread = this;
mSystemThread = system;
if (!system) {
...
final IActivityManager mgr =ActivityManagerNative.getDefault();
try {
mgr.attachApplication(mAppThread);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
// Watch for getting close to heap limit.
BinderInternal.addGcWatcher(new Runnable() {
@Override public void run() {
if (!mSomeActivitiesChanged) {
return;
}
Runtime runtime = Runtime.getRuntime();
long dalvikMax = runtime.maxMemory();
long dalvikUsed = runtime.totalMemory() - runtime.freeMemory();
if (dalvikUsed > ((3*dalvikMax)/4)) {
if (DEBUG_MEMORY_TRIM) Slog.d(TAG, "Dalvik max=" + (dalvikMax/1024)
+ " total=" + (runtime.totalMemory()/1024)
+ " used=" + (dalvikUsed/1024));
mSomeActivitiesChanged = false;
try {
mgr.releaseSomeActivities(mAppThread);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}
});
} else {
// Don't set application object here -- if the system crashes,
// we can't display an alert, we just want to die die die.
android.ddm.DdmHandleAppName.setAppName("system_process",
UserHandle.myUserId());
try {
mInstrumentation = new Instrumentation();
ContextImpl context = ContextImpl.createAppContext(
this, getSystemContext().mPackageInfo);
mInitialApplication = context.mPackageInfo.makeApplication(true, null);
mInitialApplication.onCreate();
} catch (Exception e) {
throw new RuntimeException(
"Unable to instantiate Application():" + e.toString(), e);
}
}
}
首先attach方法有一個boolean的變量system,傳過來的是false透硝,聰明的你一看就是這個意思狰闪,是否是系統(tǒng)應(yīng)用 ,當(dāng)然我們不是濒生,然后我們會走到if里面埋泵,里面有一行比較關(guān)鍵的代碼
final IActivityManager mgr = ActivityManagerNative.getDefault();
我點進去看下getDefault()函數(shù)
ActivityManagerNative.java
/**
* Retrieve the system's default/global activity manager.
*/
static public IActivityManager getDefault() {
return gDefault.get();
}
看注釋說返回一個系統(tǒng)全局的ActivityManager,調(diào)用了gDefault.get()
,我們來看下這個gDefault
變量看看是在哪里初始化的
private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
protected IActivityManager create() {
IBinder b = ServiceManager.getService("activity");
if (false) {
Log.v("ActivityManager", "default service binder = " + b);
}
IActivityManager am = asInterface(b);
if (false) {
Log.v("ActivityManager", "default service = " + am);
}
return am;
}
};
ServiceManager.getService("activity")返回一個IBinder 這個Binder對象是誰呢罪治?既然有g(shù)etService丽声,那么肯定有addService,并且這個key是“activity”觉义,我們來看下是在哪個類添加的
ActivityManagerService.java
public void setSystemProcess() {
try {
ServiceManager.addService(Context.ACTIVITY_SERVICE, this, true);
ServiceManager.addService(ProcessStats.SERVICE_NAME, mProcessStats);
ServiceManager.addService("meminfo", new MemBinder(this));
ServiceManager.addService("gfxinfo", new GraphicsBinder(this));
ServiceManager.addService("dbinfo", new DbBinder(this));
if (MONITOR_CPU_USAGE) {
ServiceManager.addService("cpuinfo", new CpuBinder(this));
}
ServiceManager.addService("permission", new PermissionController(this));
ServiceManager.addService("processinfo", new ProcessInfoService(this));
ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(
"android", STOCK_PM_FLAGS | MATCH_SYSTEM_ONLY);
mSystemThread.installSystemApplicationInfo(info, getClass().getClassLoader());
synchronized (this) {
ProcessRecord app = newProcessRecordLocked(info, info.processName, false, 0);
app.persistent = true;
app.pid = MY_PID;
app.maxAdj = ProcessList.SYSTEM_ADJ;
app.makeActive(mSystemThread.getApplicationThread(), mProcessStats);
synchronized (mPidsSelfLocked) {
mPidsSelfLocked.put(app.pid, app);
}
updateLruProcessLocked(app, false, null);
updateOomAdjLocked();
}
} catch (PackageManager.NameNotFoundException e) {
throw new RuntimeException(
"Unable to find android system package", e);
}
}
我們看這一行代碼
ServiceManager.addService(Context.ACTIVITY_SERVICE, this, true);
看下這個Context這個常量:
public static final String ACTIVITY_SERVICE = "activity";
正好是剛才ServiceManager.getService()
傳過去的key, addService()的時候傳過去一個this
雁社,也就是ActivityManagerService自己,那么getService()的時候返回的毫無疑問就是ActivityManagerService
我們在回來看下ActivityThread.attach()方法
ActivityThread.java
final IActivityManager mgr = ActivityManagerNative.getDefault();
try {
mgr.attachApplication(mAppThread);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
我們搞清楚調(diào)用ActivityManagerNative.getDefault()返回了IActivityManager類型的mgr晒骇,也就是ActivityManagerService之后 霉撵,它調(diào)用了attachApplication
,并傳入了一個mAppThread我們首先來看下這個變量是什么類型
final ApplicationThread mAppThread = new ApplicationThread();
是ApplicationThread
,我們在來看看調(diào)用了ActivityManagerService.attachApplication
做了什么事情
ActivityManagerService.java
@Override
public final void attachApplication(IApplicationThread thread) {
synchronized (this) {
int callingPid = Binder.getCallingPid();
final long origId = Binder.clearCallingIdentity();
attachApplicationLocked(thread, callingPid);
Binder.restoreCallingIdentity(origId);
}
}
調(diào)用了attachApplicationLocked()洪囤,繼續(xù)看下做了啥
這個方法比較長徒坡,我就只取關(guān)鍵的部分貼出來
private final boolean attachApplicationLocked(IApplicationThread thread,
int pid) {
....
try {
...
ProfilerInfo profilerInfo = profileFile == null ? null
: new ProfilerInfo(profileFile, profileFd, samplingInterval, profileAutoStop);
thread.bindApplication(processName, appInfo, providers, app.instrumentationClass,
profilerInfo, app.instrumentationArguments, app.instrumentationWatcher,
app.instrumentationUiAutomationConnection, testMode,
mBinderTransactionTrackingEnabled, enableTrackAllocation,
isRestrictedBackupMode || !normalMode, app.persistent,
new Configuration(mConfiguration), app.compat,
getCommonServicesLocked(app.isolated),
mCoreSettingsObserver.getCoreSettingsLocked());
updateLruProcessLocked(app, false, null);
app.lastRequestedGc = app.lastLowMemory = SystemClock.uptimeMillis();
} catch (Exception e) {
// todo: Yikes! What should we do? For now we will try to
// start another process, but that could easily get us in
// an infinite loop of restarting processes...
Slog.wtf(TAG, "Exception thrown during bind of " + app, e);
app.resetPackageList(mProcessStats);
app.unlinkDeathRecipient();
startProcessLocked(app, "bind fail", processName);
return false;
}
....
Application是在什么時候創(chuàng)建的?onCreate()什么時候調(diào)用的瘤缩?
執(zhí)行完判斷和賦值的操作后最后調(diào)用了thread.bindApplication(),剛才看到是ApplicationThread,所以我來看看ApplicationThread.bindApplication
ActivityThread.java
private class ApplicationThread extends ApplicationThreadNative {
private static final String DB_INFO_FORMAT = " %8s %8s %14s %14s %s";
private int mLastProcessState = -1;
...
public final void bindApplication(String processName, ApplicationInfo appInfo,
List<ProviderInfo> providers, ComponentName instrumentationName,
ProfilerInfo profilerInfo, Bundle instrumentationArgs,
IInstrumentationWatcher instrumentationWatcher,
IUiAutomationConnection instrumentationUiConnection, int debugMode,
boolean enableBinderTracking, boolean trackAllocation,
boolean isRestrictedBackupMode, boolean persistent, Configuration config,
CompatibilityInfo compatInfo, Map<String, IBinder> services, Bundle coreSettings) {
if (services != null) {
// Setup the service cache in the ServiceManager
ServiceManager.initServiceCache(services);
}
setCoreSettings(coreSettings);
AppBindData data = new AppBindData();
data.processName = processName;
data.appInfo = appInfo;
data.providers = providers;
data.instrumentationName = instrumentationName;
data.instrumentationArgs = instrumentationArgs;
data.instrumentationWatcher = instrumentationWatcher;
data.instrumentationUiAutomationConnection = instrumentationUiConnection;
data.debugMode = debugMode;
data.enableBinderTracking = enableBinderTracking;
data.trackAllocation = trackAllocation;
data.restrictedBackupMode = isRestrictedBackupMode;
data.persistent = persistent;
data.config = config;
data.compatInfo = compatInfo;
data.initProfilerInfo = profilerInfo;
sendMessage(H.BIND_APPLICATION, data);
...
}
可以看到是ActivityThread
的內(nèi)部類喇完,我去!你在玩我呢剥啤?繞了這么半天又繞回來了锦溪。
客官別急,我們繼續(xù)向下看府怯,執(zhí)行bindApplication的時候發(fā)了一個消息
sendMessage(H.BIND_APPLICATION, data);
這個H就是我們前面說的Handler刻诊,我們來看下handler做了哪些處理
private class H extends Handler {
.....
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
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;
}
看到它調(diào)用了handleBindApplication()
,其主要作用是綁定我的的自定義的application富腊,做一些初始化操作坏逢,我們繼續(xù)看
private void handleBindApplication(AppBindData data) {
...
if (ii != null) {
final ApplicationInfo instrApp = new ApplicationInfo();
ii.copyTo(instrApp);
instrApp.initForUser(UserHandle.myUserId());
final LoadedApk pi = getPackageInfo(instrApp, data.compatInfo,
appContext.getClassLoader(), false, true, false);
final ContextImpl instrContext = ContextImpl.createAppContext(this, pi);
try {
final ClassLoader cl = instrContext.getClassLoader();
mInstrumentation = (Instrumentation)
cl.loadClass(data.instrumentationName.getClassName()).newInstance();
} catch (Exception e) {
throw new RuntimeException(
"Unable to instantiate instrumentation "
+ data.instrumentationName + ": " + e.toString(), e);
}
final ComponentName component = new ComponentName(ii.packageName, ii.name);
mInstrumentation.init(this, instrContext, appContext, component,
data.instrumentationWatcher, data.instrumentationUiAutomationConnection);
if (mProfiler.profileFile != null && !ii.handleProfiling
&& mProfiler.profileFd == null) {
mProfiler.handlingProfiling = true;
final File file = new File(mProfiler.profileFile);
file.getParentFile().mkdirs();
Debug.startMethodTracing(file.toString(), 8 * 1024 * 1024);
}
} else {
mInstrumentation = new Instrumentation();
}
....
try {
// If the app is being launched for full backup or restore, bring it up in
// a restricted environment with the base application class.
Application app = data.info.makeApplication(data.restrictedBackupMode, null);
mInitialApplication = app;
// don't bring up providers in restricted mode; they may depend on the
// app's custom Application class
if (!data.restrictedBackupMode) {
if (!ArrayUtils.isEmpty(data.providers)) {
installContentProviders(app, data.providers);
// For process that contains content providers, we want to
// ensure that the JIT is enabled "at some point".
mH.sendEmptyMessageDelayed(H.ENABLE_JIT, 10*1000);
}
}
// Do this after providers, since instrumentation tests generally start their
// test thread at this point, and we don't want that racing.
try {
mInstrumentation.onCreate(data.instrumentationArgs);
}
catch (Exception e) {
throw new RuntimeException(
"Exception thrown in onCreate() of "
+ data.instrumentationName + ": " + e.toString(), e);
}
try {
mInstrumentation.callApplicationOnCreate(app);
} catch (Exception e) {
if (!mInstrumentation.onException(app, e)) {
throw new RuntimeException(
"Unable to create application " + app.getClass().getName()
+ ": " + e.toString(), e);
}
}
} finally {
StrictMode.setThreadPolicy(savedPolicy);
}
}
這里有個比較重要的類,mInstrumentation
為什么說它特別重要呢,我們先來看看它怎么初始化的
final ClassLoader cl = instrContext.getClassLoader();
mInstrumentation=(Instrumentation)cl.loadClass(data.instrumentationName.getClassName()).newInstance();
通過反射創(chuàng)建mInstrumentation 然后給進行一系列初始化操作,然后執(zhí)行了
Application app = data.info.makeApplication(data.restrictedBackupMode, null);
data.info是一個LoadeApk對象是整。
LoadeApk.java
public Application makeApplication(boolean forceDefaultAppClass,
Instrumentation instrumentation) {
if (mApplication != null) {
return mApplication;
}
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "makeApplication");
Application app = null;
String appClass = mApplicationInfo.className;
if (forceDefaultAppClass || (appClass == null)) {
appClass = "android.app.Application";
}
try {
java.lang.ClassLoader cl = getClassLoader();
if (!mPackageName.equals("android")) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
"initializeJavaContextClassLoader");
initializeJavaContextClassLoader();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
app = mActivityThread.mInstrumentation.newApplication(
cl, appClass, appContext);
appContext.setOuterContext(app);
} catch (Exception e) {
if (!mActivityThread.mInstrumentation.onException(app, e)) {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
throw new RuntimeException(
"Unable to instantiate application " + appClass
+ ": " + e.toString(), e);
}
}
mActivityThread.mAllApplications.add(app);
mApplication = app;
if (instrumentation != null) {
try {
//這里不會執(zhí)行肖揣,以為傳過來的是null,onCreate在上一層執(zhí)行的
instrumentation.callApplicationOnCreate(app);
} catch (Exception e) {
if (!instrumentation.onException(app, e)) {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
throw new RuntimeException(
"Unable to create application " + app.getClass().getName()
+ ": " + e.toString(), e);
}
}
}
}
執(zhí)行了
app = mActivityThread.mInstrumentation.newApplication(
cl, appClass, appContext);
Instrumentation.java
static public Application newApplication(Class<?> clazz, Context context)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
Application app = (Application)clazz.newInstance();
app.attach(context);
return app;
}
同樣通過反射出一個application并且 調(diào)用其attach()
,也就是說我的自定義application的時候attach就是在這里調(diào)用的
接著上面ActivityThread.handleBindApplication()
中,首先反射出mInstrumentation
和Application然后執(zhí)行了下面一句代碼
try {
mInstrumentation.callApplicationOnCreate(app);
} catch (Exception e) {
if (!mInstrumentation.onException(app, e)) {
throw new RuntimeException(
"Unable to create application " + app.getClass().getName()
+ ": " + e.toString(), e);
}
}
調(diào)用了mInstrumentation.callApplicationOnCreate()
浮入,我們的Application.oncreate()就是在這里調(diào)用的龙优,現(xiàn)在明白為什么Instrumentation為什么那么重要了吧,它就像個管家婆一樣事秀,負(fù)責(zé)家里的大事小事彤断,但是一般不拋頭露面,聽一家之主ActivityThread
的安排易迹。
好宰衙,搞清楚Application后我們在來看看activity在哪里被初始化以及調(diào)用oncreate()方法的
Activity是怎樣啟動的
前面說了ActivityThread.attach()調(diào)用了ActivityManagerService.attachApplication(),在代碼中看到通過調(diào)用ApplicationThread.bindApplication()綁定了application,我們在看看bindApplication()之后在干了什么
ActivityManagerService.java
private final boolean attachApplicationLocked(IApplicationThread thread,
int pid) {
...
try {
...
thread.bindApplication(processName, appInfo, providers, app.instrumentationClass,
profilerInfo, app.instrumentationArguments, app.instrumentationWatcher,
app.instrumentationUiAutomationConnection, testMode,
mBinderTransactionTrackingEnabled, enableTrackAllocation,
isRestrictedBackupMode || !normalMode, app.persistent,
new Configuration(mConfiguration), app.compat,
getCommonServicesLocked(app.isolated),
mCoreSettingsObserver.getCoreSettingsLocked());
...
}catch (Exception e) {
...
}
...
if (normalMode) {
try {
if (mStackSupervisor.attachApplicationLocked(app)) {
didSomething = true;
}
} catch (Exception e) {
Slog.wtf(TAG, "Exception thrown launching activities in " + app, e);
badApp = true;
}
}
調(diào)用了mStackSupervisor.attachApplicationLocked(app),mStackSupervisor是ActivityStackSupervisor
類型,這個類也是非常重要的睹欲,它決定著我們app是否能啟動成功,我們看看做了什么
ActivityStackSupervisor.java
boolean attachApplicationLocked(ProcessRecord app) throws RemoteException {
final String processName = app.processName;
boolean didSomething = false;
for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
//當(dāng)前應(yīng)用的整個activity堆信息
ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
final ActivityStack stack = stacks.get(stackNdx);
if (!isFocusedStack(stack)) {
continue;
}
ActivityRecord hr = stack.topRunningActivityLocked();
if (hr != null) {
if (hr.app == null && app.uid == hr.info.applicationInfo.uid
&& processName.equals(hr.processName)) {
try {
//啟動Activity
if (realStartActivityLocked(hr, app, true, true)) {
didSomething = true;
}
} catch (RemoteException e) {
Slog.w(TAG, "Exception in new application when starting activity "
+ hr.intent.getComponent().flattenToShortString(), e);
throw e;
}
}
}
}
}
if (!didSomething) {
ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
}
return didSomething;
}
看到調(diào)用了realStartActivityLocked(hr, app, true, true))供炼,繼續(xù)看
final boolean realStartActivityLocked(ActivityRecord r, ProcessRecord app,
boolean andResume, boolean checkConfig) throws RemoteException {
...
if (andResume) {
app.hasShownUi = true;
app.pendingUiClean = true;
}
app.forceProcessStateUpTo(mService.mTopProcessState);
app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
System.identityHashCode(r), r.info, new Configuration(mService.mConfiguration),
new Configuration(task.mOverrideConfig), r.compat, r.launchedFromPackage,
task.voiceInteractor, app.repProcState, r.icicle, r.persistentState, results,
newIntents, !andResume, mService.isNextTransitionForward(), profilerInfo);
...
}
看到了調(diào)用了app.thread.scheduleLaunchActivity(),這個app是上一層傳過來的ActivityRecord窘疮,它代表的就是要開啟的Activity對象里面分裝了很多信息袋哼,比如所在的ActivityTask等,如果這是首次打開應(yīng)用闸衫,那么這個Activity會被放到ActivityTask的棧頂涛贯,那么這個thread就是我們的ApplicationThread,我們回到ActivityThread
看下ApplicationThread.scheduleLaunchActivity()做了什么
ActivityThread.java
private class ApplicationThread extends ApplicationThreadNative {
@Override
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
int procState, Bundle state, PersistableBundle persistentState,
List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {
...
updatePendingConfiguration(curConfig);
sendMessage(H.LAUNCH_ACTIVITY, r);
}
}
又用hanlder發(fā)了個消息,我們來看看hanlder怎么處理的
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;
}
Word天蔚出,幾經(jīng)周折又回到ActivityThread自己,心好累弟翘。。我們繼續(xù)~
ActivityThread.java
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
unscheduleGcIdler();
mSomeActivitiesChanged = true;
if (r.profilerInfo != null) {
mProfiler.setProfiler(r.profilerInfo);
mProfiler.startProfiling();
}
// Make sure we are running with the most recent config.
handleConfigurationChanged(null, null);
if (localLOGV) Slog.v(
TAG, "Handling launch of " + r);
// Initialize before creating the activity
WindowManagerGlobal.initialize();
//反射創(chuàng)建一個Activity
Activity a = performLaunchActivity(r, customIntent);
if (a != null) {
r.createdConfig = new Configuration(mConfiguration);
reportSizeConfigurations(r);
Bundle oldState = r.state;
//調(diào)用Activity.onResume
handleResumeActivity(r.token, false, r.isForward,
!r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);
if (!r.activity.mFinished && r.startsNotResumed) {
// The activity manager actually wants this one to start out paused, because it
// needs to be visible but isn't in the foreground. We accomplish this by going
// through the normal startup (because activities expect to go through onResume()
// the first time they run, before their window is displayed), and then pausing it.
// However, in this case we do -not- need to do the full pause cycle (of freezing
// and such) because the activity manager assumes it can just retain the current
// state it has.
performPauseActivityIfNeeded(r, reason);
// We need to keep around the original state, in case we need to be created again.
// But we only do this for pre-Honeycomb apps, which always save their state when
// pausing, so we can not have them save their state when restarting from a paused
// state. For HC and later, we want to (and can) let the state be saved as the
// normal part of stopping the activity.
if (r.isPreHoneycomb()) {
r.state = oldState;
}
}
} else {
// If there was an error, for any reason, tell the activity manager to stop us.
try {
ActivityManagerNative.getDefault()
.finishActivity(r.token, Activity.RESULT_CANCELED, null,
Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
}
看到開頭 骄酗,關(guān)鍵代碼調(diào)用了Activity a = performLaunchActivity(r, customIntent);
返回一個Activity衅胀,我們看看performLaunchActivity()
ActivityThread.java
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
// System.out.println("##### [" + System.currentTimeMillis() + "] ActivityThread.performLaunchActivity(" + r + ")");
ActivityInfo aInfo = r.activityInfo;
if (r.packageInfo == null) {
r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
Context.CONTEXT_INCLUDE_CODE);
}
ComponentName component = r.intent.getComponent();
if (component == null) {
component = r.intent.resolveActivity(
mInitialApplication.getPackageManager());
r.intent.setComponent(component);
}
if (r.activityInfo.targetActivity != null) {
component = new ComponentName(r.activityInfo.packageName,
r.activityInfo.targetActivity);
}
Activity activity = null;
try {
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
//通過反射創(chuàng)建activity實例
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
r.intent.prepareToEnterProcess();
if (r.state != null) {
r.state.setClassLoader(cl);
}
} catch (Exception e) {
if (!mInstrumentation.onException(activity, e)) {
throw new RuntimeException(
"Unable to instantiate activity " + component
+ ": " + e.toString(), e);
}
}
try {
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
if (localLOGV) Slog.v(TAG, "Performing launch of " + r);
if (localLOGV) Slog.v(
TAG, r + ": app=" + app
+ ", appName=" + app.getPackageName()
+ ", pkg=" + r.packageInfo.getPackageName()
+ ", comp=" + r.intent.getComponent().toShortString()
+ ", dir=" + r.packageInfo.getAppDir());
if (activity != null) {
//創(chuàng)建BaseContext
Context appContext = createBaseContextForActivity(r, activity);
CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
Configuration config = new Configuration(mCompatConfiguration);
if (r.overrideConfig != null) {
config.updateFrom(r.overrideConfig);
}
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
+ r.activityInfo.name + " with config " + config);
Window window = null;
if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
window = r.mPendingRemoveWindow;
r.mPendingRemoveWindow = null;
r.mPendingRemoveWindowManager = null;
}
//我們的activity.attach就在這里被調(diào)用的
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window);
if (customIntent != null) {
activity.mIntent = customIntent;
}
r.lastNonConfigurationInstances = null;
activity.mStartedActivity = false;
int theme = r.activityInfo.getThemeResource();
if (theme != 0) {
activity.setTheme(theme);
}
activity.mCalled = false;
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
if (!activity.mCalled) {
throw new SuperNotCalledException(
"Activity " + r.intent.getComponent().toShortString() +
" did not call through to super.onCreate()");
}
r.activity = activity;
r.stopped = true;
if (!r.activity.mFinished) {
activity.performStart();
r.stopped = false;
}
if (!r.activity.mFinished) {
if (r.isPersistable()) {
if (r.state != null || r.persistentState != null) {
mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state,
r.persistentState);
}
} else if (r.state != null) {
mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);
}
}
if (!r.activity.mFinished) {
activity.mCalled = false;
if (r.isPersistable()) {
mInstrumentation.callActivityOnPostCreate(activity, r.state,
r.persistentState);
} else {
mInstrumentation.callActivityOnPostCreate(activity, r.state);
}
if (!activity.mCalled) {
throw new SuperNotCalledException(
"Activity " + r.intent.getComponent().toShortString() +
" did not call through to super.onPostCreate()");
}
}
}
r.paused = true;
mActivities.put(r.token, r);
} catch (SuperNotCalledException e) {
throw e;
} catch (Exception e) {
if (!mInstrumentation.onException(activity, e)) {
throw new RuntimeException(
"Unable to start activity " + component
+ ": " + e.toString(), e);
}
}
return activity;
}
我們看到通過調(diào)用mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);返回一個Activity,哇~這個管家婆真是厲害酥筝!看看做了啥?肯定是反射創(chuàng)建一個Activity嘛雏门。嘿歌。不信?
Instrumentation.java
public Activity newActivity(ClassLoader cl, String className,
Intent intent)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
return (Activity)cl.loadClass(className).newInstance();
}
不得不說Instrumentation真是任勞任怨啊茁影。宙帝。
好,接著上面創(chuàng)建Activity之后,判斷activity是否不等于空募闲,走進if里面調(diào)用了
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window);
原來我們Activity的attach在這里調(diào)用的啊步脓。。。接著下面走
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
看到?jīng)]靴患!看到?jīng)]仍侥!熟悉不,我的application.onCreate也是這樣調(diào)用的,管家婆666666
Instrumentation.java
public void callActivityOnCreate(Activity activity, Bundle icicle) {
prePerformCreate(activity);
activity.performCreate(icicle);
postPerformCreate(activity);
}
調(diào)用了activity.performCreate(icicle);看到這么名字就應(yīng)該很清楚了吧~鸳君,又不信农渊?
Activity.java
final void performCreate(Bundle icicle) {
restoreHasCurrentPermissionRequest(icicle);
onCreate(icicle);
mActivityTransitionState.readState(icicle);
performCreateCommon();
}
看到木有~~~回到上面,當(dāng)執(zhí)行handleLaunchActivity的時候或颊,調(diào)用performLaunchActivity(r, customIntent);
返回了Activity之后
我們接著向下看
ActivityThread.java
Activity a = performLaunchActivity(r, customIntent);
if (a != null) {
r.createdConfig = new Configuration(mConfiguration);
reportSizeConfigurations(r);
Bundle oldState = r.state;
handleResumeActivity(r.token, false, r.isForward,
!r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);
}
調(diào)用了handleResumeActivity砸紊,不用說 我敢肯定是用mInstrumentation來調(diào)用Activity.onResume的,我們來驗證一下
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
ActivityClientRecord r = mActivities.get(token);
....
// TODO Push resumeArgs into the activity for consideration
r = performResumeActivity(token, clearHide, reason);
....
}
繼續(xù)看performResumeActivity()
public final ActivityClientRecord performResumeActivity(IBinder token,
boolean clearHide, String reason) {
ActivityClientRecord r = mActivities.get(token);
if (localLOGV) Slog.v(TAG, "Performing resume of " + r
+ " finished=" + r.activity.mFinished);
if (r != null && !r.activity.mFinished) {
if (clearHide) {
r.hideForNow = false;
r.activity.mStartedActivity = false;
}
try {
r.activity.onStateNotSaved();
r.activity.mFragments.noteStateNotSaved();
if (r.pendingIntents != null) {
deliverNewIntents(r, r.pendingIntents);
r.pendingIntents = null;
}
if (r.pendingResults != null) {
deliverResults(r, r.pendingResults);
r.pendingResults = null;
}
r.activity.performResume();
...
} catch (Exception e) {
if (!mInstrumentation.onException(r.activity, e)) {
throw new RuntimeException(
"Unable to resume activity "
+ r.intent.getComponent().toShortString()
+ ": " + e.toString(), e);
}
}
}
看到了調(diào)用r.activity.performResume(),我們在繼續(xù)看看Activity.performResume()
Activity.java
final void performResume() {
performRestart();
mFragments.execPendingActions();
mLastNonConfigurationInstances = null;
mCalled = false;
// mResumed is set by the instrumentation
mInstrumentation.callActivityOnResume(this);
if (!mCalled) {
throw new SuperNotCalledException(
"Activity " + mComponent.toShortString() +
" did not call through to super.onResume()");
}
// invisible activities must be finished before onResume() completes
if (!mVisibleFromClient && !mFinished) {
Log.w(TAG, "An activity without a UI must call finish() before onResume() completes");
if (getApplicationInfo().targetSdkVersion
> android.os.Build.VERSION_CODES.LOLLIPOP_MR1) {
throw new IllegalStateException(
"Activity " + mComponent.toShortString() +
" did not call finish() prior to onResume() completing");
}
}
// Now really resume, and install the current status bar and menu.
mCalled = false;
mFragments.dispatchResume();
mFragments.execPendingActions();
onPostResume();
if (!mCalled) {
throw new SuperNotCalledException(
"Activity " + mComponent.toShortString() +
" did not call through to super.onPostResume()");
}
}
調(diào)用了mInstrumentation.callActivityOnResume(this);
看來我們的驗證是沒錯的
結(jié)語
至此囱挑,Activity整個的啟動流程也講完了醉顽,大家也看到,調(diào)用過程極其復(fù)雜
源碼中各種條件判斷讓人眼花繚亂平挑,所以說如果你沒記住也沒關(guān)系游添,你只要記住這個流程,理解了Android在控制Activity生命周期時是如何操作弹惦,以及是通過哪幾個關(guān)鍵的類進行操作的就可以了否淤,以后遇到相關(guān)的問題之道從哪塊下手即可,這些過程我雖然也是擼了一遍棠隐,但還是記不清石抡。
時序圖
為了大家方便,我整理了下整個的調(diào)用過程
以上是本文講的整個流程圖,寫的比較水助泽,希望對大家學(xué)習(xí)有所幫助啰扛。。嗡贺。
Android Studio查看源碼的小技巧
不知道大家遇到過沒隐解,我們平時做項目的時候,想要查看哪些類使用了這個變量的,或者調(diào)用過這個類的方法的時候诫睬,都會去按住Ctrl加上鼠標(biāo)點擊那個變量或者方法,但是你只能查看自己的項目使用過
比如你想查看這個方法在哪里使用過你可以按住ctrl再點擊這個方法查看它的引用
可是我們查看源碼的時候煞茫,比如我們查看ActivityThread的源碼 想看一下handleResumeActivity在哪里調(diào)用了,可是你會發(fā)現(xiàn)你按住ctrl在點擊會找不到引用摄凡。為什么呢续徽?因為默認(rèn)是搜索你的項目,也就是說只要你的項目調(diào)用了這個方法亲澡,你才可以搜到钦扭,可是這是系統(tǒng)調(diào)用的,我想查看到底是誰調(diào)用的床绪,怎么辦捏客情?
右鍵單擊這個方法彈出菜單其弊,點擊Find Usages,快捷是Ctrl+g
默認(rèn)是搜索我們的Project引用膀斋,我們修改成
然后點擊Find
就找了調(diào)用的地方梭伐,然后我們雙擊就到了調(diào)用的那行代碼,下次你在按住ctrl單擊那個方法的時候就會彈出來引用的地方
這對于我們查看源碼有很大的幫助~
參考
【凱子哥帶你學(xué)Framework】Activity啟動過程全解析
Android系統(tǒng)進程Zygote啟動過程的源代碼分析
最后
講了那么多,對于我們這些開發(fā)者來說概页,看源碼是非常有必要籽御,文中我只貼出了部分代碼,完整的代碼還要自己去看惰匙,這樣印象也會非常深刻技掏,看一遍不會,我們看兩邊项鬼,雖然一臉懵比哑梳,但是我們要相信自己,這點挫折怎能難倒我們程序員绘盟?最后的最后鸠真,如果文中有錯誤的地方還望大家指出
博主整理不易,轉(zhuǎn)載請注明出處:
http://www.reibang.com/p/dde57a4eb402