Android研究的一些總結

CSDN (Chinese Software Developer Network) 創立於1999年,是中國最大的IT社區和服務平臺,爲中國的軟件開發者和IT從業者提供知識傳播、職業發展、軟件開發等全生命週期服務,滿足他們在職業發展中學習及共享知識和信息、建立職業發展社交圈、通過軟件開發實現技術商業化等剛性需求。擁有超過3000萬註冊會員(其中活躍會員800萬)、50萬註冊企業及合作伙伴。
旗下擁有:全球最大中文IT技術社區:csdn、權威IT專業技術期刊:《程序員》雜誌、IT人力資源服務:科銳福克斯、IT技術學習平臺:樂知教育、代碼託管+社交編程平臺:code、移動開發工具和服務聚合平臺:mobilehub、IT專屬求職網站:job、中文軟件外包和項目交易平臺:csto、程序員深度交流社區:iteye、中國最大技術管理者平臺:CTO俱樂部、雲計算產業人士沙龍:雲計算俱樂部、面向移動開發者的技術組織:移動開發者俱樂部、面向全國大學生的技術組織:高校俱樂部。

Activity、Fragment相關

1、Activity正常和異常情況下的生命週期

正常情況如下圖:

這裏寫圖片描述

tips:舊Activity的pause、stop完成後, 新Activity纔會執行create - start……,不要在pause、stop中執行耗時操作;如果新Activity使用透明主題,舊Activity不會執行onStop方法,重新返回舊Activity時也不會執行restart、start,只會執行resume,可以理解爲,透明主題的Activity等同於dialog或popuoWindow;

異常情況下:
① 內存不足導致Activity被殺死,然後再恢復;
② 配置改變導致Activity被殺死(比如屏幕方向改變),然後重新恢復;

這兩種異常情況下,Activity被殺死的回調順序是:
onPause和onSaveInstanceState(這兩個方法調用沒有固定順序) - onstop - onDestroy;

Activity重新恢復的回調順序是:
onCreate - onStart - onResume和onRestoreInstanceState(這兩個方法沒有固定順序)

對於第二種異常情況,可以通過在manifest中設置Activity標籤下的configChanges屬性,來設置其是否重啓Activity。比如設置”Android:configChanges = “orientation”後,屏幕方向改變時,不會重啓Activity,而是回調Activity的onConfigurationChanged()方法。

2、Activity啓動模式

Activity的啓動特性有兩種:一種是“是否重新創建”,一種“放在哪個任務棧”。

在Manifest中,LaunchMode屬性控制Activity“是否重新創建”,TaskAffinity屬性則是控制“任務棧”的,這兩個屬性可以同時生效。

在代碼中,啓動Activity時,通過intent.addFlag()來添加各種Flag,每一種Flag都包含了Activity“是否重新創建”和“放在哪個任務棧”兩個屬性,方便開發者設置兩種屬性。通過該方法設置模式會覆蓋manifest中的模式。

1、LaunchMode
standard:
標準模式,這是系統默認模式。每次啓動Activity都會創建一個新的實例。

singleTop:
棧頂複用模式。如果Activity已經位於棧頂,則不會調用onCreate方法重建,只會調用onNewIntent方法啓動;如果Acitvity不在棧頂,則重新創建;

singleTask:
棧內複用模式。通過一些方法可以指定Activity所在的棧(比如taskAffinity屬性、FLAG等),如果Activity已經存在於指定棧中,則不會重建,只會調用onNewIntent方法,並銷燬該Activity上面的Activity,讓其處於棧頂;如果不存在指定棧或指定棧中不存在該Activity,則在指定棧中重新創建;

singleInstance:
單例模式。如果Activity不存在(任何棧中都沒有),就爲該Activity創建一個棧,並創建該Activity;如果已經存在,就調用onNewIntent方法;在這個Activity中啓動其它Activity,其它Acitivity不會在該棧中(具體在哪個棧取決於設置),該Acitivty是獨佔一個棧的。

3、Activity啓動過程

1、各種重載的startActivity方法最終都會調用startActivityForResult方法,這個方法經過一些列的調用,最終會調用AppLicationThread的scheduleLaunchActivity方法;

2、AppLicationThread是ActivityThread的內部類,scheduleLaunchActivity方法很簡單,就是啓用Handler發送一個消息。最終Handler在handMessage方法中,會調用ActivityThread類的handleLaunchActivity方法,這個方法最終又會調用performLaunchActivity方法;

