以前8月份開始複習一些基礎知識,並陸陸續續的總結了一些面試相關的東西,過久沒寫博客了,今天就作一個基礎知識的分享吧。css
無奈本人太蔡了,面試了這麼多家沒有收到一個offer。心灰意冷以後,遵從朋友建議,如今已經開始學後端相關的知識了,之後可能發的博客也不是音視頻相關的了,更多的是後端相關技術。html
首先複習基礎,這兒推薦github上的一個項目:java
LearningNoteandroid
而後就是一些進階須要掌握的知識 ,下面是我本身作的筆記,但願能幫到你們找到一個好的工做。c++
Android系統架構分爲5層,從下到上依次爲 Linux內核層,硬件抽象層,系統運行庫層(Native),應用框架層,應用層。git
Linux內核層:Android的核心基於Linux內核,在此基礎上添加了Android的專用驅動(好比Binder)、系統的安全性、內存管理、進程管理等等。github
硬件抽象層(HAL):有了核心還不行,你得須要運行到相應的硬件上才能實現本身的價值吧。而硬件抽象層就是硬件和Linux內核之間的接口,目的就是將硬件抽象化,保護硬件廠商的知識產權(Linux是有開源協議的)面試
系統運行庫層:怎麼操縱硬件,顯示圖像到屏幕?這一層就是幹這個的,它分爲兩部分,分別是C++程序庫和Android運行時。算法
C++程序庫:被Android系統中的不一樣組件使用,能夠經過應用框架層被開發者使用,下面是主要的程序庫:數據庫
openGL ES | 3D繪圖函數庫 |
---|---|
Media Framework | 多媒體庫 |
SQLite | 關係型數據庫引擎 |
SSL | 安全套接層 |
Android Runtime:ART虛擬機(5.0以後,Dalvik虛擬機被ART取代),ART在應用第一次安裝的時候,就會將字節碼預編譯成機器碼存儲到本地,這樣應用每次運行就無須執行編譯了(Dalvik是每次打開都要即時編譯),典型的以空間換時間
應用框架層:Framework層,這層代碼是用java編寫的,爲開發人員提供了API。
應用層。
1. Broadcast廣播,當某個程序向系統發送廣播時,其餘的應用程序只能被動地接收廣播數據
2. Content Provider,多個應用程序之間數據共享的方式(跨進程共享數據) ,應用程序能夠完成對數據的增刪改查。Android系統自己也提供了不少的Content Provider,好比音頻,視頻,聯繫人等信息。
3. 經過AIDL文件,其中AIDL也是經過binder實現進程間通訊的。
4. socket
複製代碼
Android系統是基於Linux內核的,Linux提供了管道、消息隊列、共享內存和socket等IPC機制。那爲何Android還要提供Binder來實現IPC呢?主要是基於性能、穩定性和安全性方面的考慮。
性能:socket做爲通用接口,傳輸效率低,開銷大,主要用到跨網絡進程通訊。消息隊列、共享內存和管道採用存儲-轉發模式,數據拷貝至少須要兩次,共享內存雖然無需拷貝,可是控制複雜,難以使用。而binder只須要拷貝一次,性能上只次於共享內存。
穩定性:Binder 基於 C/S 架構,客戶端(Client)有什麼需求就丟給服務端(Server)去完成,架構清晰、職責明確又相互獨立,天然穩定性更好。
安全性:Android 爲每一個安裝好的 APP 分配了本身的 UID,故而進程的 UID 是鑑別進程身份的重要標誌。傳統的 IPC 只能由用戶在數據包中填入 UID/PID,但這樣不可靠,容易被惡意程序利用。可靠的身份標識只有由 IPC 機制在內核中添加。其次傳統的 IPC 訪問接入點是開放的,只要知道這些接入點的程序均可以和對端創建鏈接,無論怎樣都沒法阻止惡意程序經過猜想接收方地址得到鏈接。
一般的作法是消息發送方將要發送的數據存放在內存緩存區中,經過系統調用進入內核態。而後內核程序在內核空間分配內存,開闢一塊內核緩存區,調用 copy_from_user() 函數將數據從用戶空間的內存緩存區拷貝到內核空間的內核緩存區中。一樣的,接收方進程在接收數據時在本身的用戶空間開闢一塊內存緩存區,而後內核程序調用 copy_to_user() 函數將數據從內核緩存區拷貝到接收進程的內存緩存區。這樣數據發送方進程和數據接收方進程就完成了一次數據傳輸,咱們稱完成了一次進程間通訊。
一次數據傳遞須要經歷:內存緩存區 --> 內核緩存區 --> 內存緩存區,須要 2 次數據拷貝
接收數據的緩存區由數據接收進程提供,可是接收進程並不知道須要多大的空間來存放將要傳遞過來的數據,所以只能開闢儘量大的內存空間或者先調用 API 接收消息頭來獲取消息體的大小,這兩種作法不是浪費空間就是浪費時間。
Binder IPC 機制中涉及到的內存映射經過 mmap() 來實現,mmap() 是操做系統中一種內存映射的方法。內存映射簡單的講就是將用戶空間的一塊內存區域映射到內核空間。映射關係創建後,用戶對這塊內存區域的修改能夠直接反應到內核空間;反以內核空間對這段區域的修改也能直接反應到用戶空間。
參考資料:寫給 Android 應用工程師的 Binder 原理剖析
參考:寫給Android App開發人員的Android底層知識
在用法上,它是java的關鍵字,通常咱們不太須要關注他的鎖的釋放,代碼執行完畢或者報錯會自動釋放鎖,而且沒法判斷鎖的狀態。
是一個接口,咱們使用ReentrantLock 比較多,有多個獲取鎖的方式,能夠trylock直接返回獲取成功或者失敗,線程不用一直等待。在finally中必需要釋放該鎖。
注:引用G神的博客:點擊連接直達
Android中的view是樹形結構的,view可能會重疊在一塊兒,當咱們點擊的地方有多個view的時候,這個時間該給誰,這就是爲何要有事件分發。
先來看看view的樹形結構:
上面多出來兩個東西是phonewindow
和decorview
,其中,主題顏色和標題欄內容等主要就是decorview來負責顯示的,那PhoneWindow
是作什麼的呢?
PhoneWindow
繼承window
,而且是window
惟一的實現類,window
是一個抽象類,是全部視圖的最頂層容器,視圖的外觀和行爲都歸他管,不管是背景顯示,標題欄仍是事件處理都是他管理的範疇,它其實就像是View界的太上皇。
`DecorView` 是 `PhoneWindow` 的一個內部類,其職位至關於小太監,就是跟在 `PhoneWindow` 身邊專業爲 `PhoneWindow` 服務的,除了本身要幹活以外,也負責消息的傳遞,`PhoneWindow` 的指示經過 `DecorView` 傳遞給下面的 View,而下面 View 的信息也經過 `DecorView` 回傳給 `PhoneWindow`
複製代碼
類型 | 相關方法 | Activity | ViewGroup | View |
---|---|---|---|---|
事件分發 | dispatchTouchEvent | √ | √ | √ |
事件攔截 | onInterceptTouchEvent | X | √ | X |
事件消費 | onTouchEvent | √ | √ | √ |
Activity做爲原始的事件分發者,不須要攔截事件,若是須要這個事件不分發下去就好了。
一樣的,view在事件傳遞的最末端,也不須要攔截事件,不處理回傳回去就好了。
事件在收集以後最早傳遞給Activity,而後依次向下傳遞:
Activity -> PhoneWindow -> DectorView -> ViewGroup -> ... -> view
複製代碼
若是沒有任何View消費掉事件,那麼這個事件會按照反方向回傳,最終傳回給Activity,若是最後 Activity 也沒有處理,本次事件纔會被拋棄 :
Activity <- PhoneWindow <- DecorView <- ViewGroup <- ... <- View
複製代碼
上面的模式是一個很是經典的責任鏈模式
注:參考連接
在activity的attach方法裏面,會建立一個PhoneWindow。
在onCreate中調用setContentView,setContentView
是window
的一個抽象方法,真正實現類是PhoneWindow
:
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
//1.初始化
//建立DecorView對象和mContentParent對象 ,並將mContentParent關聯到DecorView上
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();//Activity轉場動畫相關
}
//2.填充Layout
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);//Activity轉場動畫相關
} else {
//將Activity設置的佈局文件,加載到mContentParent中
mLayoutInflater.inflate(layoutResID, mContentParent);
}
//讓DecorView的內容區域延伸到systemUi下方,防止在擴展時被覆蓋,達到全屏、沉浸等不一樣體驗效果。
mContentParent.requestApplyInsets();
//3. 通知Activity佈局改變
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
//觸發Activity的onContentChanged方法
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
複製代碼
核心方法就兩個:installDecor() 和 mLayoutInflater.inflate(layoutResID, mContentParent) ;
installDecor會建立一個DecorView
對象,該對象將做爲整個應用窗口的根視圖。而後配置不一樣窗口修飾屬性(style theme等)。
mLayoutInflater.inflate就是解析xml,深度優先地遞歸解析xml,一層層添加到root view上,最終返回root view.解析的部分大體包含兩點:1.解析出View對象,2.解析View對應的Params,並設置給View。
有四種狀況會形成ANR發生:
onReceive()
函數時10秒沒有處理完成,後臺爲20秒ContentProvider
的publish
在10s內沒進行完如何避免:
儘可能避免在主線程中作耗時操做。 多線程==>引出如何實現多線程,線程池的使用
如何分析ANR:
data/anr/
目錄下生成一個文件traces.txt
Logcat
中查看app啓動加速:一個app的啓動分爲三種不一樣的狀態,其中,咱們只須要對第一種狀態作優化。對於App來講, 咱們能夠控制的啓動時間線無外乎Application的onCreate
,首屏Activity的渲染。
佈局優化 減小沒必要要的嵌套
響應優化
內存優化 bitmap的使用,options的jusdecodeBounds屬性,設置只解析bitmap的寬高等,而後使用insimplesize對bitmap進行壓縮。在android2.3的時代,bitmap的回收須要調用recycler方法,而且置空,可是以後只須要進行置空操做。
加載一張大圖:使用BitmapRegionDecode進行局部解碼,
lru算法的實現=> LinkedHashMap
電池使用優化
網絡優化
一步手機的刷新率爲60hz,當一幀圖像繪製完畢後準備繪製下一幀時,顯示器會發出一個垂直同步信號(如VSync), 60Hz的屏幕就會一秒內發出 60次這樣的信號。而這個信號主要是用於同步CPU、GPU和顯示器的。
通常地來講,計算機系統中
,CPU、GPU和顯示器以一種特定的方式協做:CPU將計算好的顯示內容提交給 GPU,GPU渲染後放入幀緩衝區,而後視頻控制器按照同步信號從幀緩衝區取幀數據傳遞給顯示器顯示。
面試幾家以後發現都沒有問,因此略過
GC:垃圾回收器,自動釋放垃圾佔用的空間,讓建立的對象不須要像c/c++那樣手動delete、free掉。
GC是在何時,對什麼東西,作了什麼事情?
Java堆內存不足時,GC會被調用。當應用線程在運行,並在運行過程當中建立新對象,若這時內存空間不足,JVM就會強制地調用GC線程,以便回收內存用於新的分配。若GC一次以後仍不能知足內存分配的要求,JVM會再進行兩次GC做進一步的嘗試,若仍沒法知足要求,則 JVM將報「out of memory」的錯誤,Java應用將中止。
minor gc/full gc的觸發條件、OOM的觸發條件,下降GC的調優的策略 (深刻理解jvm)
minor gc :當新生代的eden區滿了的時候,會觸發gc
full gc: 當老年代空間不足,方法區空間不足,minor gc進入老年代的時候。大對象
從gc root搜索不到,並且通過第一次標記、清理後,仍然沒有復活的對象
JVM將堆分紅了二個大區新生代(Young)和老年代(Old),新生代又被進一步劃分爲Eden和Survivor區,而Survivor由FromSpace和ToSpace組成。
Young中的98%的對象都是死朝生夕死,因此將內存分爲一塊較大的Eden和兩塊較小的Survivor一、Survivor2,JVM默認分配是8:1:1,每次調用Eden和其中的Survivor1(FromSpace),當發生回收的時候,將Eden和Survivor1(FromSpace)存活的對象複製到Survivor2(ToSpace)
注:分紅三塊是爲了充分利用內存。原來是隻有一塊內存,在這上面作標記清除算法,可是gc以後會存在大量不連續的空間,因此有人提出將內存一分爲二,將第一塊內存存活的對象轉移到第二塊內存,而後將第一塊內存gc。
新生代的GC(Minor GC):新生代一般存活時間較短基於Copying算法進行回收,所謂Copying算法就是掃描出存活的對象,並複製到一塊新的徹底未使用的空間中,對應於新生代,就是在Eden和FromSpace或ToSpace之間copy。新生代採用空閒指針的方式來控制GC觸發,指針保持最後一個分配的對象在新生代區間的位置,當有新的對象要分配內存時,用於檢查空間是否足夠,不夠就觸發GC。當連續分配對象時,對象會逐漸從Eden到Survivor,最後到老年代。
老年代的GC(Major GC/Full GC):老年代與新生代不一樣,老年代對象存活的時間比較長、比較穩定,所以採用標記(Mark)算法來進行回收,所謂標記就是掃描出存活的對象,而後再進行回收未被標記的對象,回收後對用空出的空間要麼進行合併、要麼標記出來便於下次進行分配,總之目的就是要減小內存碎片帶來的效率損耗。
參考:全面理解java內存模型,深刻理解java虛擬機
Java內存模型定義了多線程之間共享變量的可見性以及如何在須要的時候對共享變量進行同步。
類從被加載到虛擬機內存中開始,到卸載出內存中爲止,它的整個生命週期以下:
類加載模型
雙親委派模型,從java虛擬機的角度講,只存在兩種不一樣的類加載器,啓動類加載器和全部的其餘類加載器,啓動類加載器使用C++語言實現,是虛擬機的一部分,其餘的類加載器由java語言實現,獨立於虛擬機外,對於java開發人員來講,類加載器劃分得更細:
雙親委派的工做過程:若是一個類加載器收到了類加載的請求,首先它不會本身去嘗試加載這個類,而是把這個請求委派給父類去完成,最終都會傳送到頂層的啓動類加載器中,只有當父加載器反饋本身沒法完成這個加載請求的時候,子加載器纔會嘗試本身去加載。
Android中經常使用的類加載器有:DexClassLoader
和PathClassLoader
,這兩個類都繼承自BaseDexClassLoader
,
PathClassLoader
用於加載內部的Dex文件,DexClassLoader
多傳了一個optimizedDirectory參數 ,能夠用來加載外部的apk文件(插件化技術的核心)
代碼修復主要有兩大方案,一種是阿里系的底層替換方案,另外一種是騰訊系的類加載方案,這兩種方案各有優劣。
底層替換方案限制比較多,可是時效性最好,加載快,當即見效
類加載方案時效性差,須要冷啓動才能見效,但修復範圍廣,限制少。
直接在已加載類中替換掉原有方法,即在原來類基礎上進行修改,所以沒法實現增減原有類方法或字段,這樣會破壞原有類的結構。
在app從新啓動後讓Classloader加載新的類。由於當app運行到一半時,所需發生變動的類已經被加載過,而在Android上沒法對一個類進行卸載操做,若不重啓,原來的類還存儲於虛擬機中,新類沒法被加載。所以只有在下次重啓時,在業務邏輯運行以前搶先加載補丁中的新類,這樣後續訪問此類時,纔會Resolve爲新類,從而達到熱修復的目的。
池化技術
簡單的來說就是提早保存大量的資源,以備不時之需。在機器資源有限的狀況下,使用池化技術能夠大大的提升資源的利用率。比較典型的池化技術有線程池、鏈接池、內存池、對象池等。
爲何使用線程池
常見的線程池:
ThreadPoolExecutor
:基本線程池FixedThreadPool
:可重用固定線程池,執行長期的任務,性能好不少CachedThreadPool
:按需建立,執行不少短時間異步的小程序或者負載較輕的服務器SingleThreadPool
:單個核線的fixed,一個任務一個任務執行的場景ScheduledThreadPool
:定時延時執行,週期性執行任務的場景上面的2-5的線程池,都是基於第一個基本線程池實現的,不一樣的地方在於核心線程數和存聽任務的隊列類型不一樣。
如何使用
在Executors
這個類中封裝了不少方法。
有兩種方法提交任務,submit和execute,其中submit就是將runnable包裝成FutureTask 而已,最終調用的仍是execuet,因此咱們看execute的實現過程
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) { //直接新建核心線程
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) { //線程池是否在運行,添加到隊列裏等待
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command)) //線程池若是沒有在運行,移除隊列並拒絕任務
reject(command);
else if (workerCountOf(recheck) == 0) //核心線程滿時,建立非核心線程執行任務
addWorker(null, false);
}
else if (!addWorker(command, false)) //拒絕
reject(command);
}
複製代碼
主要的工做是:
注:參考 圖解HTTPS協議加密解密全過程
在傳輸層和應用層之間添加了一個SSL層,變成HTTP先和SSL通訊,再由SSL和TCP通訊
加密方式有兩種:
對稱密鑰加密:加密和解密同用一個密鑰。加密時就必須將密鑰傳送給對方。那密鑰如何保證安全傳輸的?這是一個先有雞仍是先有蛋的問題,傳輸內容怎麼加密的密鑰也能夠這麼加密
非對稱加密:使用一對非對稱的密鑰。一把叫作私有密鑰,一把叫作公開密鑰。私有密鑰不能讓其餘任何人知道,而公開密鑰則能夠隨意發佈,任何人均可以得到。能夠想象成一把鑰匙和一個鎖頭,只是全世界只有你一我的有這把鑰匙,你能夠把鎖頭給別人,別人能夠用這個鎖把重要的東西鎖起來,而後發給你,由於只有你一我的有這把鑰匙,因此只有你才能看到被這把鎖鎖起來的東西。
過程:
任何不兼容的問題,均可以經過增長一「層」來解決。在cpu和外設之間的這一層就是IO接口。IO接口形式不限,它能夠是個電路板,也能夠是塊芯片,甚至能夠是個插槽,它的做用就是在cpu和外設之間相互作協調轉換,如cpu和外設速度不匹配,它就是變速箱,cpu和外設信號不通用,它就是翻譯機。
一旦發生衝突,就去尋找下一個空的散列地址,只要散列表足夠大,空的散列地址總能找到
實現準備多個計算散列值得函數,發生衝突以後,換一種計算散列值的函數。
在發生衝突的地方插入一條單鏈表,將衝突的元素都放到這個鏈表裏。
java的hashmap就是使用的這種方法解決衝突 >>經過數組+鏈表+(紅黑樹,jdk1.8以後對hashmap作了優化)的方式,將hashcode相同的元素串成一串。
發生衝突以後的都存儲到公共溢出區
推薦書籍:大話數據結構
多去leetcode刷題
隊列一般能夠分爲兩種類型: