Android必備知識

1.Volley優(yōu)缺點(5優(yōu)5缺)

  • Volley 的優(yōu)點

① 非常適合進(jìn)行數(shù)據(jù)量不大,但通信頻繁的網(wǎng)絡(luò)操作;
② 可直接在主線程調(diào)用服務(wù)端并處理返回結(jié)果稼钩;
③ 可以取消請求肢扯,容易擴展负蚊,面向接口編程铝宵;
④ 網(wǎng)絡(luò)請求線程NetworkDispatcher默認(rèn)開啟了4個绸贡,可以優(yōu)化浩考,通過手機CPU數(shù)量屠缭;
⑤ 通過使用標(biāo)準(zhǔn)的HTTP緩存機制保持磁盤和內(nèi)存響應(yīng)的一致掘猿;

  • Volley 的缺點

① 使用的是httpclient两波、HttpURLConnection滔蝉;
② Android 6.0不支持httpclient了击儡;
③ 對大文件下載 Volley的表現(xiàn)非常糟糕;
④ 只支持http請求蝠引;
⑤ 圖片加載性能一般阳谍;

Volley能否下載電影以及加載大圖片?

Volley的request是在內(nèi)存上操作數(shù)據(jù)螃概,所以不適用大文件的操作矫夯。比如:ImageRequest去加載大圖片的時候,也是在內(nèi)存中讀取的吊洼,這個時候就可能會出現(xiàn)OOM训貌。

使用

private void getStringRequest() {
            String url="http://api.k780.com:88/?app=phone.get&phone=13800138000&appkey=10003&sign=b59bc3ef6191eb9f747dd4e83c99f2a4&format=json";
            RequestQueue queue= Volley.newRequestQueue(this);
            StringRequest request=new StringRequest(url, new Response.Listener<String>() {
                @Override
                public void onResponse(String s) {
                    Log.e("success",s);
                }
            }, new Response.ErrorListener() {
                @Override
                public void onErrorResponse(VolleyError volleyError) {

                }
            });
            queue.add(request);
        }

源碼解析:http://www.reibang.com/p/33be82da8f25

2. handler工作機制

Handler、Looper冒窍、Message递沪、MessageQueue四兄弟

關(guān)系如下:

MessageQueue:裝食物的容器
Message:被裝的食物
Handler(msg.target實際上就是Handler):食物的消費者
Looper:負(fù)責(zé)分發(fā)食物的人

當(dāng)我們調(diào)用handler.sendMessage(msg)方法發(fā)送一個Message時,實際上這個Message是發(fā)送到與當(dāng)前線程綁定的一個MessageQueue中综液,然后與當(dāng)前線程綁定的Looper將會不斷的從MessageQueue中取出新的Message款慨,最后調(diào)用msg.target.dispathMessage(msg)(msg.target指的是當(dāng)前的handler)方法將消息分發(fā)到與Message綁定的Runnablehandler.handleMessage()方法中。

    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg); // 調(diào)用run方法
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg); // 調(diào)用handleMessage方法
        }
    }

由此谬莹,不難發(fā)現(xiàn):

  • 一個Thread可以對應(yīng)多個Handler檩奠;
  • 一個Thread對應(yīng)一個Looper和MessageQueue
  • Handler與Thread共享Looper和MessageQueue。
  • Message只是消息的載體附帽,將會被發(fā)送到與線程綁定的唯一的MessageQueue中埠戳,并且被與線程綁定的唯一的Looper分發(fā),被與其自身綁定的Handler消費蕉扮。

我們都知道Handler是運行在主線程上的整胃,那我們?nèi)绾卧谧泳€程上使用Handler呢?

你可能會寫下如下代碼:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);

        childThread = new MyThread();
        childThread.start();

        //這樣之后喳钟,childHandler和childLooper就關(guān)聯(lián)起來了爪模。
        Handler childHandler = new Handler(childThread.childLooper){
            public void handleMessage(Message msg) {
                Log.d("LK","childThread========"+Thread.currentThread());
            };
        };
        childHandler.sendMessage(1);
    }

    private class MyThread extends Thread{
        public Looper childLooper;

        @Override
        public void run() {
            Looper.prepare();//創(chuàng)建與當(dāng)前線程相關(guān)的Looper
            childLooper = Looper.myLooper();//獲取當(dāng)前線程的Looper對象
            Looper.loop();//調(diào)用此方法欠啤,消息才會循環(huán)處理
        }
    }

但是運行時會發(fā)現(xiàn)上述代碼偶爾會由于空指針錯誤而崩潰。這是因為當(dāng)子線程start的時候屋灌,雖然子線程的run方法得到執(zhí)行,但是主線程中代碼依然會向下執(zhí)行应狱,當(dāng)我們new Handler(childThread.childLooper)的時候共郭,run方法中的Looper對象可能還沒初始化。

其實疾呻,Android早已經(jīng)為我們提供好解決上述問題的方法除嘹,即HandlerThread類。

HandlerThread

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);

        HandlerThread handlerThread = new HandlerThread("HandlerThread");
        handlerThread.start();

        Handler mHandler = new Handler(handlerThread.getLooper()){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                Log.d("LK","childThread========"+Thread.currentThread());//子線程
            }
        };

        Log.d("LK","mainThread======="+Thread.currentThread());//主線程
        mHandler.sendEmptyMessage(1);
    }

創(chuàng)建HandlerThread對象的時候岸蜗,有個參數(shù)尉咕,是指定線程名字的。上面的代碼不管運行多少次都不會奔潰Aг馈D甓小!并且這種方法創(chuàng)建的handlerhandleMessage方法運行在子線程中铃慷。所以我們可以在這里處理一些耗時的邏輯单芜。到此我們完成了主線程給子線程發(fā)通知,在子線程做耗時邏輯的操作犁柜。

為什么使用HandlerThread就可以避免空指針那洲鸠?

public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }
        
        // If the thread has been started, wait until the looper has been created.
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }

HandlerThread類的getLooper方法如上,我們看到當(dāng)我們獲取當(dāng)前線程Looper對象的時候馋缅,會先判斷當(dāng)前線程是否存活扒腕,然后還要判斷Looper對象是否為空,都滿足之后才會返回給我Looper對象萤悴,否則處于等待狀態(tài)q!既然有等待稚疹,那就有喚醒的時候居灯,在那里那?内狗?怪嫌?我們發(fā)現(xiàn)HandlerThread的run方法中,有如下代碼:

  @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }

所以柳沙,當(dāng)Looper對象初始化完畢才會喚醒之前getLooper中的等待岩灭,返回Looper對象。HandlerThread很好的避免了之前空指針的產(chǎn)生赂鲤。

所以以后要想創(chuàng)建非主線程的Handler時噪径,我們用HandlerThread類提供的Looper對象即可柱恤。


ZXing源碼處理handler在子線程使用

handlerInitLatch = new CountDownLatch(1);

    Handler getHandler() {
        try {
            handlerInitLatch.await();
        } catch (InterruptedException ie) {
            // continue?
        }
        return handler;
    }

    @Override
    public void run() {
        Looper.prepare();
        handler = new DecodeHandler(context, cameraManager, captureHandler, hints);
        handlerInitLatch.countDown();
        Looper.loop();
    }

Looper.loop()為什么可以一直運行而不卡死?

先看源碼:

 public static void loop() {
        final Looper me = myLooper();//獲得當(dāng)前線程綁定的Looper
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;//獲得與Looper綁定的MessageQueue

        // 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();
        
        //進(jìn)入死循環(huán)找爱,不斷地去取對象梗顺,分發(fā)對象到Handler中消費
        for (;;) {
            Message msg = queue.next(); // 不斷的取下一個Message對象,在這里可能會造成堵塞车摄。
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            // This must be in a local variable, in case a UI event sets the logger
            Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }
            
            //在這里寺谤,開始分發(fā)Message了
            //至于這個target是神馬?什么時候被賦值的吮播? 
            //我們一會分析Handler的時候就會講到
            msg.target.dispatchMessage(msg);

            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);
            }
            
            //當(dāng)分發(fā)完Message之后变屁,當(dāng)然要標(biāo)記將該Message標(biāo)記為 *正在使用* 啦
            msg.recycleUnchecked();
        }
    }

epoll+pipe,有消息就依次執(zhí)行意狠,沒消息就block住粟关,讓出CPU,等有消息了环戈,epoll會往pipe中寫一個字符闷板,把主線程從block狀態(tài)喚起

3.Android運行過程

Zygote和System進(jìn)程的啟動過程(系統(tǒng)啟動運行)

+------------+    +-------+   +-----------+
|Linux Kernel+--> |init.rc+-> |app_process|
+------------+    +-------+   +-----------+
               create and public          
                 server socket
  • /system/bin/app_process 是Zygote服務(wù)啟動的進(jìn)程名。
  • Zygote啟動完成之后谷市,會啟動System進(jìn)程蛔垢。
  • 在System進(jìn)程中會創(chuàng)建一個socket客戶端,后續(xù)AMS(ActivityManagerService)會通過此客戶端和zygote通信迫悠。
  • 在System進(jìn)程中啟動各種Service(例如AMS鹏漆,PMS,WMS等)创泄,AMS會啟動系統(tǒng)界面以及Home程序艺玲。。
IMG_20180510_111505.jpg

App進(jìn)程啟動

① AMS向Zygote發(fā)起請求(通過之前保存的socket)鞠抑,攜帶各種參數(shù)饭聚,包括“android.app.ActivityThread”。
② Zygote進(jìn)程fork自己搁拙,然后在新Zygote進(jìn)程中調(diào)用RuntimeInit.zygoteInit方法進(jìn)行一系列的初始化(commonInit秒梳、Binder線程池初始化等)。
③ 新Zygote進(jìn)程中調(diào)用ActivityThread的main函數(shù)箕速,并啟動消息循環(huán)酪碘。

  • 首先,ActivityThread(ActivityThread是應(yīng)用程序的入口)從static main()函數(shù)開始盐茎,調(diào)用prepareMainLooper()為UI線程創(chuàng)建一個消息對列(MessageQueue)兴垦。
  • 然后,創(chuàng)建一個ActivityThread對象,在其初始化代碼中會創(chuàng)建一個H(Handler)對象和一個AppplicationThread(Binder)對象探越,Binder負(fù)責(zé)接收遠(yuǎn)程AmS的IPC(進(jìn)程間通信)調(diào)用狡赐,收到消息后,通過Handler將消息發(fā)送到消息隊列钦幔,UI主線程會異步的從消息隊列中取出消息并執(zhí)行操作枕屉。
  • 接著,UI主線程調(diào)用Looper.loop()進(jìn)入消息循環(huán)體鲤氢。當(dāng)ActivityThread接收到AmS發(fā)送start某個activity后搀庶,就會創(chuàng)建指定的Activity對象。
  • Activity又會創(chuàng)建PhoneWindow類--->DecroView類--->相應(yīng)的View創(chuàng)建完成后铜异,Activity需要把創(chuàng)建好的界面顯示到屏幕上,于是調(diào)用WindowManager秸架,他會創(chuàng)建一個ViewRoot對象揍庄,創(chuàng)建完ViewRoot后,WindowManager調(diào)用WindowManagerService提供的遠(yuǎn)程接口完成添加一個窗口并顯示到屏幕上东抹。
    -------------------------以上摘自柯元旦<Android內(nèi)核剖析>
IMG_20180510_112350.jpg

4.Android 事件分發(fā)機制

分發(fā)機制

注意:當(dāng)ViewGroup的onInterceptTouchEvent返回superfalse此處需要注意B熳印!g郧食茎!當(dāng)自定義控件繼承自ViewGroup時,上圖是正確的馏谨;而當(dāng)自定義控件繼承自控件(MyView)時别渔,返回super調(diào)用的是MyView的onInterceptTouchEvent,最后返回什么由MyView的onInterceptTouchEvent為準(zhǔn)惧互。

U
move_up
move_up2.png
  • 紅色的箭頭代表ACTION_DOWN 事件的流向
  • 藍(lán)色的箭頭代表ACTION_MOVE 和 ACTION_UP 事件的流向

5.RelativeLayout與LinearLayout的性能分析

  • 測試

主要是3個方法哎媚,onMeasure(),onLayout(),onDraw()
可以自己自定義控件測試。
通過測試喊儡,我們發(fā)現(xiàn)LinearLayout的onMeasure()耗時僅是RelativeLayout的一半左右拨与。

  • 原因

通過查看RelativieLayout與LinearLayout的onMeasure()方法發(fā)現(xiàn),RelativeLayout的對子View測量了兩次艾猜。為什么要測量兩次呢买喧?

在RelativeLayout中View存在橫向和縱向上的依賴。所以匆赃,源碼中是對子View分別進(jìn)行了橫向(第一次)和縱向(第二次)的測量淤毛。
在LinearLayout中,是先判斷方向炸庞,然后onMeasure()钱床。但是如果遇到有weight屬性的子View,會對其進(jìn)行第二次的onMeasure()埠居。so, try not to use “weight” as possible as you can.

5.EventBus源碼