3、performLaunchActivity裏會做4件事:通過類加載器加載Activity並創建對象、獲取AppLication對象、創建ContextImpl對象、調用Activity的attach方法將三者進行綁定。attach方法中就會完成Window的創建、綁定等操作;

4、在ActivityThread類裏,就會通過調用Instrumentation的callActivityOnCreate、callActivityOnResume……等方法,這樣Activity就會接收到生命週期的回調。

4、Fragment生命週期

Fragment生命週期:
1. 啓動:onAttach、onCreate、onCreateView、onActivityCreated、onStart、onResume
2. 暫停、停止:onPause、onStop
3. 銷燬:onPause、onStop、onDestroyView、onDestroy、onDetach

Activity 和 Fragment 生命週期的交互:
1. 因爲Fragment是依附於Activity的,因此所有啓動類動作都是Activity先執行,Fragment再執行,暫停、銷燬類動作則是Fragment先執行
2. 執行Activity的onCreate後,執行Fragment的onAttach ~ onActivityCreated
3. 執行Activity的onStart後,執行Fragment的onStart(onResume同理)
4. 執行Fragment的onPause後,執行Activity的onPause(onStop同理)
5. 執行Fragment的onDestroyView、onDestroy、onDetach後,執行Activity的onDestroy

5、Fragment和Activity通信

1、通過接口,自定Listener;
2、Activity和拿到Fragment的實例,Fragment也可以通過getActivity方法得到Activity的實例,互相調用public方法即可;
3、通過eventBus或廣播;

View、Window相關

1、Window的工作原理

Window表示一個窗口的概念,負責顯示畫面、UI交互等。它的唯一實現類就是PhoneWindow,它有一個內部類Callback,負責傳遞Window的各種通知(Activity、Dialog實現該接口,就可以接受到touch通知了)。Window會綁定一個WindowManager,這個WindowManager負責一些具體工作的實現,比如添加view。

WindowManager是一個接口,同時還繼承了ViewManager接口(這個接口聲明瞭添加、刪除view等方法)。WindowManagerImpl是實現類,負責大部分具體實現,但是添加、刪除view等方法是委託給WindowManagerGlobal完成,最終它會新建一個ViewImpl,把view添加到ViewImpl中去,後面的測量佈局繪製流程也會有這個ViewImlp發起。

Activity的attach方法中會new出PhoneWindow對象,然後通過getSystemService得到WindowManager對象,然後將PhoneWindow和WindowManager綁定,這樣,Activity就和WindowManager建立了綁定,後面的很多實現就是依託WindowManager來完成的。

Activity的setContentView就是調用PhoneWindow的setContentView方法,這裏面又通過一層一層的方法調用,這裏就不一一列出每個方法,只說明整體邏輯。首先new一個DecorView,這是PhoneWindow的內部類,繼承自FrameLayout。然後根據Activity設置的主題加載不同的佈局文件(系統中有很多主題的佈局文件),然後找到id爲R.id.content的控件(所有主題的佈局文件中都有這個FrameLayout,id命名也是一致的),是一個FrameLayout,然後將Activity對應的layout文件添加到這個FrameLayout中。然後,在Activity的onResume方法中就會調用DecorView的setVisible讓其顯示,這也是爲什麼在onResume之後我們就能看到View了。

setContentView完成後,ViewImpl就會調用perfomeMeasure、performLayout、performDraw方法啓動View的測量佈局繪製流程。

2、View的測量佈局繪製流程

測量流程由ViewImpl調用performMeasure方法啓動,這個方法內部會調用最底層view的measure方法,該方法首先根據父控件(ViewImpl)建議的尺寸,計算出合適的尺寸,然後就調用onMeasure方法。onMeasure中首先會調用measureChildren方法遍歷調用子控件的measure方法,把所有子控件的尺寸確定後,最終再確定自己的尺寸。

每個view的measure都是由父控件調用,父控件會把建議尺寸傳過來,view根據父控件建議的尺寸,調用onMeasure方法,onMeasure方法中先遍歷子控件的尺寸,最後再根據子控件的尺寸確定自身的尺寸。可以看出,measure方法是發起測量流程,由父控件發起,onMeasure方法就是收到測量通知,開始進行實際測量。

