成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

3分鐘看懂Activity啟動流程

bang590 / 3973人閱讀

摘要:在結(jié)合下面簡要的分析,分鐘內(nèi)你就能搞明白的啟動流程。關(guān)于的啟動,我在驚天秘密從開始,揭露線程通訊的詭計和主線程的陰謀一文中有提到過。從上圖可以看到,方法中主要做的事情有初始化主線程的主。并使主線程進(jìn)入等待接收消息的無限循環(huán)狀態(tài)。

背景介紹

從事開發(fā)到了一定階段,想要提高就必須搞明白系統(tǒng)的一些工作原理。為什么?因為只有明白了這些,你才能針對平臺的特性寫出優(yōu)質(zhì)的代碼。當(dāng)遇到棘手的問題時,你才能更快速的結(jié)合系統(tǒng)原理去尋找最優(yōu)解決方案。底層基礎(chǔ)決定上層建筑。這個原理在開發(fā)中同樣適用。我是提倡 回歸基礎(chǔ) 的。高級的功能總是由最基本的元件構(gòu)成,就好比為數(shù)不多的元素構(gòu)成了我們難以想象的豐富的物質(zhì)世界一樣。只有掌握了最根本的內(nèi)容,才能促使你爆發(fā)出難以想象的創(chuàng)造力來!

重視基礎(chǔ),回歸基礎(chǔ)。回到最初,去探尋靈感。 愿與君共勉??!

一張圖明白Activity的啟動流程


本篇主要講的是從一個App啟動,到Activity執(zhí)行onCreate()的流程。后面關(guān)于Activity的生命周期相信大家基本都耳熟能詳了。

上圖中我把涉及到的類名方法命均列出來了,你可以看著流程,打開源碼跟著過一遍。相信在過完一遍之后,在今后的開發(fā)中你會更加自信!

上圖乍一看可能感覺有些眼花繚亂,但請不要懼怕。其實根本就沒什么東西,你只需要從藍(lán)色箭頭開始看下去,會發(fā)現(xiàn)一下就看完了。在結(jié)合下面簡要的分析,3分鐘內(nèi)你就能搞明白Activity的啟動流程。

關(guān)于Activity的啟動,我在【驚天秘密!從Thread開始,揭露Android線程通訊的詭計和主線程的陰謀】http://www.jianshu.com/p/8862bd2b6a29 一文中有提到過。這篇文章主要講的是Thread線程到底是個什么東西,以及Android中的消息機(jī)制。感興趣可以點鏈接看一看。

一切從main()方法開始

Android中,一個應(yīng)用程序的開始可以說就是從ActivityThread.java中的main()方法開始的。都是學(xué)過Java的人,想必也都知道Java的程序入口就是main()方法。從這點而言,我們可以把它想成是一個Java程序(注意,不是說Android是個Java程序哦)去理解。

從上圖可以看到,main()方法中主要做的事情有:

初始化主線程的Looper、主Handler。并使主線程進(jìn)入等待接收Message消息的無限循環(huán)狀態(tài)。關(guān)于Android的Handler機(jī)制,可以參考一下我上面提到的文章:
【驚天秘密!從Thread開始,揭露Android線程通訊的詭計和主線程的陰謀】http://www.jianshu.com/p/8862bd2b6a29

下面是main()方法中比較關(guān)鍵的代碼:

public static void main(String[] args){
    ...
    Looper.prepareMainLooper(); 
    //初始化Looper
    ...
    ActivityThread thread = new ActivityThread();
    //實例化一個ActivityThread
    thread.attach(false);
    //這個方法最后就是為了發(fā)送出創(chuàng)建Application的消息
    ... 
    Looper.loop();
    //主線程進(jìn)入無限循環(huán)狀態(tài),等待接收消息
}

2.調(diào)用attach()方法,主要就是為了發(fā)送出初始化Application的消息。這個流程說長不長,說短不短。下文會再捋一捋。

創(chuàng)建Application的消息是如何發(fā)送的呢?

上面提到過,ActivityThread的attach()方法最終的目的是發(fā)送出一條創(chuàng)建Application的消息——H.BIND_APPLICATION,到主線程的主Handler中。那我們來看看attach()方法干了啥。
attach()關(guān)鍵代碼:

public void attach(boolean system){
    ...
    final IActivityManager mgr = ActivityManagerNative.getDefault();  
    //獲得IActivityManager實例,下面會看看它是個啥
    try {
        mgr.attachApplication(mAppThread);
         //看見沒?關(guān)鍵啊。mAppThread這個參數(shù)下面也會說一下
    } catch (RemoteException ex) {
        throw ex.rethrowFromSystemServer();
    }
    ...
}

莫慌莫慌,下面看看上面出現(xiàn)的兩個對象是個啥。

IActivityManager mgr是個啥?

從上圖也可以看到,IActivityManager是一個接口,當(dāng)我們調(diào)用ActivityManagerNative.getDefault()獲得的實際是一個代理類的實例——ActivityManagerProxy,這個東西實現(xiàn)了IActivityManager接口。打開源碼你會發(fā)現(xiàn),ActivityManagerProxy是ActivityManagerNative的一個內(nèi)部類??梢钥闯?,Android團(tuán)隊在設(shè)計的過程中是實踐了最小驚異原則的,就是把相關(guān)的東西盡量放在一起。那么既然是個代理類,它究竟代理了誰?代碼里看看嘍。
下面這個代碼稍微有點繞??!老哥,穩(wěn)??!

先看ActivityManagerProxy的構(gòu)造函數(shù):

public ActivityManagerProxy(IBinder remote) {
        mRemote = remote;
}

這個構(gòu)造函數(shù)非常的簡單。首先它需要一個IBinder參數(shù),然后賦值給mRemote變量。這個mRemote顯然是ActivityManagerNative的成員變量。但對它的操作是由ActivityManagerProxy來代理間接進(jìn)行的。這樣設(shè)計的好處是保護(hù)了mRemote,并且能夠在操作mRemote前執(zhí)行一些別的事務(wù),并且我們是以IActivityManager的身份來進(jìn)行這些操作的!這就非常巧妙了。

那么這個構(gòu)造函數(shù)是在那調(diào)用的呢?

static public IActivityManager asInterface(IBinder obj) {
    if (obj == null) {
        return null;
    }
    IActivityManager in =
        (IActivityManager)obj.queryLocalInterface(descriptor);
    //先檢查一下有沒有
    if (in != null) {
        return in;
    }
    ...
    return new ActivityManagerProxy(obj);
    //這個地方調(diào)用了構(gòu)造函數(shù)
}

上面這個方法是ActivityManagerNative中的一個靜態(tài)方法,它會調(diào)用到ActivityManagerProxy的構(gòu)造方法。然而,這個靜態(tài)方法也需要一個IBinder作為參數(shù)!老夫被繞暈了。但是不怕,咱們繼續(xù)往找!

getDefault()獲取到的靜態(tài)常量gDefault

private static final Singleton gDefault = 
  new Singleton() {
    protected IActivityManager create() {
       IBinder b = ServiceManager.getService("activity");
       //重點??!IBinder實例就是在這里獲得的。
        ...
        IActivityManager am = asInterface(b);
        //調(diào)用了上面的方法。
        ...
        return am;
    }
};

這是ActivityManagerNative的靜態(tài)常量,它是一個單例。在其中終于獲得了前面一直在用的IBinder實例。

IBinder b = ServiceManager.getService("activity");

試著在上圖中找到對應(yīng)位置。