image.png
image.png

6.MVC和MVP

image.png

(最主要區(qū)別)View與Model并不直接交互查牌,而是通過與Presenter交互來與Model間接交互事期。而在MVC中View可以與Model直接交互

視圖(View):用戶界面
控制器(Controller):業(yè)務(wù)邏輯
模型(Model):數(shù)據(jù)保存

  • Models--負(fù)責(zé)主要的數(shù)據(jù)或者操作數(shù)據(jù)的數(shù)據(jù)訪問層。包括http數(shù)據(jù)交互纸颜,sqlite數(shù)據(jù)交互兽泣,sharePreference,contentProvider等胁孙。

  • Views--負(fù)責(zé)展示層(GUI)唠倦,可以聯(lián)想一下以 UI 開頭的所有類。

  • Controller/Presenter/ViewModel--負(fù)責(zé)協(xié)調(diào) Model 和 View涮较,通常根據(jù)用戶在View上的動作在Model上作出對應(yīng)的更改稠鼻,同時將更改的信息返回到View上。

7.TCP/IP 三次握手和四次揮手

【HTTP與TCP/IP】和其他的協(xié)議在最初OSI模型中的位置:


握手&揮手流程
image.png

握手

  • 第一次握手:客戶端發(fā)送了一個帶有SYN的Tcp報文到服務(wù)器狂票,這個三次握手中的開始候齿。表示客戶端想要和服務(wù)端建立連接。
  • 第二次握手:服務(wù)端接收到客戶端的請求闺属,返回客戶端報文慌盯,這個報文帶有SYN和ACK標(biāo)志,詢問客戶端是否準(zhǔn)備好掂器。
  • 第三次握手:客戶端再次響應(yīng)服務(wù)端一個ACK亚皂,表示我已經(jīng)準(zhǔn)備好。

揮手

  • 第一次揮手:TCP發(fā)送一個FIN(結(jié)束)国瓮,用來關(guān)閉客戶到服務(wù)端的連接灭必。
  • 第二次揮手:服務(wù)端收到這個FIN,他發(fā)回一個ACK(確認(rèn))巍膘,確認(rèn)收到序號為收到序號+1厂财,和SYN一樣,一個FIN將占用一個序號峡懈。
  • 第三次揮手:服務(wù)端發(fā)送一個FIN(結(jié)束)到客戶端璃饱,服務(wù)端關(guān)閉客戶端的連接。
  • 第四次揮手:客戶端發(fā)送ACK(確認(rèn))報文確認(rèn)肪康,并將確認(rèn)的序號+1荚恶,這樣關(guān)閉完成。

Q: 為什么揮手是四次磷支,而不是三次谒撼,即第二次和第三次FIN+ACK明明可以一起發(fā)送,就像握手是SYN和ACK一起發(fā)送一樣雾狈?

A:第二次揮手時廓潜,回復(fù)客戶端一個ACK,然后同時繼續(xù)傳輸數(shù)據(jù),當(dāng)數(shù)據(jù)傳輸完畢后辩蛋,發(fā)起第三次揮手呻畸,所以需要分成兩次發(fā)送。

8.Dalvik和ART區(qū)別

Android主流的Dalvik和ART兩個虛擬機悼院,它們最大的區(qū)別就是是否支持多個dex文件的加載伤为。

ART也就是Android 5.0以上的虛擬機本身就支持多個dex文件加載,而Dalvik卻不支持多個dex加載据途,只支持一個dex加載绞愚,如果需要支持多個dex加載則需要引入multi-dex方案。

ART上應(yīng)用啟動快颖医,運行快位衩,但是耗費更多存儲空間,安裝時間長熔萧,總的來說ART的功效就是"空間換時間"蚂四。

ART:Ahead of Time Dalvik: Just in Time

  • 什么是Dalvik:Dalvik是Google公司自己設(shè)計用于Android平臺的Java虛擬機。Dalvik虛擬機是Google等廠商合作開發(fā)的Android移動設(shè)備平臺的核心組成部分之一哪痰,它可以支持已轉(zhuǎn)換為.dex(即Dalvik Executable)格式的Java應(yīng)用程序的運行,.dex格式是專為Dalvik應(yīng)用設(shè)計的一種壓縮格式久妆,適合內(nèi)存和處理器速度有限的系統(tǒng)晌杰。Dalvik經(jīng)過優(yōu)化,允許在有限的內(nèi)存中同時運行多個虛擬機的實例筷弦,并且每一個Dalvik應(yīng)用作為獨立的Linux進(jìn)程執(zhí)行肋演。獨立的進(jìn)程可以防止在虛擬機崩潰的時候所有程序都被關(guān)閉。

  • 什么是ART:Android操作系統(tǒng)已經(jīng)成熟烂琴,Google的Android團(tuán)隊開始將注意力轉(zhuǎn)向一些底層組件爹殊,其中之一是負(fù)責(zé)應(yīng)用程序運行的Dalvik運行時。Google開發(fā)者已經(jīng)花了兩年時間開發(fā)更快執(zhí)行效率更高更省電的替代ART運行時奸绷。ART代表Android Runtime,其處理應(yīng)用程序執(zhí)行的方式完全不同于Dalvik梗夸,Dalvik是依靠一個Just-In-Time(JIT)編譯器去解釋字節(jié)碼。開發(fā)者編譯后的應(yīng)用代碼需要通過一個解釋器在用戶的設(shè)備上運行号醉,這一機制并不高效反症,但讓應(yīng)用能更容易在不同硬件和架構(gòu)上運行。ART則完全改變了這套做法畔派,在應(yīng)用安裝的時候就預(yù)編譯字節(jié)碼到機器語言铅碍,這一機制叫Ahead-Of-Time(AOT)編譯。在移除解釋代碼這一過程后线椰,應(yīng)用程序執(zhí)行將更有效率胞谈,啟動更快。

ART優(yōu)點:

  • 系統(tǒng)性能的顯著提升
  • 應(yīng)用啟動更快、運行更快烦绳、體驗更流暢卿捎、觸感反饋更及時
  • 更長的電池續(xù)航能力
  • 支持更低的硬件

ART缺點:
*更大的存儲空間占用,可能會增加10%-20%
*更長的應(yīng)用安裝時間

9.熱修復(fù)

https://yq.aliyun.com/articles/231111

10.AsyncTask工作原理

從構(gòu)造函數(shù)開始說起爵嗅,AsyncTask()中構(gòu)造了一個InternalHandlerFutureTask(可以理解為有返回值的Runnable)娇澎。

    public AsyncTask(@Nullable Looper callbackLooper) {
        mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
            ? getMainHandler()
            : new Handler(callbackLooper);

        mWorker = new WorkerRunnable<Params, Result>() {
            public Result call() throws Exception {
                mTaskInvoked.set(true);
                Result result = null;
                try {
                    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                    //noinspection unchecked
                    //這里!6蒙埂L俗!這里N焙堋F萆丁!锉试!
                    result = doInBackground(mParams); 
                    Binder.flushPendingCommands();
                } catch (Throwable tr) {
                    mCancelled.set(true);
                    throw tr;
                } finally {
                    postResult(result);
                }
                return result;
            }
        };

        mFuture = new FutureTask<Result>(mWorker) {
            @Override
            protected void done() {
                try {
                    postResultIfNotInvoked(get());
                } catch (InterruptedException e) {
                    android.util.Log.w(LOG_TAG, e);
                } catch (ExecutionException e) {
                    throw new RuntimeException("An error occurred while executing doInBackground()",
                            e.getCause());
                } catch (CancellationException e) {
                    //取消AsyncTask時走這里
                    postResultIfNotInvoked(null);
                }
            }
        };
    }

當(dāng)執(zhí)行AsyncTask的時候猫十,execute()最終會調(diào)用executeOnExecutor(),先走 onPreExecute()呆盖,然后線程池執(zhí)行FutureTask對象拖云。

    public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
            Params... params) {
        if (mStatus != Status.PENDING) {
            switch (mStatus) {
                case RUNNING:
                    throw new IllegalStateException("Cannot execute task:"
                            + " the task is already running.");
                case FINISHED:
                    throw new IllegalStateException("Cannot execute task:"
                            + " the task has already been executed "
                            + "(a task can be executed only once)");
            }
        }

        mStatus = Status.RUNNING;
        //這里!Sτ帧宙项!這里!V昕浮尤筐!
        onPreExecute();

        mWorker.mParams = params;
        exec.execute(mFuture);

        return this;
    }

doInBackground所在的call()方法執(zhí)行結(jié)束后,直接回調(diào)FutureTaskdone()方法洞就,當(dāng)取消異步線程時此處會拋出異常從而執(zhí)行postResultIfNotInvoked(null)盆繁。

    private Result postResult(Result result) {
        @SuppressWarnings("unchecked")
        Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
                new AsyncTaskResult<Result>(this, result));
        message.sendToTarget();
        return result;
    }

    ... ... 

    private static class InternalHandler extends Handler {
        public InternalHandler(Looper looper) {
            super(looper);
        }

        @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
        @Override
        public void handleMessage(Message msg) {
            AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
            switch (msg.what) {
                case MESSAGE_POST_RESULT:
                    // There is only one result
                    //onPostExecute和onCancelled
                    result.mTask.finish(result.mData[0]);
                    break;
                case MESSAGE_POST_PROGRESS:
                    //這里!Q油昂!這里!G惴 秕狰!
                    result.mTask.onProgressUpdate(result.mData);
                    break;
            }
        }
    }

··· ···
    private void finish(Result result) {
        if (isCancelled()) {
            onCancelled(result);
        } else {
            onPostExecute(result);
        }
        mStatus = Status.FINISHED;
    }

Q: onProgressUpdate在源碼中沒有觸發(fā)?
A: 只有當(dāng)開發(fā)者在復(fù)寫的doInBackground方法中調(diào)用publishProgress方法躁染,通過InternalHandler實例發(fā)送一條MESSAGE_POST_PROGRESS消息更新進(jìn)度鸣哀,onProgressUpdate 方法才將被調(diào)用; 如果遇到異常吞彤,則發(fā)送一條MESSAGE_POST_CANCEL的消息取消任務(wù)我衬,onCancelled()方法將被調(diào)用叹放。

11.LeakCanary 的原理(如何確定是否發(fā)生內(nèi)存泄露)

  • 監(jiān)聽 Activity 的生命周期
  • onDestroy的時候,創(chuàng)建相應(yīng)的 RefrenceRefrenceQueue挠羔,并啟動后臺進(jìn)程去檢測井仰。
  • 一段時間之后,從 RefrenceQueue 讀取破加,若讀取不到相應(yīng) activity 的 refrence俱恶,有可能發(fā)生泄露了,這個時候范舀,再促發(fā) gc合是,一段時間之后,再去讀取锭环,若在從 RefrenceQueue 還是讀取不到相應(yīng) activity 的 refrence聪全,可以斷定是發(fā)生內(nèi)存泄露了。
  • 發(fā)生內(nèi)存泄露之后辅辩,dump难礼,分析 hprof 文件,找到泄露路徑(使用 haha 庫分析)玫锋。

其中蛾茉,比較重要的是如何確定是否發(fā)生內(nèi)存泄露,而如何確定發(fā)生內(nèi)存泄露最主要的原理是通過 RefrenceRefrenceQueue撩鹿。

弱引用WeakReference 和引用隊列 ReferenceQueue 聯(lián)合使用時臀稚,如果弱引用持有的對象被垃圾回收,Java 虛擬機就會把這個弱引用加入到與之關(guān)聯(lián)的引用隊列中三痰。

12.一個線程是否只有一個Looper,如何保證一個線程只有一個Looper?

A:
第一個問題窜管,一個線程最多只能有一個Looper散劫。
第二個問題,源碼中使用ThreadLocal來存儲每個線程的Looper對象幕帆,源碼如下:
Looper.class

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));
    }

應(yīng)用實現(xiàn):EventBus

用于保存post狀態(tài)等信息获搏。

    private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {
        @Override
        protected PostingThreadState initialValue() {
            return new PostingThreadState();
        }
    };
 
··· ···

  public void post(Object event) {
        PostingThreadState postingState = currentPostingThreadState.get();
        List<Object> eventQueue = postingState.eventQueue;
        eventQueue.add(event);

        if (!postingState.isPosting) {
            postingState.isMainThread = isMainThread();
            postingState.isPosting = true;
            if (postingState.canceled) {
                throw new EventBusException("Internal error. Abort state was not reset");
            }
            try {
                while (!eventQueue.isEmpty()) {
                    postSingleEvent(eventQueue.remove(0), postingState);
                }
            } finally {
                postingState.isPosting = false;
                postingState.isMainThread = false;
            }
        }
    }

13. ANR異常發(fā)生條件

    1. 5s內(nèi)沒有響應(yīng)用戶輸入事件;
    1. 10s內(nèi)廣播接收器沒有處理完畢失乾;
    1. 20s內(nèi)服務(wù)沒有處理完畢常熙;