佈局流程由ViewImpl調用performLayout方法啓動,該方法會調用最底層view的layout方法,並傳入left、top、right、bottom座標值,確定view的位置。layout方法最後又會調用onLayout方法,onLayout方法的作用是遍歷子控件,確定每個子控件的left、top、right、bottom位置,然後調用子控件的layout方法。onLayout由每個控件自己重寫(因爲每個控件的佈局規則不一樣)。

同樣,父控件根據自身的佈局規則和子控件的尺寸,確定每個子控件的位置,然後就調用子控件的layout方法發起佈局流程。layout方法確定了自己在父控件中的位置,而onLayout方法則是完成自身內部的佈局(子控件的佈局)。

繪製流程由ViewImpl調用performDraw方法啓動,該方法會調用最底層view的draw方法。draw方法做一些初始化操作,接着就會調用onDraw方法繪製自身,自身繪製完成後,就調用dispathDraw方法,遍歷子控件,調用子控件的draw方法,啓動子控件的繪製流程。

可以看出如下規則:
1、measure()、layout()、draw()是啓動流程、初始化數據,每個控件都是一樣的,所以在基類View中,代碼共用;

2、onMeasure()、onLayout()、onDraw()是具體的測量、佈局、繪製工作,每個控件不一樣,所以各個控件自己實現。凡是onXXX開頭的方法就是接收到通知,開始自身的繪製工作,而自己的子控件當然也是屬於自己的,所以還需要遍歷子控件,啓動子控件的繪製流程;

4、繪製子控件的過程中,由於每個控件measureChildren()、dispatchDraw()的功能差不多,所以該實現在ViewGroup中,所有子類共用。但是每個控件的佈局規則肯定不一樣,所以沒有layoutChildren方法,而是自己在onLayout中實現。

3、事件分發

在WindowManagerGlobal添加view的過程中,ViewRootImpl會想WMS註冊WindowInputEventReceiver,用於接收點擊事件。系統接收到點擊事件後,通知ViewRootImpl。當接收到點擊事件後,通過一系列的操作,最終會調用DecorView的dispatchTouchEvent方法,該方法中,會調用Activity的dispatchTouchEvent方法。同時,它集成自FrameLayout,自然也會啓動View的時間分發流程。

這裏面有三個重要的方法,dispatchTouchEvent方法是負責第一時間接收事件的,onInterceptTouchEvent方法是判斷是否攔截事件的,最後由onTouchEvent方法來處理事件。其中,由於View沒有子控件了,所以View是沒有onInterceptTouchEvent方法的(該方法位於ViewGroup中),所以,View的時間流程少了一個環節,其它都是一樣的。

分發流程如下圖:

這裏寫圖片描述

有幾個注意的點:
1、如果onInterceptTouchEvent返回true,確定消費,那後面就不會再回調onInterceptTouchEvent方法了,直接給onTouchEvent,如圖虛線①那樣。如果點擊處沒有子view,也是這樣;
2、如果子控件的onTouchEvent在action_down時返回false,這個false會傳給dispatchTouchEvent,通過dispatchTouchEvent又傳給了父控件的onInterceptTouchEvent,所以後面的move、up等事件,在onInterceptTouchEvent方法中也會返回true,讓父控件消費;
3、如果子控件的onTouchEvent在action_down時返回true,在move、up事件時返回false,是不會傳給父控件的,直接拋棄掉;

Android通信、消息機制、線程相關

1、線程的基本知識

1、 線程的狀態
創建:new出線程對象,就處於創建狀態;
就緒:執行start()方法,就處於就緒狀態。處於就緒狀態只是表示它準備好了,至於何時開始運行,取決於JVM的調度。已經處於就緒狀態的線程,調用start()方法會報錯;
運行:當CPU調度到此線程,開始執行裏面的run()方法,就處於運行狀態;
阻塞:正在運行的時候被暫停,比如sleep、wait等方法,都會導致阻塞。阻塞結束後會自動回到就緒狀態,等待CPU調度重新進入運行狀態;
死亡:run()方法執行完畢,或調用了stop()方法,就死亡了,無法通過start()再次就緒;

2、線程的sleep()
Thread的靜態方法sleep(long millis),讓此線進入阻塞狀態long毫秒,阻塞結束後重新等待CPU的調度;

3、join()方法
在線程A中,調用線程B的join()方法,即b.join(),那麼A將會等待B執行完了纔會繼續向下執行;

4、線程讓步yield()方法
Thread的靜態方法yield(),讓此線程放棄執行機會,進入就緒狀態,讓CPU重新調度線程。CPU重新調度線程時,只會調度優先級大於等於此線程的線程,如果沒有,就重新調度此線程;