這里是通過ServiceManager獲取到IBinder實例的。如果你以前了解AIDL通訊流程的話。這可能比較好理解一點,這只是通過另一種方式獲取IBinder實例罷了。獲取IBinder的目的就是為了通過這個IBinderActivityManager進(jìn)行通訊,進(jìn)而ActivityManager會調(diào)度發(fā)送H.BIND_APPLICATION即初始化Application的Message消息。如果之前沒接觸過Binder機(jī)制的話,只需知道這個目的就行了。我后面會寫一篇專門介紹Android中Binder機(jī)制的文章。當(dāng)然,你也可以參考一下羅大的系列文章,寫的很詳細(xì),非常的很贊![【Android系統(tǒng)進(jìn)程間通信Binder機(jī)制在應(yīng)用程序框架層的Java接口源代碼分析
】http://m.blog.csdn.net/articl...。

再來看看attachApplication(mAppThread)方法。

public void attachApplication(IApplicationThread app){
  ...
  mRemote.transact(ATTACH_APPLICATION_TRANSACTION, data, reply, 0);  
  ...
}

這個方法我在上圖中也體現(xiàn)出來了。

這個方法中上面這一句是關(guān)鍵。調(diào)用了IBinder實例的tansact()方法,并且把參數(shù)app(這個參數(shù)稍后就會提到)放到了data中,最終傳遞給ActivityManager。

現(xiàn)在,我們已經(jīng)基本知道了IActivityManager是個什么東東了。其實最重要的就是它的一個實現(xiàn)類ActivityManagerProxy,它主要代理了內(nèi)核中與ActivityManager通訊的Binder實例。下面再看看ApplicationThread mAppThread。

ApplicationThread mAppThread又是個啥?

在ActivityThread的成員變量中,你能夠發(fā)現(xiàn):

final ApplicationThread mAppThread = new ApplicationThread();

ApplicationThread是作為ActivityThread中的一個常量出現(xiàn)的。這表明系統(tǒng)不希望這個變量中途被修改,可見這個變量具有特定而十分重要的作用。

我們看看他是啥。

private class ApplicationThread extends ApplicationThreadNative{
    ...
}

ApplicationThread是ActivityThread中的一個內(nèi)部類,為什么沒有多帶帶出來寫在別的地方呢?我覺得這也是對最小驚異原則的實踐。因為ApplicationThread是專門真對這里使用的對象。

它繼承自ApplicationThreadNative,我們再看看它是個啥。

public abstract class ApplicationThreadNative extends Binder 
    implements IApplicationThread{
    ...
    //無參構(gòu)造函數(shù)
    public ApplicationThreadNative() {
        //這是Binder的
        attachInterface(this, descriptor);
    }
    ...
}

那么很明顯,ApplicationThread最終也是一個Binder!同時,由于實現(xiàn)了IApplicationThread接口,所以它也是一個IApplicationThread。以上這系對應(yīng)關(guān)系你都可以在上圖中找到。

我們在ActivityThread中看到的ApplicationThread使用的構(gòu)造函數(shù)是無參的,所以看上面無參構(gòu)造函數(shù)都干了啥!

Binder的attachInterface(IInterface owner, String descriptor)方法沒什么特別的,就是賦值了。

public void attachInterface(IInterface owner, String descriptor) {
    mOwner = owner;
    mDescriptor = descriptor;
}

4.那么IApplicationThread又是啥?老鐵,走著!我們繼續(xù)挖。

public interface IApplicationThread extends IInterface {
    ...
    String descriptor = "android.app.IApplicationThread"; 
    //留意下這個參數(shù)
    ...
}

好吧,這在上圖中沒有,挖的有點什么了。但是學(xué)習(xí)嘛,咱就看看嘍。

IApplicationThread是繼承了IInterface的一個接口,我們需要關(guān)注一下里面的descriptor參數(shù)。后面會用它,它是一個標(biāo)識,查詢的時候很重要。

好,我們終于知道attach()方法中出現(xiàn)的兩個對象是啥了。ApplicationThread作為IApplicationThread的一個實例,承擔(dān)了最后發(fā)送Activity生命周期、及其它一些消息的任務(wù)。也就是說,前面繞了一大圈,最后還是回到這個地方來發(fā)送消息。我擦!

也許你會想,既然在ActivityThread中我們已經(jīng)創(chuàng)建出了ApllicationThread的了,為什么還要繞這么彎路?,當(dāng)然是為了讓系統(tǒng)根據(jù)情況來控制這個過程嘍,不然為什么要把ApplicationThread傳到ActivityManager中呢?

ActivityManagerService調(diào)度發(fā)送初始化消息

經(jīng)過上面的輾轉(zhuǎn),ApplicationThread終于到了ActivityManagerService中了。請在上圖中找到對應(yīng)位置!

從上圖中可以看到,ActivityManagerService中有一這樣的方法:

private final boolean attachApplicationLocked(IApplicationThread thread
, int pid) {
    ...
    thread.bindApplication();
    //注意啦!
    ...
}

ApplicationThread以IApplicationThread的身份到了ActivityManagerService中,經(jīng)過一系列的操作,最終被調(diào)用了自己的bindApplication()方法,發(fā)出初始化Applicationd的消息。

public final void bindApplication(String processName, 
    ApplicationInfo appInfo,
    List 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 services, 
    Bundle coreSettings){
    
    ...
    sendMessage(H.BIND_APPLICATION, data);
}

嚇屎老紙!這么多參數(shù)。這明明很違反參數(shù)盡量要少的原則嘛!所以說,有的時候,開發(fā)過程中還是很難避免一些參數(shù)堆積的情況的。也不能一概而論。

但是,這個地方,我們只要知道最后發(fā)了一條H.BIND_APPLICATION消息,接著程序開始了。

收到初始化消息之后的世界

上面我們已經(jīng)找到初始化Applicaitond的消息是在哪發(fā)送的了?,F(xiàn)在,需要看一看收到消息后都發(fā)生了些什么。

現(xiàn)在上圖的H下面找到第一個消息:H.BIND_APPLICATION。一旦接收到這個消息就開始創(chuàng)建Application了。這個過程是在handleBindApplication()中完成的??纯催@個方法。在上圖中可以看到對應(yīng)的方法。

private void handleBindApplication(AppBindData data) {
    ...
    mInstrumentation = (Instrumentation)
        cl.loadClass(data.instrumentationName.getClassName())
        .newInstance();
    //通過反射初始化一個Instrumentation儀表。后面會介紹。
    ...
    Application app = data.info.makeApplication(data.restrictedBackupMode, null);
    //通過LoadedApp命令創(chuàng)建Application實例
    mInitialApplication = app;
    ...
    mInstrumentation.callApplicationOnCreate(app);
    //讓儀器調(diào)用Application的onCreate()方法
    ...
}

handleBindApplication()是一個很長的方法,但是我為各位看官精選出了上面這幾句代碼。對于本篇的主題來說,他們是至關(guān)重要的。上面短短的代碼中出現(xiàn)了幾個新對象。下面我會一一道來。

Instrumentation儀表,什么鬼?

1.這個叫Instrumentation儀表的東西十分詭異,姑且翻譯為儀器吧。字面上看不出任何它是干什么的線索。但是,我們可以打開文檔看看嘍。

Instrumentation會在應(yīng)用程序的任何代碼運(yùn)行之前被實例化,它能夠允許你監(jiān)視應(yīng)用程序和系統(tǒng)的所有交互。

大概就這個意思啦。

2.但是,從上面的代碼我們可以看出,Instrumentation確實是在Application初始化之前就被創(chuàng)建了。那么它是如何實現(xiàn)監(jiān)視應(yīng)用程序和系統(tǒng)交互的呢?

打開這個類你可以發(fā)現(xiàn),最終Apllication的創(chuàng)建,Activity的創(chuàng)建,以及生命周期都會經(jīng)過這個對象去執(zhí)行。簡單點說,就是把這些操作包裝了一層。通過操作Instrumentation進(jìn)而實現(xiàn)上述的功能。

3.那么這樣做究竟有什么好處呢?仔細(xì)想想。Instrumentation作為抽象,當(dāng)我們約定好需要實現(xiàn)的功能之后,我們只需要給Instrumentation儀表添加這些抽象功能,然后調(diào)用就好。剩下的,不管怎么實現(xiàn)這些功能,都交給Instrumentation儀器的實現(xiàn)對象就好。??!這是多態(tài)的運(yùn)用。啊!這是依賴抽象,不依賴具體的實踐。?。∵@是上層提出需求,底層定義接口,即依賴倒置原則的踐行。呵!抽象不過如此。

從代碼中可以看到,這里實例化Instrumentation的方法是反射!而反射的ClassName是來自于從ActivityManagerService中傳過來的Binder的。套路太深!就是為了隱藏具體的實現(xiàn)對象。但是這樣耦合性會很低。

4.好了,不瞎扯了。既然在說Instrumentation,那就看看最后調(diào)的callApplicationOnCreate()方法。

public void callApplicationOnCreate(Application app) {
    app.onCreate();
}

你沒看錯,它啥也沒干。只是調(diào)用了一下Application的onCreate()方法。這就是為什么它能夠起到監(jiān)控的作用。

在上圖中你能夠看到Instrumentation,以及它的交互過程。

LoadedApk就是data.info哦!

關(guān)于它是怎么來的本篇就不說了,以后可能會介紹下。本篇就看流程就好。所以直接進(jìn)去看它的makeApplication()干了啥,就把Application給創(chuàng)建了。

public Application makeApplication(boolean forceDefaultAppClass,
    Instrumentation instrumentation) {
    ...
    String appClass = mApplicationInfo.className;
    //Application的類名。明顯是要用反射了。
    ...
    ContextImpl appContext = ContextImpl.createAppContext(mActivityThread
        , this);
    //留意下Context
    app = mActivityThread.mInstrumentation
        .newApplication( cl, appClass, appContext);
    //通過儀表創(chuàng)建Application
    ...
}

在這個方法中,我們需要知道的就是,在取得Application的實際類名之后,最終的創(chuàng)建工作還是交由Instrumentation去完成,就像前面所說的一樣。

值得留意的是,就像上圖所標(biāo)注的一樣,當(dāng)需要第二次獲取Application時,同樣只需要調(diào)用這個方法就好。“真是方便!”

現(xiàn)在把目光移回Instrumentation

看看newApplication()中是如何完成Application的創(chuàng)建的。

static public Application newApplication(Class clazz
    , Context context) throws InstantiationException
    , IllegalAccessException
    , ClassNotFoundException {
        Application app = (Application)clazz.newInstance();
        //反射創(chuàng)建,簡單粗暴
        app.attach(context);
        //關(guān)注下這里,Application被創(chuàng)建后第一個調(diào)用的方法。
        //目的是為了綁定Context。
        return app;
    }

我的天,繞了這么多,這Application可算是創(chuàng)建出來了。快給自己一個小紅花吧!

LaunchActivity

當(dāng)Application初始化完成后,系統(tǒng)會更具M(jìn)anifests中的配置的啟動Activity發(fā)送一個Intent去啟動相應(yīng)的Activity。這個過程本篇先不提,下次再說。主要看流程!

直接的,H就收到了一條LAUNCH_ACTIVITY的消息。然后開始初始化Activity之旅。收到消息后,真正處理是在ActivityThread中的handleLaunchActivity()中進(jìn)行的。是不是迫不及待的想要知道發(fā)生了啥?快在上圖中找到對應(yīng)的步驟吧!

private void handleLaunchActivity(ActivityClientRecord r
    , Intent customIntent
    , String reason) {
    ...
    Activity a = performLaunchActivity(r, customIntent);
    //媽蛋!又封裝到另一個方法中創(chuàng)建了。
    ...
    if (a != null) {
        ...
        handleResumeActivity(r.token
        , false
        , r.isForward
        ,!r.activity.mFinished && !r.startsNotResumed
        , r.lastProcessedSeq, reason);
        //Activity創(chuàng)建成功就往onResume()走了!
        ...
    }
}

從上面的代碼中可以看出...好吧,什么都看不出來!

再走一個方法。

private Activity performLaunchActivity(ActivityClientRecord r
    , Intent customIntent) {
    ...
    activity = mInstrumentation.newActivity(
         cl, component.getClassName(), r.intent);
    //通過儀表來創(chuàng)建Activity
    ...
     Application app = r.packageInfo.makeApplication(false
     , mInstrumentation);
     //前面說過,是在獲取Application
    ...
    activity.attach(appContext
        , this
        , getInstrumentation()
        , r.token
        ,.ident
        , app
        , r.intent
        , r.activityInfo
        , title
        , r.parent
        , r.embeddedID
        , r.lastNonConfigurationInstances
        , config
        ,r.referrer
        , r.voiceInteractor
        , window);
    //方法怪出現(xiàn)!
    ...
    if (r.isPersistable()) {
        mInstrumentation.callActivityOnCreate(
          activity, r.state, r.persistentState);
    } else {
        mInstrumentation.callActivityOnCreate(activity, r.state);
    }
    //根據(jù)是否可持久化選擇onCreate()方法。
    ...
}

這個方法內(nèi)容較多,我們一個個看。

activity = mInstrumentation.newActivity(
         cl, component.getClassName(), r.intent);

正如前面所說,Activity、Application的創(chuàng)建及生命周期都被承包給Instrumentation儀表了。所以由它來負(fù)責(zé)??纯碔nstrumentation干了啥。

public Activity newActivity(ClassLoader cl, String className,
            Intent intent)
            throws InstantiationException
            , IllegalAccessException,
            ClassNotFoundException {
        return (Activity)cl.loadClass(className).newInstance();
        //真的沒干啥。反射實例化Activity而已
    }

就是反射出一個Activity而已。

if (r.isPersistable()) {
        mInstrumentation.callActivityOnCreate(
          activity, r.state, r.persistentState);
    } else {
        mInstrumentation.callActivityOnCreate(activity, r.state);
    }

根據(jù)是否可持久化選擇Activity的onCreate()方法。同樣是通過Instrumentation儀表來執(zhí)行onCreate()的。它兩分別對應(yīng)的onCreate()方法為:

onCreate(icicle, persistentState);
//可獲得持久化數(shù)據(jù)

onCreate(icicle);
//平時重寫的最多的。

中間兩個方法留意一下就好,就不在解釋的,感興趣的點源碼看看。

到此,Activity就跑起來了!怎么樣?是不是并不復(fù)雜。

總結(jié)

本篇就到此結(jié)束了。本篇主要流程是從Application創(chuàng)建開始,到第一個Activity onCreate()結(jié)束的。這了流程也不算長,關(guān)鍵是結(jié)合上面的圖來看。重點環(huán)節(jié)我都用不同的顏色標(biāo)記出來了。

看到這里的同學(xué)獎勵自己一包辣條吧!

參考鏈接

[【Android系統(tǒng)進(jìn)程間通信Binder機(jī)制在應(yīng)用程序框架層的Java接口源代碼分析
】http://m.blog.csdn.net/articl...

【Instrumentation API】https://developer.android.com/reference/android/app/Instrumentation.html

【Activity啟動流程(下)】http://www.jianshu.com/p/3cf90c633bb1

【Android學(xué)習(xí)——ActivityManager與Proxy模式的運(yùn)用】http://www.cnblogs.com/bastard/archive/2012/05/25/2517522.html

【ActivityManager API】https://developer.android.com/reference/android/app/ActivityManager.html

感謝你的閱讀。如果你覺得對你有用的話,記得給CoorChice點個贊,添加下關(guān)注哦,謝謝!

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/67210.html

相關(guān)文章

  • 【 全干貨 】5 分鐘帶你看懂 Docker !

    摘要:本文從定義,作用,技術(shù)架構(gòu),安裝和使用等全方位帶你看懂。如圖中左邊紅框中和右邊的紅框中都唯一表示為同一個鏡像。最后,于開發(fā)者而言提供了一種開發(fā)環(huán)境的管理辦法,與測試人員而言保證了環(huán)境的同步,于運(yùn)維人員提供了可移植的標(biāo)準(zhǔn)化部署流程。 作者丨唐文廣:騰訊工程師,負(fù)責(zé)無線研發(fā)部地圖測試。 導(dǎo)語:Docker,近兩年才流行起來的超輕量級虛擬機(jī),它可以讓你輕松完成持續(xù)集成、自動交付、自動部署...

    Edison 評論0 收藏0
  • 【 全干貨 】5 分鐘帶你看懂 Docker !

    摘要:本文從定義,作用,技術(shù)架構(gòu),安裝和使用等全方位帶你看懂。最后,于開發(fā)者而言提供了一種開發(fā)環(huán)境的管理辦法,與測試人員而言保證了環(huán)境的同步,于運(yùn)維人員提供了可移植的標(biāo)準(zhǔn)化部署流程。顯示上圖內(nèi)容就表明安裝完成。 作者丨唐文廣:騰訊工程師,負(fù)責(zé)無線研發(fā)部地圖測試。 導(dǎo)語:Docker,近兩年才流行起來的超輕量級虛擬機(jī),它可以讓你輕松完成持續(xù)集成、自動交付、自動部署,并且實現(xiàn)開發(fā)環(huán)境、測試環(huán)...

    lavnFan 評論0 收藏0
  • Intent 使用詳解

    摘要:注意類別指示此的圖標(biāo)應(yīng)放入系統(tǒng)的應(yīng)用啟動器。如果元素未使用指定圖標(biāo),則系統(tǒng)將使用元素中的圖標(biāo)使用方法對象是對象的包裝器。主要應(yīng)用于以下場景通知應(yīng)用小部件定時任務(wù)使用注意事項適用于啟動的。 showImg(https://segmentfault.com/img/remote/1460000019975019?w=157&h=54); 極力推薦文章:歡迎收藏Android 干貨分享 s...

    lifesimple 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<