14. Http和Https的區(qū)別?

    1. Https是ssl加密傳輸碱茁,Http是明文傳輸裸卫;
    1. Https是使用端口443,而Http使用80纽竣;
    1. Https是SSL+HTTP協(xié)議構(gòu)建的可進(jìn)行加密傳輸墓贿、身份認(rèn)證的網(wǎng)絡(luò)協(xié)議要比Http協(xié)議安全茧泪;
    1. Https協(xié)議需要到CA申請證書;


      image.png

15.常用的圖片壓縮方式

圖片壓縮方式常用的有尺寸壓縮聋袋、質(zhì)量壓縮队伟、格式變化以及通過JNI調(diào)用libjpeg庫來進(jìn)行壓縮,下面就先分別介紹下常見的質(zhì)量壓縮和尺寸壓縮幽勒。(尺寸壓縮嗜侮,質(zhì)量壓縮底層也是通過調(diào)用native的方法進(jìn)行壓縮的,而native中的則是通過Skia這個庫實現(xiàn)的啥容,但是锈颗,最終還是調(diào)用了libjpeg庫進(jìn)行壓縮的。)

  1. 格式變化
    現(xiàn)在android支持的圖片格式有三種:png干毅、jpeg宜猜、WebP
  • png: 無損圖片的壓縮類型硝逢,能保存透明等圖姨拥,它同時提供24位和48位真彩色圖像支持以及其他諸多技術(shù)性支持。
  • Jpeg:有損圖片的壓縮類型渠鸽,有損壓縮方式去除冗余的圖像和彩色數(shù)據(jù)叫乌,獲取得極高的壓縮率的同時能展現(xiàn)十分豐富生動的圖像,換句話說徽缚,就是可以用最少的磁盤空間得到較好的圖像質(zhì)量憨奸。但是,bitmap quality屬性越小凿试,圖片的清晰度越差排宰。
  • WebP:WebP(發(fā)音 weppy,項目主頁)那婉,是谷歌推出的一種支持有損壓縮和無損壓縮的圖片文件格式板甘,派生自圖像編碼格式VP8。
  1. 質(zhì)量壓縮
    設(shè)置bitmap quality屬性详炬,降低圖片的質(zhì)量盐类,像素不會減少(就是指bitmap所占的內(nèi)存大小)呛谜,第一個參數(shù)為需要壓縮的bitmap圖片對象在跳,第二個參數(shù)為壓縮后圖片保存的位置設(shè)置quality屬性0-100,來實現(xiàn)壓縮隐岛。(因為png是無損壓縮猫妙,所以該屬性對png是無效的人乓。
/**
     * 質(zhì)量壓縮
     *
     * @param format  圖片格式   PNG图贸,JPEG仆葡,WEBP
     * @param quality 圖片的質(zhì)量 1-100
     */
    public void compress(Bitmap.CompressFormat format, int quality) {
        FileOutputStream fos = null;
        try {
            //得到一個儲存路徑
            File file = new File(Environment.getExternalStorageDirectory(), "test.jpg");
            //得到一個文件輸入流
            fos = new FileOutputStream(file);
            //開始壓縮
            Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.default_icon);
            bitmap.compress(format, quality, fos);

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

3.尺寸壓縮
尺寸壓縮由于是減小了圖片的像素肖卧,所以它直接對bitmap產(chǎn)生了影響,當(dāng)然最終生成的file文件也是相對的變小了韭脊。

  • 通過縮放圖片像素來減少圖片占用內(nèi)存大小
public static void compressBitmapToFile(Bitmap bmp, File file){
    // 尺寸壓縮倍數(shù),值越大童谒,圖片尺寸越小
    int ratio = 2;
    // 壓縮Bitmap到對應(yīng)尺寸
    Bitmap result = Bitmap.createBitmap(bmp.getWidth() / ratio, bmp.getHeight() / ratio, Config.ARGB_8888);
    Canvas canvas = new Canvas(result);
    Rect rect = new Rect(0, 0, bmp.getWidth() / ratio, bmp.getHeight() / ratio);
    canvas.drawBitmap(bmp, null, rect, null);

    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    // 把壓縮后的數(shù)據(jù)存放到baos中
    result.compress(Bitmap.CompressFormat.JPEG, 100 ,baos);
    try {  
        FileOutputStream fos = new FileOutputStream(file);  
        fos.write(baos.toByteArray());  
        fos.flush();  
        fos.close();  
    } catch (Exception e) {  
        e.printStackTrace();  
    } 
}
  • 設(shè)置圖片的采樣率,降低圖片像素
public static void compressBitmap(String filePath, File file){
    // 數(shù)值越高沪羔,圖片像素越低
    int inSampleSize = 2;
    BitmapFactory.Options options = new BitmapFactory.Options();
    //采樣率
    options.inSampleSize = inSampleSize;
    Bitmap bitmap = BitmapFactory.decodeFile(filePath, options);  

    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    // 把壓縮后的數(shù)據(jù)存放到baos中
    bitmap.compress(Bitmap.CompressFormat.JPEG, 100 ,baos);
    try {  
        FileOutputStream fos = new FileOutputStream(file);  
        fos.write(baos.toByteArray());  
        fos.flush();  
        fos.close();  
    } catch (Exception e) {  
        e.printStackTrace();  
    } 
}

16.WebViewClient與WebChromeClient的區(qū)別

  1. WebViewClient主要幫助WebView處理各種通知饥伊、請求事件的。
//處理webView的按鍵事件
boolean shouldOverrideKeyEvent(WebView view, KeyEvent event)
//截取url請求蔫饰,在當(dāng)前視圖加載琅豆,避免在跳轉(zhuǎn)到自帶瀏覽器
boolean shouldOverrideUrlLoading(WebView view, String url)
//WebView改變時調(diào)用
void onScaleChanged(WebView view, float oldScale, float newScale)
//對https的請求處理
void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error)
//獲取返回信息授權(quán)
void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm)
//處理報錯信息(API23以后用第二個)
void onReceivedError(WebView view, int errorCode, String description, String failingUrl)
void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error)
//頁面開始載入時調(diào)用
void onPageStarted(WebView view, String url, Bitmap favicon)
//頁面載入結(jié)束時調(diào)用
void onPageFinished(WebView view, String url)
//加載資源時調(diào)用
void onLoadResource(WebView view, String url)
  1. WebChromeClient主要輔助WebView處理Javascript的對話框、網(wǎng)站圖標(biāo)篓吁、網(wǎng)站title茫因、加載進(jìn)度等。