5、線程同步
可以使用對象obj或類.class來作爲鎖,鎖定一塊代碼或一個方法。鎖的意思就是,obj作爲鎖的這段期間,不能再被當作鎖。這樣,其它線程想要執行該方法、或其它使用obj作爲鎖的方法時,需要獲取obj作爲鎖,而在上一個鎖釋放錢,obj是不能再作爲鎖的,所以這個方法無法立即執行。通過“鎖的唯一性”保證了被鎖的代碼不會同時執行。

死鎖,就是線程1使用了a作爲鎖,線程2使用了b作爲鎖。然後線程1需要執行某段代碼,該代碼需要b作爲鎖,同時,線程2需要執行某段代碼需要a作爲鎖。導致兩個線程都無法執行下去,形成死鎖。

6、線程間通信
爲什麼需要線程間通信?當兩個線程要同時對同一個對象進行操作時,才需要協調運行,需要協調運行的方法/代碼塊就是需要加鎖同步的,很顯然,這個對象就是鎖。

線程間通信用到的方法就是Object類的三個方法wait()、notify()、notifyall(),這三個方法的調用者是鎖(也就是那個被當作鎖的對象)。線程1使用“鎖”調用wait()方法會使當前線程等待,同時鎖被釋放;線程2使用“鎖”調用notify()方法會喚醒線程1。

7、線程池
查看博客

8、Callable和Futrue
Callable接口只聲明瞭一個call方法,主要用於把Callable對象提交到線程池任務中,然後線程池中的新線程會調用Callable對象的call方法。

Callable和Futrue都有一個泛型的參數,配合使用時,這兩個泛型參數類型必須一致。Callable泛型參數用於聲明任務返回的結果,Futrue的泛型參數就是用於接收這個結果,所以類型肯定是要一致的。

2、對Handler、Looper、MessageQueue的理解

一個線程唯一對應一個Looper和MessageQueue,它是如何實現做到的呢?Looper通過prepare方法實例化、並創建一個MessageQueue,然後以該線程爲key,Looper爲value,存入靜態變量ThreadLocal中。ThreadLocal是一個負責存儲數據的類,其原理是通過一個數組,存儲key-value數據,在這裏,key就是線程,value是Looper。由於是static修飾的,所以全局共用。在同一個線程內重複調用Looper.prepare方法時,會判斷TreadLocal中是否已經存過當前線程的key-value,如果已經存過,就會報錯,通過這種方式,保證了“一個線程唯一對應一個Looper和MessageQueue”。

Handler的構造方法中,會獲取到當前線程的Looper,MessageQueue是Looper的成員變量,自然Handler也就獲取到了當前線程對應的MessageQueue。

Handler發送消息就是調用MessageQueue的插入方法,把message插入到消息隊列中。至於插入的具體邏輯,要先簡單介紹一下Message對象,Message有兩個核心成員變量,一個是Handler,一個是next。每條消息都綁定着一個Handler,這樣Looper才知道把這條消息交給哪個Handler處理;每個消息都有一個next,這個是用來標識自己的“下一個”,這樣,多個消息就形成了首位項鍊的鏈表,MessageQueue就是管理着這個消息鏈表。插入消息的過程,就是把這個消息插入到鏈表中的過程,具體的插入邏輯會根據當前消息的when參數,遍歷所有消息,按照時間首尾相連。

這樣,插入消息就分析完成了,接下來就分析如何處理這些消息。

通過調用Looper.loop方法,該線程的Looper就會進入一個無限循環,調用MessageQueue的next()方法讀取消息。這裏分三種情況,如果成功取到消息,就交給對應的Handler處理;如果next()返回的消息爲null,Looper就退出死循環,只有人爲設置退出,這裏纔會返回null;沒有消息的時候並不會返回null,而是阻塞在next方法中,知道有新消息爲止。

這裏有兩個阻塞需要解釋下,第一個阻塞就是MessageQueue中的next方法,這裏的阻塞使用的是Linux管道流機制,只是讓next方法沉睡,設定一個喚醒時間,不影響後面的執行,具體原理好像是本地c++代碼寫的。第二個阻塞是Looper在for循環中調用MessageQueue的next方法,next一直沒有返回,這裏是真阻塞,就是後面的代碼都無法執行。但是爲什麼不會報ANR呢?因爲Android系統就是由消息驅動的,比如接收到用戶點擊,就發一條消息通知View進行處理,Android系統內部無時不刻都在進行消息處理,ANR的真正含義是:處理某個消息超過了約定時間,或下一條消息到了時間遲遲無法處理。

3、對HandlerThread的理解

HandlerThread集成Thread,在run方法中完成了Looper的初始化,並調用了loop()方法開啓循環。跟普通Thread不同的是,這個run方法會在loop()方法中死循環,所以這個線程的run方法不會執行完成,除非認爲停止,該線程不會銷燬。

HandlerThread創建、啓動完成後,需要創建一個Handler實例與之關聯,並調用Handler的post(Runnable r)方法發送消息。

因爲是通過Handler h = new Handler(HandlerThread.getLooper())創建Handler,這個Handler發送的消息也是由HandlerThread線程裏的Looper處理。

post(Runnable r)方法會通過obtain方法獲取一個msg,把Runnable放入msg中,然後把這個msg插入消息隊列,最終HandlerThread的Looper會獲取到這個消息,調用Handler的dispatchHandMessage()方法來處理msg。該方法中會做判斷,如果msg中的callable字段爲空,就執行handMessage方法(我們自己實現的);如果不爲空,就調用該callable的run方法(注意,不是start)。

可以看出,最終handMessage方法是在HandlerThread線程中執行的,所以可以執行耗時操作。

4、對ThreadLocal的理解

ThreadLocal有一個內部類ThreadLocalMap,ThreadLocalMap又有一個內部類Entry。

Thread持有一個ThreadLocalMap對象,ThreadLocalMap又持有一個Entyr[]數組,這個Entyry是存儲數據的,等於是每個Thread都對應着一個Entyr[]數組來存儲數據。

存數據是通過ThreadLocalMap的set(ThreadLocal local,Object value)方法,等於說每個value都綁定了一個ThreadLocal,這樣一來,每個數據都綁定了一個Thread和一個ThreadLocal。

這樣設計可能有兩個用處,一個是“對於多線程,數據按Thread分類存取,維護更加方便”,一個是“對於單線程,數據又按ThreadLocal分類存取,維護方便”。可以認爲ThreadLocal是一個維護數據的工具類、管理類。

5、對AsynTask的理解

AsynTask類中有兩個static修飾的線程池,一個負責存儲、封裝runnable,一個負責執行runnable。由於是靜態的,也就是說該app裏所有AsynTask共用這兩個線程池。

創建AsynTask對象時,構造方法中就會創建一個WorkerRunnable對象(實現了Callable接口),把doInbackground()方法中的耗時邏輯封裝到call()方法中(最終在線程池中會調用該call方法)。同事創建一個FutureTask對象,與WorkerRunnable綁定,用於接收WorkRunnable的執行結果。

調用AsyncTask的excute方法後,首先會回調onPreExecute()方法,這個方法是由我們自己實現的,做一些初始化操作。然後把執行參數封裝到WorkerRunnable中,把FutureTask插入存儲線程池。

存儲線程池採用數組隊列ArrayQueue存儲Runnable,插入的時候,並不是直接把傳進來的Runnable/WorkerRunnable參數存入隊列,而是新建一個Runnable對象,對原始的參數進行一定的封裝,加入一些執行邏輯(比如執行完了需要調用一個方法執行下一個),然後再把新建的Runnable添加到隊列中。

添加到完成後就會開始執行:取出“當前”任務,使用“執行線程池”的execute()方法來執行這個任務,也就是說,這個“存儲線程池”只是負責封裝runnable,加一些控制邏輯,並存儲。真正執行是交給另外一個“執行線程池”來負責,一個任務執行完成後,會調用scheduleNext()方法,這個方法就是“存儲線程池”封裝的時候加的,這個方法的作用就是從“存儲線程池”中取出下一個任務,調用“執行線程池”來執行。

執行線程池執行任務時,是new出n個線程(取決於線程池設置),啓動這些線程,調用任務的run()方法,而不是start()。

前面已經說過,doInbackground方法被封裝到WorkRunnable的call方法中,在call方法中,執行完成後,就會調用AysncTask類的postResult方法通知結果,這個方法裏就是使用Handler來通知調用處。

Java語法相關

1、java是值傳遞還是引用傳遞

答案是值傳遞。對於基本類型,傳遞的是值本身,對於對象,傳遞的是地址的值。都是值,所以是值傳遞。

