個人記錄和整理
okhttp
程式裡直觀請求是由request和okhttpclient一起組成,request包含請求的引數,url,header等等服務端需要的一些資訊,okhttp宣告的時候例項化dispatcher,呼叫newcall方法會返回reallcall,發起請求實則是由realcall開始,宣告reallcall的構造傳入okclient,而okclient裡有dispatcher,realcall會在執行同步和非同步請求時將call物件會存到dispatcher裡,dispatcher裡面包含了三個請求佇列:1,非同步等待call佇列readyasynccalls,2,非同步執行call佇列runningasynccalls,3,同步執行call佇列runningsynccalls,加入1和2的是asynccall(reallcall的內部類),3是reallcall,1和2的評判是根據如果最大請求數目64或者同一host下請求數超過5個則放入1。
reallcall裡的enqueue呼叫的執行緒池的execute方法(需要runnable物件)傳入asyncall,asynccall繼承namedrunnable(實現runnable介面在run方法里加入抽象方法execute)重寫了execute;
reallcall同步方法直接執行的是execute方法並返回response,不管同步還是非同步都會呼叫核心方法:getResponseWithInterceptorChain(),裡面宣告一個集合按順序依次放入各種interceptor,使用者自己定義的放第一,其次是:失敗和重定向(RetryAndFollowUpInterceptor),客戶端與服務端橋接(BridgeInterceptor),讀取快取和更新快取(CacheInterceptor),與伺服器建立連線(ConnectInterceptor),從伺服器讀取響應資料(CallServerInterceptor)等攔截器;接著宣告RealInterceptorChain,呼叫它的第一次proceed(request)方法,該方法通過攔截器集合取出各個interceptor並呼叫intercept()方法傳入再次宣告的RealInterceptorChain物件,因為每個攔截器裡的具體方法都呼叫了RealInterceptorChain的proceed(request)方法,這樣通過遞迴的方式(next索引每次加1)依次執行完所有的Interceptor來共同驅動okhttp工作。
HashMap
hashmap 陣列加連結串列的結構,超過 16(預設數量)*0.75(負載因子)12個需要擴容,擴容時(雙倍)會重新hash並定位,耗效能,所以最好能提前傳入大小,並且擴容時的resize方法,並不是執行緒安全,併發操作容易形成環形連結串列(本來就不支援同步),獲取一個不存在的key時計算出的index正好是環形連結串列的下標就會出現死迴圈;使用entrySet迭代器while同時遍歷出key和value,而keySet則需要先查出key在查value效率低,1.8超過8個後修改為紅黑樹查詢後效率提高但是併發問題還是存在。
1.7put:判斷Entry table是否需要初始化,key為null則put一個null進去,根據key的hashcode方法計算出hash值,在利用hash值算出在table中的索引,將索引處Entry的key和hash與傳入key(記憶體和值隨便一個相等)和key的hash比較是否相同,如果相同則替換value,並返回舊的value,不相同說明是新資料則呼叫addEntry,此時需要判斷擴容。
1.7get:key為null返回null,不為null和put一樣根據key計算出hash,接著算出在table中的索引,將索引處的entry的hash,key與傳入的key,計算出的hash比較,相同則取出並返回value,沒有找到相對應的key的值也返回null。
2的冪次方原因:用key計算出的hash值和陣列的長度-1做與運算得到下標索引,如果陣列長度為2的冪次方,那麼陣列長度-1的二進位制末尾值都是1,與key的hash進行&運算之後的結果,除了超過陣列長度-1數值的高位部分,低位部分都與key的hash值一致。如果陣列長度不是2的冪次方,計算出的索引二進位制末尾值永遠是0,也就是不會有末尾為1的情況,比如1.3.5.7.9.11等索引永遠不會儲存值,造成空間浪費;比如陣列長度16(16-1,1111)和15(15-1,1110)分別和hashcode8(1000)和9(1001)相與,16得到的index就是hashcode低位值8,9,但是15得到的index是相同的8,這樣定位到同一位置上了這就產生了碰撞。
concurrenthashmap
同樣是陣列加連結串列的結構,採用分段鎖,其中 Segment 繼承於 ReentrantLock。不會像 HashTable 那樣不管是 put 還是 get 操作都需要做同步處理,解決了HashMap的併發問題。
java8中 是用尾插法擴容 java7以前都是頭插法 所以java8不會有 死迴圈問題。
Lrucache 近期最少使用演算法
核心思想優先淘汰那些近期最少使用的快取物件,內部使用的是LinkedHashMap雙向連結串列結構,LHM的構造傳入accessOrder為true時這個集合的順序就會是訪問順序,也就是將訪問後的資料放到最後面,put方法每次都會呼叫sizeof方法累加大小,需要使用者自己計算並返回,預設為1,並且會根據key查詢之前關聯過的value,找到就返回並且相應的size減掉(重複算過了)最後呼叫trimTosize(maxsize)判斷是否超過maxsize,超過就將LHM裡存放時間最久的取出並移除,size減掉;get方法最重要的是LHMget方法裡afterNodeAccess方法,這個方法的作用就是將剛訪問過的元素放到集合的最後一位,所以沒有用到的會被移到LHM的頭部,trimToSize方法將其移除。
建立物件時構造器的呼叫順序是:先初始化靜態成員(父→子),然後初始化非靜態成員(父→子),最後呼叫構造器(父→子)。
垃圾收集演算法
標記-清除演算法:分為標記和清除兩部階段,首先標記所需要回收的物件,在標記完成後統一回收所有被標記的物件,1:效率低,標記和清除過程都不高,2:空間問題,標記清除後會產生大量不連續的記憶體碎片,空間碎片太多導致在需要分配大物件時無法找到足夠的連續記憶體而不得不在提前觸發一次垃圾回收動作。
複製演算法:按記憶體容量劃分為大小相等的兩塊,每次只需要其中的一塊,當一塊記憶體用完了將存活的物件複製到另一塊上面,然後再把剛剛用完的記憶體空間一次清理掉,解決了記憶體碎片問題,但是可用記憶體就縮小為一半。
標記整理演算法:標記過程同標記-清除演算法一樣,但是在後續步驟不是直接對物件進行清理,而是讓所有存活的物件都向一側移動,然後直接清理掉端邊界以外的記憶體。
分代收集演算法:當前商業虛擬機器的GC都是採用分代收集演算法,根據物件存活週期的不同將堆分為:新生代和老年代,方法區為永久代(新版本已經將永久代廢棄,引入元空間概念,永久代使用JVM記憶體而原空間直接使用實體記憶體),根據各個年代的特點採用不同的收集演算法,新生代中的物件產生與死亡比較頻繁,使用複製演算法。新生代又分為Eden區和Survivor區(Survivor from Survivor to)比例8:1:1。新產生的物件優先進入Eden去,當Eden區滿了之後在使用Survivor from區,當Survivor from區也滿了之後就進行Minor GC(新生代GC):GC的時候Eden和Survivor from中存活的物件copy進入Survivor to,然後清空Eden和survivor from,這個時候的Survivor from就成了新的Survivor to(滿狀態未使用),原來的Survivor to就成了Survivor from(有存活物件,等待裝滿去Survivor to),Survivor from和Survivor to不斷的交換自身角色進行物件的儲存和清除,如果Survivor to無法容納全部存活物件根據老年代的分配擔保將物件copy進去老年代,如果還是無法容納則進行Full GC(老年代GC)。大物件直接進入老年代,JVM中的引數:-XX:PretenureSizeThreshold,大於這個值進入老年代,目的是為了避免Eden和Survivor區之間進行大量的記憶體複製。長期存活的物件進入老年代:JVM給每個物件一個年齡計數器,如果物件在Eden出生並經過第一次新生代GC後,並且能在Survivor區中容納,那麼該物件年齡設定為1,每熬過一次新生代GC,年齡就加1,直到一定程度(預設為15,可以通過XX:MaxTenuringThreshold來設定),就會移入老年代。如果Survivor區中相同年齡(如年齡為x)所有物件大小總和大於Survivor的一半,年齡大於等於x的所有物件直接進入老年代,無需等到最大年齡要求。
AsyncTask是SerialExecutor和Thread_Pool_Executor和handler協同工作,前者通過synchronize方法使得任務序列,真正工作的是後者的執行緒池,由於InternalHanlder需要用MainLooper作為引數所以AsyncTask類必須是在主執行緒初始化。構造方法初始化了mWorker和mFuture物件,mWorker作為mFuture的引數。任務執行是通過呼叫AsyncTask的execute方法,該方法裡預設傳入的是SerialExecutor,然後呼叫了executeOnExecutor方法,這裡可以通過接受Thread_Pool_Executor物件繞過SerialExecutor使得任務變成並行,該方法裡呼叫了onPreExecute,到後面呼叫了執行緒池的execute方法接受構造裡初始的mFuture,執行具體的耗時任務,即開頭建構函式中mWorker中call()方法的內容。先執行完doInBackground()方法,又執行postResult()方法,postResult裡使用InternalHanlder處理不同的訊息來執行onProgressUpdate,onPostExecute來實現執行緒的切換。
注:注意記憶體洩漏,必須在主執行緒使用
HandlerThread搞清楚不是子執行緒與主執行緒的切換,而是建立工作執行緒,不再是使用執行緒池開銷太大。具體使用時初始化時傳入工作執行緒的名稱隨便填,呼叫start方法,在初始化一個處理執行緒任務的handler傳入handlerthread的getLooper作為引數,順序不能倒。handlerthread繼承至thread並且構造很簡單傳入執行緒名稱和優先順序,run方法裡才呼叫Looper.prepare()初始化looper在呼叫notifyall該方法和getLooper裡的wait方法共同完成對looper的同步初始化工作,這也是之前宣告時順序不能倒的原因,最後呼叫loop開啟任務。handlerthread有quit和quitsafelf方法,都是呼叫的messagequene的quit,區別是前者不管任務有沒執行完都呼叫removeAllmessagelocked(迴圈遍歷message連結串列移除所有訊息回撥最後重置null)直接終止任務,而後者呼叫removeAllFuturemessage,該方法通過message.when來判斷訊息是否處理完畢,處理完就呼叫removeAllMessagelocked,沒有就等待訊息處理完畢。
IntentService抽象類繼承至service,區別service:1,自帶工作執行緒,2,停止服務不需要手動呼叫。區別其他工作執行緒:1,內部採用的是handlerthread類似於工作執行緒,2,由於是繼承service是屬於後臺服務優先順序高保證任務執行。onCreate方法裡初始化handlerthread於上面使用handlerthread的步驟一樣,呼叫startservice時執行到onStartcommand裡的onstart方法,onstart裡開始使用servicehandler傳送message訊息傳遞intent物件和一個整型startid標記來判斷是不是最後一個任務訊息,handler裡的handlermessage呼叫onhandleintent該方法自行實現,通過呼叫stopself(startid)自動停止,多次呼叫startservice不會多次建立oncreate所以多個任務會多次呼叫onstart來依次處理任務。
onMeasure
單子view測量流程:measure(final)→onmeasure(需要複寫)→setMeasuredDemission→getDefaultsize(有背景則使用背景提供的寬高,沒有則使用minxx(預設為0));具體使用:複寫後根據測算模式和測量size以及實際需求大小來決定單子view的寬高
viewgroup測量流程:measure(final)→onmeasure(需要複寫,前2個都是view的方法)→measurechildren(for迴圈呼叫measurechild)→measurechild→getChildMeasureSpec(獲得寬高的測量規格後呼叫child.measure傳入測量規格)→最後合併子view寬高呼叫setMeasureDemission;具體使用:複寫後for迴圈呼叫measurechild,然後累加子view的getMeasureXXX,最後根據父view的測量模式和測量size決定最後的viewgroup測量寬高。
測量規格MeasureSpec由測量mode和測量size決定,而mode,size又和父view的measurespec以及當前view的layoutparam有關,規則:1若layoutparam為具體值則子view的measurespec mode則為exactly,size則是具體值;2若為matchparent,①父view measurespec mode為exactly則子view measure mode為exactly 子view size為父view剩餘空間大小,②父view measurespec mode為atmost則子view measspec mode為atmost ,子view size不超過父view剩餘空間;3若為wrapcontent,子view的measspec mode為atmost ,子view size不超過父view剩餘空間。
onLayout
單子view:layout(不能惡意重寫,呼叫setFrame佈局自身)→onlayout(空實現,不建議重寫)。
viewgroup:layout(不能惡意重寫,呼叫setFrame佈局自身)→onlayout(需要重寫,for迴圈呼叫單子view的佈局流程)。具體使用:在onlayout中for迴圈getMeasureXX方法呼叫child.onlayout來擺放每個子view。
注意:在非人為情況改變layout裡返回的數值時getXX和getMeasureXX永遠相等,getXX是在onLayout方法執行後才有值,getMeasureXX是獲得測量寬高一般在onLayout中使用,使用場景不同要分先後。
onDraw
單子view:draw(繪製自身)→drawbackground(繪製背景)→onDraw(繪製自身內容複寫自行實現)→dispatchDraw(繪製子view,沒有子view,空實現)→onDrawforeground(繪製前景,比如繪製scrollbar,indicator)
viewgroup:draw(繪製自身)→drawbackground(繪製背景)→onDraw(繪製自身內容複寫自行實現)→dispatchDraw(for迴圈呼叫child.draw,與單子view的流程一樣)→onDrawforeground(繪製前景,比如繪製scrollbar,indicator)
mvc:一般的是view→contraller→model→view,view由xml或者java view,contraller由activity承擔,由於xml功能太弱所以activity承擔了view和contraller太多的工作,model業務模型,主要是網路資料獲取,資料庫,io等。主要操作:在activity宣告model,觸發view事件後呼叫model的業務操作並通過activity實現的介面來接受具體資料,然後展示。
mvp:view和presenter雙向,persent和model雙向,通過中間層presenter讓view和model完全解耦,讓view以介面的形式存在並讓activity實現相應更新介面的方法,主要操作:在activity裡宣告presenter傳入實現了view的實現類activity,呼叫presenter的方法,presenter裡有model和view的引用,model執行業務操作,然後用view更新,裡面資料傳遞都使用介面的方式。也可以使用Contract契約類將實現basepresenter的具體presenter和實現baseview的具體view裝載起來,好處是通過規範的命名和註釋可以清晰的看到整個介面的邏輯。缺點是業務多的情況下介面類和實現類也會過度增加。
mvvm藉助databinding,強化了xml的功能充當view層,佈局裡以layout包裹data標籤,和我們自己的佈局,data裡配置變數名和型別,通過@{}或@={}的方式進行引用,其中@={}的方式表示雙向繫結,為每個控制元件繫結實體物件,model層執行網路請求,資料庫,io等操作然後由於vm層(相當於presenter)持有view和model的引用,所以model通過介面將資料傳遞給vm,vm使用view的引用更新介面,vm在activity裡初始化傳入binding即view。
注:以上的三種設計模式具體使用是需要注意生命週期,當銷燬的需要銷燬
只需要在gradle檔案中新增如下程式碼:
android{
dataBinding{
enabled=true
}
}
ActivityXXBinding自動生成的xx是layout佈局名稱,可以ActivityXXBinding.控制元件id獲取控制元件,通過@BindingAdapter("bind:imgurl")註解可以自定義屬性在圖片載入方法上面,然後佈局裡app:imgurl並繫結,java程式碼裡賦值,實體繼承BaseObservable並在想要改變的欄位get的方法加上@Bindable註解,然後給需要改變的欄位的set方法加上notifyPropertyChanged(org.lenve.databinding3.BR.description);一句即可。
EventBus3.0有四種執行緒模型,分別是:
POSTING (預設) 表示事件處理函式的執行緒跟釋出事件的執行緒在同一個執行緒。
MAIN 表示事件處理函式的執行緒在主執行緒(UI)執行緒,因此在這裡不能進行耗時操作。
BACKGROUND 表示事件處理函式的執行緒在後臺執行緒,因此不能進行UI操作。如果釋出事件的執行緒是主執行緒(UI執行緒),那麼事件 處理函式將會開啟一個後臺執行緒,如果果釋出事件的執行緒是在後臺執行緒,那麼事件處理函式就使用該執行緒。ASYNC 表示無論事件釋出的執行緒是哪一個,事件處理函式始終會新建一個子執行緒執行,同樣不能進行UI操作。
在EventBus3.0之前我們必須定義以onEvent開頭的那幾個方法,分別是onEvent、onEventMainThread、onEventBackgroundThread和onEventAsync,而在3.0之後事件處理的方法名可以隨意取,不過需要加上註解@subscribe(),並且指定執行緒模型threadmode=xxx,預設是POSTING。在接受事件的類需要註冊與反註冊,Eventbus,.getdefault.register/unregister,傳送事件post(物件)或者粘性事件postSticky(即後註冊的訂閱者能接受到先發送的事件)。
棧:區域性變數裡的基本資料型別和引用,但是引用的物件實體是在堆中。
堆:成員變數(基本資料型別,引用和引用的物件實體)
方法區:方法和static變數
linux系統fork出init程序,init程序fork出zygote程序,Launcher(Launcher本質上也是一個應用程式繼承至activity)程序通過Binder通知system_server(zygote fork出)程序,裡面初始化了各種系統service,ams,pms,wms,system_server通過socket通訊告知zygote 再fork出app程序,app程序裡初始化applicationthread,activitythread,最後回撥Activity.onCreate()開始了生命週期。
斷點續傳,以上次結束的地方作為起點接著下載,httpURLconnection.setRequestProperty("Range","bytes="+50+"-"+100)設定從服務端請求引數,本地檔案寫入使用的是RandomAccessFile物件的seek(100)方法,可以從檔案的指定位置開始寫入資料,寫的方法和outputstream一樣。多執行緒斷點續傳就是在斷點續傳上延伸的,將檔案分成多個部分每個部分都按照斷點續傳的方式進行,每個執行緒的起始點需要根據檔案的大小除以執行緒數目做分隔,最後一個執行緒的終點是檔案總長度為了防止分隔不均勻,下載的進度、停止、完成等狀態需要做同步處理因為是多執行緒同時訪問的。實際專案中的下載實體要做資料庫儲存和檔案本地校驗。
陣列與集合:陣列長度固定,集合是可變的;陣列可存入基本資料型別如int,而集合需要轉成相應的包裝類。
選擇排序思想:int temp=0;int min=0;
for(int i=0;i<arrs.length-1;i++)(
for(int j=i+1;j<arrs.length;j++)(
if(arrs j< arrs min)(
min=j;//注意這裡的min是會被替換動態賦值的。 找出最小的數
temp =arrs i ; arrs i=arrs min;arrs min=temp.//替換位置
找出最小的數與第一個數交換位置,在剩下的數中找出最小的數與第二個數交換位置,以此類推。
泛型:語法糖,在編譯時期就完成型別轉換的工作,防止在執行時出現類轉換異常,在編譯時期發現錯誤總比在執行時要好。
coordinatorlayout design包下,constraintlayout 約束佈局,i++ 先賦值在自增,++i先自增在賦值,
synchronized修飾static方法時需要獲得類鎖,當所有該類的物件(多個物件)在不同執行緒中呼叫這個static同步方法時,執行緒之間會形成互斥,因為該方法只有一個;若不是靜態的方法則不會發生互斥因為他們擁有的是不同的鎖。
newfixedthreadpool(core) 核心執行緒數和最大執行緒數一樣,也就是隻有核心執行緒,0L,空閒不會被回收,若超出則等待。
newsinglethreadexecutor(無),核心執行緒為1,最大執行緒數為1,也就是隻有一個核心執行緒,0L,空閒不會被回收,若超出則等待。
newcachethreadpool (無)核心執行緒為0,總數目為最大值,60L,沒有空閒執行緒則建立執行緒,空閒超60s則回收。
newscheduledthreadpool(core),核心執行緒數目固定,總數目為最大值,0L,作用是定時開啟執行緒執行任務。
http 狀態碼 2xx:成功,3xx重定向,4xx:客戶端錯誤,其中403禁止,404未找到,5xx伺服器內部錯誤常見的500,http 使用的預設埠是80;https使用的預設埠是 443
對稱加密:加密和解密都是相同的祕鑰,常見的有DES,AES;非對稱加密:有一組祕鑰對分為公鑰和私鑰,公鑰加密只有私鑰才能解密確保內容安全,私鑰加密公鑰解密接受者確認傳送者身份,常見的RSA。
效能分析:自帶的android profiler分析記憶體 cpu network等,leakcanary分析記憶體洩漏(單利,asynctask,handler,非靜態內部類),
佈局優化:①LL和RL都能完成的使用LL,②使用include標籤載入公共佈局,③使用viewstub按需載入比如出錯頁面和空頁面, viewstub.inlfate或者viewstub.setvisiable為可見來初始化,只能初始化一次,之後只能操作viewstub裡的內容因為初 始化完成後移除自身了④使用merge標籤減少佈局巢狀層次比如include裡和外都是LL可以使用merge,⑤使用 constraintlayout 約束佈局
網路優化:①大量資料採用分頁,②儘量減少網路請求能合併的就合併,③大圖片需要壓縮推薦使用webp,④加入網路資料快取
安裝包優化:①使用混淆或者使用lint工具掃描剔除無用資原始檔,②能使用shape代替圖片就使用shape,使用圖片的話優先使 用.9圖,本地大圖使用webp格式,③使用7zip壓縮apk裡的resoures.arsc,需要zipalign對齊,然後簽名。
記憶體優化: 避免記憶體洩漏(asynctask介面銷燬時cancel,handler靜態加弱引用結束removecallbacks),,擴大記憶體
程式碼優化:①使用lint工具進行靜態程式碼塊分析,如果有用到hashmap會提示使用SparseArray代替,②避免建立非必要物件,對 象的建立需要記憶體分配,銷燬需要回收記憶體一定程度上影響到應用的效能比如不要單獨建立執行緒使用rxjava或者自定 義執行緒池。
RecyclerView優化:
如果item的高度不會影響recyclerview的高度並且使用的是增刪查改重新整理的recyclerview可以使用setHasFixedsize(true)來減少佈局的重繪。
((SimpleItemAnimator) rv.getItemAnimator()).setSupportsChangeAnimations(false)關閉預設動畫,
設定 RecyclerView.addOnScrollListener(listener);
來對滑動過程中通過scrollState停止載入的操作
使用DiffUtil進行區域性重新整理:DiffUtil.calcaulateDiff(new CustomDiffUillCallBack(oldlist,newlist)),CustomDiffUillCallBack裡實現4個方法,根據需求返回數值,返回DiffUtil.DiffResult物件呼叫dispatchUpdatesTo(adapter)
android中的類載入器,pathclassloader載入dex,dexclassloader載入jar/apk,findclass方法,for迴圈elements,每個element就是一個dex,找到對應名稱的類就返回,熱修復就是將新類插隊到bug類之前,注意引用到bug類的類的標記問題。
屬性動畫和補間動畫區別:補間動畫是父容器不斷的繪製 view,實則還是在原地,而屬性動畫改變了view內部屬性值真正的改變view,通過設定點選事件可以證明。
TaskTrackBuilder:MainActivity已退出的情況下點選通知進入通知詳情後想要回到MainActivity的關鍵,通知詳情介面清單需配置parentActivityName。
提高程序優先順序:監聽鎖屏和開屏廣播,鎖屏時開啟一個1px透明的activity,開屏時finish;使用service和該service的內部service的startForeground傳送相同id的通知, 內部的service在stopself通知全部消失。
onTouch與onTouchEvent:都是在dispatchTouchEvent中執行,但是onTouch優於onTouchEvent,如果onTouch返回true那麼onTouchEvent不會得到執行,如果控制元件是非enable的那麼註冊的onTouch無效,想要監聽touch必須重寫onTouchEvent來實現。