//webview關(guān)閉時調(diào)用
void onCloseWindow(WebView window)
//Js的彈窗
boolean onJsAlert(WebView view, String url, String message, JsResult result)
//Js提示框
boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result)
//Js確認(rèn)框
boolean onJsConfirm(WebView view, String url, String message, JsResult result)
//加載進(jìn)度
void onProgressChanged(WebView view, int newProgress)
//全屏模式(API18以后用第二種)
onShowCustomView(View view, WebChromeClient.CustomViewCallback callback)
onShowCustomView(View view, int requestedOrientation, WebChromeClient.CustomViewCallback callback)

17.URI和URL概念以及區(qū)別

什么是URL杖剪,什么是URI冻押?

  • URL(統(tǒng)一資源定位符):A Uniform Resource Locator thatidentifies the location of an Internet resource as specified by RFC 1738.(用于標(biāo)示網(wǎng)絡(luò)資源的位置)

  • URI(統(tǒng)一資源標(biāo)識符):A Uniform Resource Identifier that identifies an abstract or physical resource, as specified by RFC 2396.(用于標(biāo)示一個抽象或者物理資源)

也就是說URI是以一種抽象的,高層次概念定義統(tǒng)一資源標(biāo)識盛嘿,而URL則是具體的資源標(biāo)識的方式洛巢。URL是一種URI。

Android中的Uri與Java中的URI類
利用URL Scheme打開APP并傳遞數(shù)據(jù)
Android業(yè)務(wù)組件化之URL Scheme使用

18.WebView相關(guān)

Android:這是一份全面 & 詳細(xì)的Webview使用攻略

WebView與js交互

最全面總結(jié) Android WebView與 JS 的交互方式

Q:loadUrl()和evaluateJavascript()區(qū)別
A:后者執(zhí)行不會使頁面刷新次兆,而loadUrl的執(zhí)行則會稿茉。

優(yōu)化

加載提升網(wǎng)頁打開的速度

加載時先加載文本,后加載圖片調(diào)用方式如下
WebSettings settings = wView.getSettings();  
settings.setJavaScriptEnabled(true);  
settings.setBuiltInZoomControls(true);  
settings.setBlockNetworkImage(true);  
wView.setWebChromeClient(new WebChromeClient() {  
       @Override  
       public void onProgressChanged(WebView view, int newProgress) {  
           if (newProgress == 100) {  
              // 網(wǎng)頁加載完成  
              loadDialog.dismiss();  
              wView.getSettings().setBlockNetworkImage(false);  
           } else {  
              // 網(wǎng)頁加載中  
              loadDialog.show();  
           }  
      }  
});  

setBlockNetworkImage (boolean flag)
是否禁止從網(wǎng)絡(luò)(通過http和https URI schemes訪問的資源)下載圖片資源芥炭,默認(rèn)值為false漓库。注意,除非getLoadsImagesAutomatically()返回 true,否則該方法無效园蝠。當(dāng)設(shè)置的值從true變?yōu)閒alse渺蒿,WebView當(dāng)前顯示的內(nèi)容所引用的網(wǎng)絡(luò)圖片資源會自動獲取。

19.WebView.loadUrl使用誤區(qū)

當(dāng)使用loadUrl加載網(wǎng)頁的時候砰琢,有時候會出現(xiàn)調(diào)用系統(tǒng)瀏覽器加載網(wǎng)頁的現(xiàn)象,網(wǎng)上大部分的解決方案是 :

webView.setWebViewClient(new WebViewClient() {
    public boolean shouldOverrideUrlLoading(WebView view, String url)
    { 
        view.loadUrl(url);
        return true;
    }
}

這確實可以達(dá)到在當(dāng)前webview加載網(wǎng)頁的效果良瞧,但是卻做了多余的工作陪汽,以及不合理的返回值。

實際上褥蚯,如果你只需要避免啟動系統(tǒng)瀏覽器來加載頁面的情況挚冤,只需要這么寫就可以了:

webView.setWebViewClient(new WebViewClient());

默認(rèn)返回的super.shouldOverrideUrlLoading(view, url);其實就是false

return true 表示當(dāng)前url即使是重定向url也不會再執(zhí)行赞庶。
return false 表示由系統(tǒng)執(zhí)行url训挡,直到不再執(zhí)行此方法澳骤,即加載完重定向的url。

所以:

view.loadUrl(url);
return true;

等價于

return false;

注意:前者在應(yīng)用場景復(fù)雜的時候澜薄,會出現(xiàn)重定向后無法回退的問題为肮。
比如:

        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
 
            if (!Tools.urlCheck(url)) {
                return true;
            }
            if (!TextUtils.isEmpty(url) && url.endsWith("apk")) {
                Intent viewIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
                startActivity(viewIntent);
            } else {
                if (!WebViewActivity.processCustomUrl(WebActivity.this, url, null, false)) {
                    url = WebViewActivity.addCookie2Url(WebActivity.this, url);
                    // 注意這里!7艟<昭蕖!
                    view.loadUrl(url);
                    return true;
                } else {
                    return true;
                }
            }
            Logcat.dLog("url loading = " + url);
            return super.shouldOverrideUrlLoading(view, url);
         }

若loadUrl執(zhí)行則會重新加載webView,并且不執(zhí)行return true忘分,則由于會執(zhí)行return super.shouldOverrideUrlLoading(view, url)棋枕,導(dǎo)致返回false,則系統(tǒng)也會加載一次妒峦,導(dǎo)致執(zhí)行了兩次重斑,當(dāng)執(zhí)行webview.goBack()返回上一頁時就會一直在當(dāng)前頁打轉(zhuǎn)。

android webview對shouldOverrideUrlLoading的理解肯骇,對于重定向的url

20.ViewGroup為什么不會調(diào)用onDraw

造成這種現(xiàn)象的原因是繼承自LinearLayout窥浪,而LinearLayout這是一個容器,它本身并沒有任何可畫的東西累盗,它是一個透明的控件寒矿,因些并不會觸發(fā)onDraw,但是你現(xiàn)在給LinearLayout設(shè)置一個背景色若债,其實這個背景色不管你設(shè)置成什么顏色符相,系統(tǒng)會認(rèn)為,這個LinearLayout上面有東西可畫了蠢琳,因此會調(diào)用onDraw方法啊终。
我們可以仔細(xì)分析View的源碼,它有一個方法View#draw(Canvas)方法傲须,這里面有兩個地方調(diào)用onDraw蓝牲,它的條件都是:

if (!dirtyOpaque) onDraw(canvas); 