比如,先聲明 int i = 3; Object obj = new Object();

對於int y = i;是把3賦值給y,跟i其實毫無關係;
對於Object obj2 = obj;是把new出來的Object對象的地址賦值給obj2,調用obj2對Object進行改變,這個對象就是是真正改變了,obj還是指向這個Object對象。

2、final和static

對於static,一句話就可以說明:static聲明的東西(成員變量、方法、代碼塊)都是屬於類的,跟對象無關。

static修飾的成員變量,在類加載的時候就完成了內存分配。而普通成員變量,是在創建對象的時候才分配。
static修飾的方法,是屬於類的,所以該方法中不能有super、this關鍵字,方法裏也只能訪問屬於類的方法、變量。因爲它是屬於類的,所以不能是抽象的abstract。子類中也使用static修飾的同名方法,是屬於子類的靜態方法,不屬於類的,不算重寫。
static修飾的代碼塊,在類加載的時候初始化自動執行,如果有多個,按聲明的先後順序執行。

對於final,它的主要作用就是告訴jvm它不會改變,有助於jvm做出更好的決定,提高效率。
final修飾的變量,只能賦值一次,要麼在聲明時賦值,要麼在構造方法中。如果編譯器知道字段的值不會更改,那麼它能安全地在寄存器中高速緩存該值,提高效率。對於基本類型來說,它的值就不可改變了,對於對象來說,它指向的地址不能再改變(不影響該對象本身的變化)。
final修飾的方法和類不能被重寫、不能被繼承。

3、jvm內存分配和垃圾回收

見博客。

4、強引用(strong)、軟引用(soft)、弱引用(weak)

1、默認都是強引用,GC時,只要該對象從root可達,就不會回收;
2、軟引用在普通情況下也不會回收,只有到內存不足時纔會清理軟引用;
3、弱引用在GC時就會被直接清理。由於GC頻率並不高,所以大部分情況下,該對象是使用週期內都不會被清理的。

5、equals和hashcode

equals方法的默認實現是比較兩個對象的地址,即“return obj1 == obj2”。但是大多數時候我們都是希望它比較對象內容,所以如果有需要的話,一般會重寫equals方法;

java中有兩種集合,一種是list,有序、可重複的;一種是set,無序、不可重複。對於不可重複的集合,新添加元素時就要判斷是否相同,如果挨個使用equals方法進行比較,效率會非常低,所以有了hashcode。每個對象都有hashcode值,就像一個“索引”,插入新元素時,根據新元素的“索引”獲取對應的value,如果爲null,就說明沒有重複,如果不爲null,就說明“索引”重複了。注意,索引是有可能重複的,此時再通過equals來判斷,這樣效率就大大提高了。

java對於equals和hashcode有規範,如果equals相同,hashcode就必須相同,這樣的數據在插入集合時效率才更高。

6、java集合、map

1、集合
java的集合基類是Collection接口,有兩個子類:List接口、Set接口。List有序可重複,Set無需不可重複。

List有兩個子類:ArrayList實現類、Vector實現類。ArrayList非同步,Vector是同步的。

Set有一個子類:HashSet實現類。

ArrayList內部是由數組存儲,當添加元素時,會調用其grow()方法“增加數組長度”,具體方法就是新建一個長度+1的數組,把舊數組中的內容copy過來,然後再添加。Vector與ArrayList一樣,只是在幾乎所有方法上都加了synchronized修飾。

HashSet內部是由HashMap存儲數據,利用了HashMap元素不可重複的特點。由於HashMap是非同步的,所以HashSet也是非同步的。

2、map
Map是一個接口,有一個內部類Entry接口,這個Entry可傳入兩個泛型參數,分別對應key-value。Map有兩個實現類:HashMap和HashTable。HashMap是非同步的,HashTable是同步的,兩個類的實現是一樣的,只是HashTable裏面幾乎把所有方法都加了synchronized修飾。

HashMap添加元素時,是通過key得到對應的hashcode,然後以hashcode爲key,把數據存入Entry中。

7、字符相關

字符編碼相關參考博客。

StringBuffer是線程安全的。

網絡

TCP/IP是個協議組,可分爲四個層次:網絡接口層/鏈路層、網絡層、傳輸層和應用層。

應用層:就是把用戶A想要傳輸的數據按照協議(http、ftp等協議)封裝起來,送入下一層(傳輸層)。另一邊的用戶B接收到的也是這個封裝過的數據,用戶B按照這個協議(http、ftp)對這個數據進行解封,就能讀懂用戶A想表達的意思了。爲什麼要封裝?因爲只有按照一定的規則封裝,傳輸層、網絡層才能讀懂其中的意思,才能正確地傳輸。這個層對應的就是HTTP、FTP協議。

傳輸層:每臺設備有很多端口號、各種服務,傳輸稱就是解讀數據包中的端口號等信息,把這些信息告訴網絡層;這個層對應的有TCP、UDP協議。

網絡層:網絡層就是解讀數據包中的ip地址、目的地等,把這些信息告訴鏈路層。這個層使用最廣房的就是IP協議。

網絡接口層/鏈路層:就是網卡、網線、光線等負責傳輸數據的硬件,通過網絡層解析出來的ip地址、目的地,把數據包發送到指定地方;

因此,HTTP是應用層的協議,是從Web服務器傳輸超文本到本地瀏覽器的傳送協議。

而SOCKET是爲了實現以上的通信過程而建立成來的通信管道,而通信的規則採用指定的協議。用socket可以創建tcp連接,也可以創建udp連接,這意味着,用socket可以創建任何協議的連接。

Http協議定義了很多與服務器交互的方法,最基本的有4種,分別是PUT,DELETE,GET,POST,對應着對這個資源的增刪改查4個操作。 GET一般用於獲取/查詢資源信息,而POST一般用於更新資源信息.

GET和POST的區別
1、get是把提交的數據放在url中,通過地址欄來傳值;post是放在數據包的body中,通過提交表單來傳值;

2、因爲瀏覽器對URL的長度有限制,所以get方式提交的數據長度有限制,而POST方法提交的數據沒有限制;

3、get提交的數據就在url地址中,很容易就泄密了,post更安全;

性能優化、apk瘦身

1、UI優化

通過include標籤可以複用xml佈局文件,這樣程序需要加載的xml文件就變少了,自然就節省了內存。

merge標籤用於一個xml文件的根標籤,當這個xml文件被整體加載到某個父佈局中時,會使用父佈局中的XXXLayout加載merge的子控件,一般和include標籤配合使用,減少佈局層級。比如Activity對應的佈局是豎直排列的LinearLayout,include導入的標題欄也是豎直排列的LinearLayout,如果導入進來,就有一個LinearLayout層級是多餘的。所以標題欄的根標籤就不要用LinearLayout,直接用merge,這樣導進去的時候,就會用父佈局的LinearLayout,減少了層級。

ViewStub在Activity的加載過程中是不會加載、繪製的,只有在需要的時候,開發者調用ViewStub.inflate方法進行加載。比如網絡異常、無數據等UI,一般是不會展示的,即使設置爲gone,仍舊會加載、繪製,只是沒有顯示而已。使用viewStub的話,就可以根據需要進行加載了。

另外,由於View的draw、onDraw方法調用是非常頻繁的,所以自定義view時,千萬不要在這兩個方法裏執行太複雜的操作,也儘量不要在裏面new對象。

GPU過度繪製工具
在手機的“開發者選項”中,打開“顯示GPU過度繪製”開關。每個區域如果只被繪製了1次,就是它原本的顏色,如果繪製了2次,就是淺藍色,3次是綠色,4次是粉色,5次及以上就是紅色了。我們的目的是儘可能減少繪製的次數,提高性能。對於父控件,不需要背景色的,就不要設置背景色了,Activity的主題中會設置背景顏色,所以我們也要慎用。

Hierarchy Viewer工具
Hierarchy Viewer在連接手機時,手機上必須啓動一個叫View Server的客戶端與其進行socket通信,所以我們是無法用真機來調試的,只能用模擬器調試。這裏面可以顯示該Activity所有View的層級關係。

Android Lint工具
這是AS自帶的工具,可以檢測很多“優化”方面的問題(不僅僅是UI優化),常用的有下面這些功能:
1、檢查無用的資源、import、class等;
2、檢查過時的API;
3、檢查xml中的重命名,使用px的地方,manifest中註冊的Activity不存在,添加了不必要的權限等;
4、代碼中的問題,比如可以用局部變量的地方,可以用android優化過的集合parseArrayList,可以用equals代替的地方,有可能出現空指針的地方,拼寫錯誤的地方等等。其實這些一般在代碼中都會有加深提示。

發佈了35 篇原創文章 · 獲贊 52 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章