如果dirtyOpaque是true透明的話,onDraw就不會調(diào)用泰讽。
View還提供了一個重要的方法:setWillNotDraw例衍,setFlags(WILL_NOT_DRAW, DRAW_MASK);相于調(diào)用了setWillNotDraw(true),它就認(rèn)為是透明的了已卸。如果我們想要重寫onDraw佛玄,就需要調(diào)用setWillNotDraw(false)

  public void setWillNotDraw(boolean willNotDraw) {
     setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
 
  }

所以累澡,

  1. ViewGroup默認(rèn)情況下梦抢,會被設(shè)置成WILL_NOT_DRAW,這是從性能考慮愧哟,這樣一來奥吩,onDraw就不會被調(diào)用了哼蛆。
  2. 如果我們要重要一個ViweGrouponDraw方法,有兩種方法:
    • 在構(gòu)造函數(shù)里面霞赫,給其設(shè)置一個顏色腮介,如#00000000。
    • 在構(gòu)造函數(shù)里面绩脆,調(diào)用setWillNotDraw(false)萤厅,去掉WILL_NOT_DRAW的flag

21.getWidth()方法和getMeasureWidth()區(qū)別

  • 首先getMeasureWidth()方法在measure()過程結(jié)束后就可以獲取到了靴迫,而getWidth()方法要在layout()過程結(jié)束后才能獲取到惕味。
  • 另外,getMeasureWidth()方法中的值是通過setMeasuredDimension()方法來進(jìn)行設(shè)置的玉锌,而getWidth()方法中的值則是通過視圖右邊的坐標(biāo)減去左邊的坐標(biāo)計算出來的名挥。

22. Binder

1)Binder IPC 通信過程
  • 首先 Binder 驅(qū)動在內(nèi)核空間創(chuàng)建一個數(shù)據(jù)接收緩存區(qū);
  • 接著在內(nèi)核空間開辟一塊內(nèi)核緩存區(qū)主守,建立內(nèi)核緩存區(qū)和內(nèi)核中數(shù)據(jù)接收緩存區(qū)之間的映射關(guān)系禀倔,以及內(nèi)核中數(shù)據(jù)接收緩存區(qū)和接收進(jìn)程用戶空間地址的映射關(guān)系;
  • 發(fā)送方進(jìn)程通過系統(tǒng)調(diào)用copyfromuser()將數(shù)據(jù) copy 到內(nèi)核中的數(shù)據(jù)接收緩存區(qū)参淫,由于內(nèi)核緩存區(qū)和數(shù)據(jù)接收緩存區(qū)以及接收進(jìn)程的用戶空間存在內(nèi)存映射救湖,因此也就相當(dāng)于把數(shù)據(jù)發(fā)送到了接收進(jìn)程的用戶空間,這樣便完成了一次進(jìn)程間的通信涎才。
2)Binder通訊模型

Binder是基于C/S架構(gòu)的鞋既,其中定義了4個角色:Client、Server耍铜、Binder驅(qū)動和ServiceManager邑闺。

  • Binder驅(qū)動:類似網(wǎng)絡(luò)通信中的路由器,負(fù)責(zé)將Client的請求轉(zhuǎn)發(fā)到具體的Server中執(zhí)行棕兼,并將Server返回的數(shù)據(jù)傳回給Client陡舅。
  • ServiceManager:類似網(wǎng)絡(luò)通信中的DNS服務(wù)器,負(fù)責(zé)將Client請求的Binder描述符轉(zhuǎn)化為具體的Server地址伴挚,以便Binder驅(qū)動能夠轉(zhuǎn)發(fā)給具體的Server靶衍。Server如需提供Binder服務(wù),需要向ServiceManager注冊茎芋。

具體的通訊過程

  1. Server向ServiceManager注冊颅眶。Server通過Binder驅(qū)動向ServiceManager注冊,聲明可以對外提供服務(wù)败徊。ServiceManager中會保留一份映射表帚呼。
  2. Client向ServiceManager請求Server的Binder引用掏缎。Client想要請求Server的數(shù)據(jù)時皱蹦,需要先通過Binder驅(qū)動向ServiceManager請求Server的Binder引用(代理對象)煤杀。
  3. 向具體的Server發(fā)送請求。Client拿到這個Binder代理對象后沪哺,就可以通過Binder驅(qū)動和Server進(jìn)行通信了沈自。
  4. Server返回結(jié)果。Server響應(yīng)請求后辜妓,需要再次通過Binder驅(qū)動將結(jié)果返回給Client枯途。

Q: ServiceManager是一個單獨的進(jìn)程,那么Server與ServiceManager通訊是靠什么呢籍滴?

A: 當(dāng)Android系統(tǒng)啟動后酪夷,會創(chuàng)建一個名稱為servicemanager的進(jìn)程,這個進(jìn)程通過一個約定的命令BINDERSETCONTEXT_MGR向Binder驅(qū)動注冊孽惰,申請成為為ServiceManager晚岭,Binder驅(qū)動會自動為ServiceManager創(chuàng)建一個Binder實體。并且這個Binder實體的引用在所有的Client中都為0勋功,也就說各個Client通過這個0號引用就可以和ServiceManager進(jìn)行通信坦报。Server通過0號引用向ServiceManager進(jìn)行注冊,Client通過0號引用就可以獲取到要通信的Server的Binder引用狂鞋。

23.Fragment的懶加載實現(xiàn)

Fragment可見狀態(tài)改變時會被調(diào)用setUserVisibleHint()方法片择,可以通過復(fù)寫該方法實現(xiàn)Fragment的懶加載,但需要注意該方法可能在onVIewCreated之前調(diào)用骚揍,需要確保界面已經(jīng)初始化完成的情況下再去加載數(shù)據(jù)字管,避免空指針。
當(dāng)fragment被用戶可見時疏咐,setUserVisibleHint()會調(diào)用且傳入true值纤掸,當(dāng)fragment不被用戶可見時,setUserVisibleHint()則得到false值浑塞。而在傳統(tǒng)的fragment生命周期里也看不到這個函數(shù)借跪。

http://www.reibang.com/p/cf1f4104de78

24.Retrofit的實現(xiàn)與原理

Retrofit采用動態(tài)代理,創(chuàng)建聲明service接口的實現(xiàn)對象酌壕。當(dāng)我們調(diào)用service的方法時候會執(zhí)行InvocationHandler的invoke方法掏愁。在這方法中:首先,通過method把它轉(zhuǎn)換成ServiceMethod卵牍,該類是對聲明方法的解析果港,可以進(jìn)一步將設(shè)定參數(shù)變成Request ;然后糊昙,通過serviceMethod, args獲取到okHttpCall 對象辛掠,實際調(diào)用okhttp的網(wǎng)絡(luò)請求方法就在該類中,并且會使用serviceMethod中的responseConverter對ResponseBody轉(zhuǎn)化;最后萝衩,再把okHttpCall進(jìn)一步封裝成聲明的返回對象(默認(rèn)是ExecutorCallbackCall,將原本call的回調(diào)轉(zhuǎn)發(fā)至UI線程)回挽。

Retrofit2使用詳解及從源碼中解析原理
Retrofit2 完全解析 探索與okhttp之間的關(guān)系

25.View的加載流程

  1. 通過Activity的setContentView方法間接調(diào)用Phonewindow的setContentView(),在PhoneWindow中通過getLayoutInflate()得到LayoutInflate對象猩谊。

  2. 通過LayoutInflate對象去加載View千劈,主要步驟是

  • 通過xml的Pull方式去解析xml布局文件,獲取xml信息牌捷,并保存緩存信息墙牌,因為這些數(shù)據(jù)是靜態(tài)不變的

  • 根據(jù)xml的tag標(biāo)簽通過反射創(chuàng)建View逐層構(gòu)建View

  • 遞歸構(gòu)建其中的子View,并將子View添加到父ViewGroup中暗甥。

View的繪制流程

View的繪制從ActivityThread類中Handler的處理RESUME_ACTIVITY事件開始喜滨,在執(zhí)行performResumeActivity之后,創(chuàng)建Window以及DecorView并調(diào)用WindowManager的addView方法添加到屏幕上撤防,addView又調(diào)用ViewRootImpl的setView方法鸿市,最終執(zhí)行performTraversals方法,依次執(zhí)行performMeasure即碗,performLayout焰情,performDraw。也就是view繪制的三大過程剥懒。

26.OnTouchListener内舟,onTouchEvent,onClickListener執(zhí)行順序

結(jié)果:首先執(zhí)行OnTouchListener初橘,之后為onTouchEvent验游,最后才執(zhí)行onClickListener內(nèi)的方法,至于為什么OnTouchListener和onTouchEvent執(zhí)行了兩次保檐,是因為在DOWN和UP時兩個方法都被調(diào)用耕蝉,至于onClickListener則只在UP的時候調(diào)用。

與事件分發(fā)相關(guān)聯(lián)的三個方法分別為dispatchTouchEvent夜只,onInterceptTouchEvent垒在,onTouchEvent,直接去看View的dispatchTouchEvent方法:

public boolean dispatchTouchEvent(MotionEvent event) {
        ......
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }
        if (!result && onTouchEvent(event)) {
            result = true;
        }
        ......
        return result;
    }

我們先看ListenerInfo 扔亥,它是View的一個內(nèi)部靜態(tài)類场躯。

static class ListenerInfo {

        protected OnFocusChangeListener mOnFocusChangeListener;
        private ArrayList<OnLayoutChangeListener> mOnLayoutChangeListeners;
        protected OnScrollChangeListener mOnScrollChangeListener;
        private CopyOnWriteArrayList<OnAttachStateChangeListener> mOnAttachStateChangeListeners;
        public OnClickListener mOnClickListener;
        protected OnLongClickListener mOnLongClickListener;
        protected OnContextClickListener mOnContextClickListener;
        protected OnCreateContextMenuListener mOnCreateContextMenuListener;
        ......
 }

再看dispatchTouchEvent方法,只有OnTouchListener返回false旅挤,onTouchEvent才可能被觸發(fā)踢关。

繼續(xù)看onTouchEvent代碼:

public boolean onTouchEvent(MotionEvent event) {
        ......
        switch (action) {
             case MotionEvent.ACTION_UP:
                  ......
                  performClick();
                  ......
                  break;
        ......
    }

public boolean performClick() {
        final boolean result;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }
        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
        return result;
    }

在View的onTouchEvent方法中,如果判斷事件為MotionEvent.ACTION_UP時粘茄,則會調(diào)用performClick签舞,而在performClick中則會回調(diào)mOnClickListener的onClick方法,即點擊事件被回調(diào),同時直接返回true儒搭。

27.okHttp工作流程

OkHttpClient通過newCall可以將一個Request構(gòu)建成一個Call撒会,Call表示準(zhǔn)備被執(zhí)行的請求。Call調(diào)用executed或enqueue會調(diào)用Dispatcher對應(yīng)的方法在當(dāng)前線程或者異步開始執(zhí)行請求师妙,經(jīng)過RealInterceptorChain獲得最終結(jié)果,RealInterceptorChain是一個攔截器鏈屹培,其中依次包含以下攔截器:

  • 自定義的攔截器
  • retryAndFollowUpInterceptor 請求失敗重試
  • BridgeInterceptor 為請求添加請求頭默穴,為響應(yīng)添加響應(yīng)頭
  • CacheInterceptor 緩存get請求
  • ConnectInterceptor 連接相關(guān)的攔截器,分配一個Connection和HttpCodec為最終的請求做準(zhǔn)備
  • CallServerInterceptor 該攔截器就是利用HttpCodec完成最終請求的發(fā)送

28.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末褪秀,一起剝皮案震驚了整個濱河市蓄诽,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌媒吗,老刑警劉巖仑氛,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異闸英,居然都是意外死亡锯岖,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進(jìn)店門甫何,熙熙樓的掌柜王于貴愁眉苦臉地迎上來出吹,“玉大人,你說我怎么就攤上這事辙喂〈防危” “怎么了?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵巍耗,是天一觀的道長秋麸。 經(jīng)常有香客問我,道長炬太,這世上最難降的妖魔是什么灸蟆? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮亲族,結(jié)果婚禮上次乓,老公的妹妹穿的比我還像新娘。我一直安慰自己孽水,他們只是感情好票腰,可當(dāng)我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著女气,像睡著了一般杏慰。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天缘滥,我揣著相機與錄音轰胁,去河邊找鬼。 笑死朝扼,一個胖子當(dāng)著我的面吹牛赃阀,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播擎颖,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼榛斯,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了搂捧?” 一聲冷哼從身側(cè)響起驮俗,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎允跑,沒想到半個月后王凑,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡聋丝,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年索烹,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片弱睦。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡术荤,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出每篷,到底是詐尸還是另有隱情瓣戚,我是刑警寧澤,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布焦读,位于F島的核電站子库,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏矗晃。R本人自食惡果不足惜仑嗅,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望张症。 院中可真熱鬧仓技,春花似錦、人聲如沸俗他。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽兆衅。三九已至地沮,卻和暖如春嗜浮,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背摩疑。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工危融, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人雷袋。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓吉殃,卻偏偏與公主長得像,于是被迫代替她去往敵國和親楷怒。 傳聞我的和親對象是個殘疾皇子蛋勺,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內(nèi)容