接口的意義
規范、擴展、回調
在Activity中如何保存/恢復狀態?
分別調用onSaveInstanceState和onRestoreInstanceState 2個方法保存和恢復狀態。
launchMode之singleTask與taskAffinity
taskAffinity是用來指示Activity屬於哪一個Task的。taskAffinity能夠決定以下兩件事情(前提是Activity的launchMode為singleTask或者設置了FLAG_ACTIVITY_NEW_TASK),默認情況下,在一個app中的所有Activity都有一樣的taskAffinity,但是我們可以設置不同的taskAffinity,為這些Activity分Task。甚至可以在不同的app之中,設置相同的taskAffinity,以達到不同app的activity公用同一個Task的目的。
res/raw和asserts的區別
這兩個目錄下的文件都會被打包進APK,並且不經過任何的壓縮處理。
assets與res/raw不同點在於,assets支持任意深度的子目錄,這些文件不會生成任何資源ID,只能使用AssetManager按相對的路徑讀取文件。如需訪問原始文件名和文件層次結構,則可以考慮將某些資源保存在assets目錄下。
圖片放錯目錄會產生的問題嗎?
高密度(density)的系統去使用低密度目錄下的圖片資源時,會將圖片長寬自動放大以去適應高密度的精度,當然圖片占用的內存會更大。所以如果能提各種dpi的對應資源那是最好,可以達到較好內存使用效果。如果提供的圖片資源有限,那么圖片資源應該盡量放在高密度文件夾下,這樣可以節省圖片的內存開支。
drawable-nodpi文件夾
這個文件夾是一個密度無關的文件夾,放在這里的圖片系統就不會對它進行自動縮放,原圖片是多大就會實際展示多大。但是要注意一個加載的順序,drawable-nodpi文件夾是在匹配密度文件夾和更高密度文件夾都找不到的情況下才會去這里查找圖片的,因此放在drawable-nodpi文件夾里的圖片通常情況下不建議再放到別的文件夾里面。
Bitmap和Drawable
Bitmap是Android系統中的圖像處理的最重要類。可以簡單地說,Bitmap代表的是圖片資源在內存中的數據結構,如它的像素數據,長寬等屬性都存放在Bitmap對象中。Bitmap類的構造函數是私有的,只能是通過JNI實例化,系統提供BitmapFactory工廠類給我們從從File、Stream和byte[]創建Bitmap的方式。
Drawable官文文檔說明為可繪制物件的一般抽象。View也是可以繪制的,但Drawable與View不同,Drawable不接受事件,無法與用戶進行交互。我們知道很多UI控件都提供設置Drawable的接口,如ImageView可以通過setImageDrawable(Drawable drawable)設置它的顯示,這個drawable可以是來自Bitmap的BitmapDrawable,也可以是其他的如ShapeDrawable。
也就是Drawable是一種抽像,最終實現的方式可以是繪制Bitmap的數據或者圖形、Color數據等。理解了這些,你很容易明白為什么我們有時候需要進行兩者之間的轉換。
要加載很大的圖片怎么辦?
如果圖片很大,比如他們的占用內存算下來就直接OOM了,那么我們肯定不能直接加載它。解決主法還是有很多的,系統也給我們提供了一個類BitmapRegionDecoder,可以用來分塊加載圖片。
圖片圓角(或稱矩形圓角)或者圓形頭像的實現方式
除了把原圖直接做成圓角外,常見有三種方式實現:
使用Xfermode混合圖層;
使用BitmapShader;
通過裁剪畫布區域實現指定形狀的圖形(ClipPath)
Context
ContextWrapper是上下文功能的封裝類,而ContextImpl則是上下文功能的實現類,Context的具體能力是由ContextImpl類去實現。那么Context到底可以實現哪些功能呢?這個就實在是太多了,彈出Toast、啟動Activity、啟動Service、發送廣播、操作數據庫等等等等都需要用到Context。
getApplicationContext()比getApplicatio()作用域會更廣一些,但實際上獲取的對象是同一個。
除了上面兩個方法之外,其實還有一個getBaseContext()方法,這個方法實際上獲得的是一個ContextImpl對象。ContextWrapper的attachBaseContext()方法其實是由系統來調用的,它會把ContextImpl對象作為參數傳遞到attachBaseContext()方法當中,從而賦值給mBase對象,之后ContextWrapper中的所有方法其實都是通過這種委托的機制交由ContextImpl去具體實現的,所以說ContextImpl是上下文功能的實現類是非常准確的。
內存泄漏的原因:
導致內存泄漏有很多原因,最常見的有內部類的使用,因為內部類持有外部引用。
還有例如:1.資源對象沒關閉造成的內存泄漏(源性對象比如(Cursor,File文件等)往往都用了一些緩沖,我們在不使用的時候,應該及時關閉它們,以便它們的緩沖及時回收內存) 2.構造Adapter時,沒有使用緩存的convertView 3.Bitmap對象不在使用時調用recycle()釋放內存(視情況而定) 4.使用Application context。這個context的生存周期和你的應用的生存周期一樣長,而不是取決於activity的生存周期。如果你想保持一個長期生存的對象,並且這個對象需要一個context,記得使用application對象。你可以通過調用 Context.getApplicationContext() or Activity.getApplication()來獲得 5.注冊沒取消造成的內存泄漏(一些Android程序可能引用我們的Anroid程序的對象(比如注冊機制)。即使我們的Android程序已經結束了,但是別的引用程序仍然還有對我們的Android程序的某個對象的引用,泄漏的內存依然不能被垃圾回收。調用registerReceiver后未調用unregisterReceiver。) 6.集合中對象沒清理造成的內存泄漏(我們通常把一些對象的引用加入到了集合中,當我們不需要該對象時,並沒有把它的引用從集合中清理掉,這樣這個集合就會越來越大。如果這個集合是static的話,那情況就更嚴重了。)
http://www.jackywang.tech/AndroidInterview-Q-A/chinese/android/%E4%BB%80%E4%B9%88%E6%83%85%E5%86%B5%E5%AF%BC%E8%87%B4%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F-%E7%BE%8E%E5%9B%A2.html
oom和內存泄漏
內存泄漏:就是存在一些被分配的對象,這些對象有下面兩個特點,首先,這些對象是可達的,及有引用指向,存在通路可以與其相連;其次,這些對象是無用的,即程序以后不會再使用這些對象。如果對象滿足這兩個條件,這些對象就可以判定為Java中的內存泄漏,這些對象不會被GC所回收,然而它卻占用內存。
內存泄露的原因:
1.資源對象沒關閉造成的內存泄漏,如查詢數據庫后沒有關閉游標cursor
2.構造Adapter時,沒有使用 convertView 重用
3.Bitmap對象不在使用時調用recycle()釋放內存
4.對象被生命周期長的對象引用,如activity被靜態集合引用導致activity不能釋放
內存泄漏示例:非靜態的內部類和匿名內部類都會隱式地持有其外部類的引用。靜態的內部類不會持有外部類的引用
不會導致內存泄露的代碼如下:
當這個Activity退出時消息隊列中還有未處理的消息或者正在處理消息,而消息隊列中的Message持有mHandler實例的引用,mHandler又持有Activity的引用,所以導致該Activity的內存資源無法及時回收,引發內存泄漏
https://www.androiddesignpatterns.com/2013/01/inner-class-handler-memory-leak.html
四種引用的區別
1、強引用
通常我們編寫的代碼都是強引用,eg :Object obj = new Object();當內存空間不足,Java虛擬機寧願拋出OutOfMemoryError錯誤,使程序異常終止,也不會靠隨意回收具有強引用的對象來解決內存不足問題。比如你創建一個很長的數組Object[] objArr = new Object[10000];當運行至這句時,如果內存不足,JVM會拋出OOM錯誤也不會回收數組中的object對象。不過要注意的是,當方法運行完之后,數組和數組中的對象都已經不存在了,所以它們指向的對象都會被JVM回收。
2、軟引用
只要有足夠的內存,就一直保持對象。一般可用來實現緩存
,通過java.lang.ref.SoftReference類實現。內存非常緊張的時候會被回收,其他時候不會被回收,所以在使用之前需要判空,從而判斷當前時候已經被回收了。
3、弱引用
通過java.lang.ref.WeakReference或java.util.WeakHashMap類實現,eg : WeakReference p = new WeakReference(new Person(“Rain”));不管內存是否足夠,系統垃圾回收時必定會回收。
4、虛引用
不能單獨使用,主要是用於追蹤對象被垃圾回收的狀態。通過java.lang.ref.PhantomReference類和引用隊列ReferenceQueue類聯合使用實現。
如何打多渠道包
在AndroidMainfest.xml配置相應的渠道
<meta-data android:value="UMENG_CHANNEL" android:name="${UMENG_CHANNEL_VALUE}"/> <!--動態更改渠道號-->
在build.gradle中配置渠道信息和自動替換腳本
//多渠道打包 productFlavors { xiaomi {} huawei {} yingyongbao {} wandoujia {} }
//自動替換清單文件中的渠道號 productFlavors.all { flavor -> flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name] }
比如會問在一個工作線程中創建自己的消息隊例應該怎么做
Looper.prepare(在每個線程只允許執行一次)
handler
handler通過post(Runnable)和sendMessage(Message)傳遞Runnable和Message,最后調用的是sendMessageAtTime
MessageQueue核心方法:enqueueMessage和next(實現原理:synchronized 和 for(;;))
Looper核心方法:Looper.prepare()和Looper.loop(),其中loop()方法里面是一個for(;;)死循環
msg.target = this
Android中為什么主線程不會因為Looper.loop()里的死循環卡死?
事實上,會在進入死循環之前便創建了新binder線程,在代碼ActivityThread.main()中:thread.attach(false);便會創建一個Binder線程(具體是指ApplicationThread,Binder的服務端,用於接收系統服務AMS發送來的事件),該Binder線程通過Handler將Message發送給主線程。對於主線程,我們是絕不希望會被運行一段時間,自己就退出,那么如何保證能一直存活呢?簡單做法就是可執行代碼是能一直執行下去的,死循環便能保證不會被退出,例如,binder線程也是采用死循環的方法,通過循環方式不同與Binder驅動進行讀寫操作,當然並非簡單地死循環,無消息時會休眠。
主線程的死循環一直運行是不是特別消耗CPU資源呢? 其實不然,這里就涉及到Linux pipe/epoll機制**,簡單說就是在主線程的MessageQueue沒有消息時,便阻塞在loop的queue.next()中的nativePollOnce()方法里,此時主線程會釋放CPU資源進入休眠狀態,直到下個消息到達或者有事務發生,通過往pipe管道寫端寫入數據來喚醒主線程工作。
鏈式存儲結構和順序存儲結構
順序表適宜於做查找這樣的靜態操作;鏈表宜於做插入、刪除這樣的動態操作。
若線性表的長度變化不大,且其主要操作是查找,則采用順序表;
若線性表的長度變化較大,且其主要操作是插入、刪除操作,則采用鏈表。
順序表平均需要移動近一半元素
鏈表不需要移動元素,只需要修改指針
MVP(要特別熟悉,能說出優點)
任何事務都存在兩面性,MVP當然也不列外,我們來看看MVP的優缺點。
優點:
-
1.降低耦合度,實現了Model和View真正的完全分離,可以修改View而不影響Modle
-
2.模塊職責划分明顯,層次清晰
-
3.Presenter可以復用,一個Presenter可以用於多個View,而不需要更改Presenter的邏輯(當然是在View的改動不影響業務邏輯的前提下)
-
4.利於測試驅動開發。以前的Android開發是難以進行單元測試的(雖然很多Android開發者都沒有寫過測試用例,但是隨着項目變得越來越復雜,沒有測試是很難保證軟件質量的;而且近幾年來Android上的測試框架已經有了長足的發展——開始寫測試用例吧),在使用MVP的項目中Presenter對View是通過接口進行,在對Presenter進行不依賴UI環境的單元測試的時候。可以通過Mock一個View對象,這個對象只需要實現了View的接口即可。然后依賴注入到Presenter中,單元測試的時候就可以完整的測試Presenter應用邏輯的正確性。
-
5.View可以進行組件化。在MVP當中,View不依賴Model。這樣就可以讓View從特定的業務場景中脫離出來,可以說View可以做到對業務完全無知。它只需要提供一系列接口提供給上層操作。這樣就可以做到高度可復用的View組件。
-
6.代碼靈活性
缺點: -
1.Presenter中除了應用邏輯以外,還有大量的View->Model,Model->View的手動同步邏輯,造成Presenter比較笨重,維護起來會比較困難。
-
2.由於對視圖的渲染放在了Presenter中,所以視圖和Presenter的交互會過於頻繁。
-
3.如果Presenter過多地渲染了視圖,往往會使得它與特定的視圖的聯系過於緊密。一旦視圖需要變更,那么Presenter也需要變更了。
-
4.額外的代碼復雜度及學習成本。
組件化方案和思想
組件化開發就是將一個app分成多個模塊,每個模塊都是一個組件(Module),開發的過程中我們可以讓這些組件相互依賴或者單獨調試部分組件等,但是最終發布的時候是將這些組件合並統一成一個apk,這就是組件化開發。
組件中提供common庫:
1.我們將給給整個工程提供統一的依賴第三方庫的入口,前面介紹的Common庫的作用之一就是統一依賴開源庫,因為其他業務組件都依賴了Common庫,所以這些業務組件也就間接依賴了Common所依賴的開源庫。
2.在Common組件中我們封裝了項目中用到的各種Base類,這些基類中就有BaseApplication 類。
Gradle構建工具在組件的build.gradle中有如下用法:
//設置了resourcePrefix值后,所有的資源名必須以指定的字符串做前綴,否則會報錯。 //但是resourcePrefix這個值只能限定xml里面的資源,並不能限定圖片資源,所有圖片資源仍然需要手動去修改資源名。 resourcePrefix "girls_"
http://blog.csdn.net/guiying712/article/details/55213884
做SDK類項目的要求
1.暴露的接口盡可能少,最大程度減少 SDK 接入方需要了解的細節。
2.license
3.調用盡可能簡單
4.library,aar兩種
aar和jar的區別
aar:.aar文件中包含所有資源,class以及res資源文件。
jar:只包含了class文件與清單文件 ,不包含資源文件,如圖片等所有res中的文件。
如果你只是一個簡單的類庫那么使用生成的.jar文件即可;如果你的是一個UI庫,包含一些自己寫的控件布局文件以及字體等資源文件那么就只能使用*.aar文件。
性能優化(分類優化:UI,內存,cpu,電量,網絡)
ui優化:約束布局
內存優化:上下文,handler,線程,單例的,圖片,native,java(不保留活動 java堆內存)
cpu優化:看電話熱不熱
電量優化: 大的東西一直不放
網絡優化:gip壓縮,圖片webp,策略
1.在發現某個頁面或者操作會卡頓時,可以使用 TraceView 定位問題代碼。它可以加載 trace 文件,用圖形的形式展示代碼的執行時間、次數及調用棧,便於我們分析。
2.查看布局深度:HierarchyViewer工具
3.Systrace在一些分析顯示的問題上特別有用,如應用畫圖慢,顯示動作或動畫時變形等。Systrace會自動分析信息,將性能問題直接以alerts的方式高亮顯示,我們只要修改這些alerts就可以。如果我們想要知道方法的更詳細信息,我們可以結合Traceview來解決問題。
http://developer.51cto.com/art/201511/496263.htm(35個代碼優化小細節)
http://www.jackywang.tech/AndroidInterview-Q-A/chinese/subject/%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96.html (性能優化)
VectorDrawable for smaller APKs 針對不同的分辨率提供多張精度的圖片會額外增加APK的大小,針對這個問題的解決方案是考慮使用VectorDrawable,它僅僅只需要一個文件,能夠動態生成對應分辨率的圖片。
Using LINT for Performance Tips Lint已經集成到Android Studio中了,我們可以手動去觸發這個工具,點擊工具欄的Analysis -> Inspect Code,觸發之后,Lint會開始工作,並把結果輸出到底部的工具欄,我們可以逐個查看原因並根據指示做相應的優化修改。
Tool: Strict Mode Android提供了一個叫做Strict Mode的工具,我們可以通過手機設置里面的開發者選項,打開Strict Mode選項,如果程序存在潛在的隱患,屏幕就會閃現紅色。我們也可以通過StrictMode API在代碼層面做細化的跟蹤,可以設置StrictMode監聽那些潛在問題,出現問題時如何提醒開發者,可以對屏幕閃紅色,也可以輸出錯誤日志。
匿名內部類
調用方式
new 父類構造器(參數列表)|實現接口() { //匿名內部類的類體部分 }
當所在的方法的形參需要被內部類里面使用時,該形參必須為final。
在這里我們看到使用匿名內部類我們必須要繼承一個父類或者實現一個接口,這個引用是隱式的。由於匿名內部類不能是抽象類,所以它必須要實現它的抽象父類或者接口里面所有的抽象方法。對於匿名內部類的使用它是存在一個缺陷的,就是它僅能被使用一次,創建匿名內部類時它會立即創建一個該類的實例,該類的定義會立即消失,所以匿名內部類是不能夠被重復使用。
在使用匿名內部類的過程中,我們需要注意如下幾點:
1、使用匿名內部類時,我們必須是繼承一個類或者實現一個接口,但是兩者不可兼得,同時也只能繼承一個類或者實現一個接口。
2、匿名內部類中是不能定義構造函數的。
3、匿名內部類中不能存在任何的靜態成員變量和靜態方法。
4、匿名內部類為局部內部類,所以局部內部類的所有限制同樣對匿名內部類生效。
5、匿名內部類不能是抽象的,它必須要實現繼承的類或者實現的接口的所有抽象方法。
匿名內部類初始化:
使用構造代碼塊!利用構造代碼塊能夠達到為匿名內部類創建一個構造器的效果。
多線程(同步,原子類,原子操作)
同步:
1.同步方法 synchronized
2.同步代碼塊 synchronized
3.使用特殊域變量(volatile)實現線程同步
a.volatile關鍵字為域變量的訪問提供了一種免鎖機制,
b.使用volatile修飾域相當於告訴虛擬機該域可能會被其他線程更新,
c.因此每次使用該域就要重新計算,而不是使用寄存器中的值
d.volatile不會提供任何原子操作,它也不能用來修飾final類型的變量
4.lock() : 獲得鎖 unlock() : 釋放鎖
5.ThreadLocal 類的常用方法
6.使用阻塞隊列實現線程同步
使用LinkedBlockingQueue來實現線程的同步
7.使用原子變量實現線程同步
原子類:
在java的util.concurrent.atomic包中提供了創建了原子類型變量的工具類,使用該類可以簡化線程同步。其中AtomicInteger 表可以用原子方式更新int的值。(AtomicInteger,AtomicBoolean,AtomicLong)
原子操作:
原子操作就是指將讀取變量值、修改變量值、保存變量值看成一個整體來操作即-這幾種行為要么同時完成,要么都不完成。
多線程CAS算法
CAS(Compare and Swap),即比較並替換。CAS的思想很簡單:三個參數,一個當前內存值V、舊的預期值A、即將更新的值B,當且僅當預期值A和內存值V相同時,將內存值修改為B並返回true,否則什么都不做,並返回false。
獨占鎖是一種悲觀鎖,synchronized就是一種獨占鎖,會導致其它所有需要鎖的線程掛起,等待持有鎖的線程釋放鎖。而另一個更加有效的鎖就是樂觀鎖。所謂樂觀鎖就是,每次不加鎖而是假設沒有沖突而去完成某項操作,如果因為沖突失敗就重試,直到成功為止。樂觀鎖用到的機制就是CAS,Compare and Swap。
CAS雖然很高效的解決原子操作,但是CAS仍然存在三大問題
-
ABA問題。因為CAS需要在操作值的時候檢查下值有沒有發生變化,如果沒有發生變化則更新,但是如果一個值原來是A,變成了B,又變成了A,那么使用CAS進行檢查時會發現它的值沒有發生變化,但是實際上卻變化了。ABA問題的解決思路就是使用版本號。在變量前面追加上版本號,每次變量更新的時候把版本號加一,那么A-B-A 就會變成1A-2B-3A。
從Java1.5開始JDK的atomic包里提供了一個類AtomicStampedReference來解決ABA問題。這個類的compareAndSet方法作用是首先檢查當前引用是否等於預期引用,並且當前標志是否等於預期標志,如果全部相等,則以原子方式將該引用和該標志的值設置為給定的更新值。 -
循環時間長開銷大。自旋CAS如果長時間不成功,會給CPU帶來非常大的執行開銷。如果JVM能支持處理器提供的pause指令那么效率會有一定的提升,pause指令有兩個作用,第一它可以延遲流水線執行指令(de-pipeline),使CPU不會消耗過多的執行資源,延遲的時間取決於具體實現的版本,在一些處理器上延遲時間是零。第二它可以避免在退出循環的時候因內存順序沖突(memory order violation)而引起CPU流水線被清空(CPU pipeline flush),從而提高CPU的執行效率。
-
只能保證一個共享變量的原子操作。當對一個共享變量執行操作時,我們可以使用循環CAS的方式來保證原子操作,但是對多個共享變量操作時,循環CAS就無法保證操作的原子性,這個時候就可以用鎖,或者有一個取巧的辦法,就是把多個共享變量合並成一個共享變量來操作。比如有兩個共享變量i=2,j=a,合並一下ij=2a,然后用CAS來操作ij。從Java1.5開始JDK提供了AtomicReference類來保證引用對象之間的原子性,你可以把多個變量放在一個對象里來進行CAS操作。
線程阻塞(sleep、await、yield)
wait是Object類的方法,sleep和yield是Thread類的靜態方法。
await:wait( )方法導致當前線程進入等待狀態直到它被通知(其他線程調用notify或notifyAll方法。notify/notifyAll方法解除等待線程的阻塞狀態)。wait( )后,線程會釋放掉它所占有的對象的鎖,從而使線程所在對象中的其它synchronized數據可被別的線程使用。wait方法只能在一個同步方法中調用。
sleep:sleep(long millis)可能使任意優先級的其他線程得到執行機會。sleep(long millis)不會釋放鎖。調用sleep方法的線程在喚醒之后不保證能獲取到CPU,它會先進入就緒態,與其他線程競爭CPU。
yield:yield方法也不會釋放鎖。yield只能把CPU讓給相同優先級的其他線程,而不會把CPU給更高或更低優先級的其他線程。若此時沒有其他線程跟它在有一個優先級,則該線程繼續獲得CPU時間,因此可能某線程剛調用yield方法又馬上被執行。
https://www.jianshu.com/p/ff8f2e5906fd
suspend()和resume()必須要成對出現,否則非常容易發生死鎖。
因為suspend方法並不會釋放鎖,如果使用suspend的目標線程對一個重要的系統資源持有鎖,那么沒任何線程可以使用這個資源直到要suspend的目標線程被resumed,如果一個線程在resume目標線程之前嘗試持有這個重要的系統資源鎖再去resume目標線程,這兩條線程就相互死鎖了,也就凍結線程。
http://blog.csdn.net/woaigaolaoshi/article/details/51298759
synchronized和lock
在JDK1.5中,synchronized是性能低效的。 到了JDK1.6,發生了變化,對synchronize加入了很多優化措施,有自適應自旋,鎖消除,鎖粗化,輕量級鎖,偏向鎖等等。導致在JDK1.6上synchronize的性能並不比Lock差。官方也表示,他們也更支持synchronize。
好多android原生里面用的都是synchronize,比如handler里面
synchronized
1.對於同步方法,鎖是當前實例對象。
2.對於靜態同步方法,鎖是當前對象的Class對象。
3.對於同步方法塊,鎖是Synchonized括號里配置的對象。
死鎖
如何避免:避免嵌套封鎖、只對有請求的進行封鎖(針對最需要的地方進行鎖)、避免無限期的等待
suspend()和resume()必須要成對出現,否則非常容易發生死鎖。
android 多線程
Activity.runOnUiThread(Runnable)
View.post(Runnable),View.postDelay(Runnable,long):https://www.jianshu.com/p/b1d5e31e2011
調用View.post()既方便又可以保證指定的任務在視圖操作中順序執行。
Handler
AsyncTask
使用線程池的原因
1.無線創建線程的不足
在生產環境中,為每一個任務都分配一個線程這種方法存在一些缺陷:
a.線程生命周期的開銷:線程的創建與銷毀都會消耗大量資源,頻繁創建與銷毀線程會帶來很大的資源開銷
b.資源消耗:活躍的線程會消耗系統資源。如果可運行的線程數量大於可用的處理器數量,閑置的線程會占用許多內存,並且頻繁的線程上下文切換也會帶來很大的性能開銷
c.穩定性:操作系統在可創建的線程數量上有一個限制。在高負載情況下,應用程序很有可能突破這個限制,資源耗盡后很可能拋出OutOfMemoryError異常注
一般而言,對於線程數為最小值的線程池,一個新線程一旦創建出來,至少應該保留幾分鍾,以處理任何負載飆升。空閑時間應該以分鍾計,而且至少在10分鍾到30分鍾之間,這樣可以防止頻繁創建線程。
2.提高響應速度
任務到達時,不再需要創建線程就可以立即執行
3.線程池提供了管理線程的功能
比如,可以統計任務的完成情況,統計活躍線程與閑置線程的數量等
threadlocal
可以總結為一句話:ThreadLocal的作用是提供線程內的局部變量,這種變量在線程的生命周期內起作用,減少同一個線程內多個函數或者組件之間一些公共變量的傳遞的復雜度。
三個方法:get,set,init
參考:https://www.jianshu.com/p/f4ed71560771
Java 自動裝箱拆箱
目的是將原始類型值轉自動地轉換成對應的對象,Java1.5下進行過編程的話,你一定不會陌生這一點,你不能直接地向集合(Collections)中放入原始類型值,因為集合只接收對象。
自動裝箱就是Java自動將原始類型值轉換成對應的對象,比如將int的變量轉換成Integer對象,這個過程叫做裝箱,反之將Integer對象轉換成int類型值,這個過程叫做拆箱
自動裝箱時編譯器調用valueOf將原始類型值轉換成對象,同時自動拆箱時,編譯器通過調用類似intValue(),doubleValue()這類的方法將對象轉換成原始類型值。
自動裝箱有一個問題,那就是在一個循環中進行自動裝箱操作的情況,如下面的例子就會創建多余的對象,影響程序的性能。如下列例子會創建4000多個對象
Integer sum = 0; for(int i=1000; i<5000; i++){ sum+=i; }
因為自動裝箱會隱式地創建對象,像前面提到的那樣,如果在一個循環體中,會創建無用的中間對象,這樣會增加GC壓力,拉低程序的性能。所以在寫循環時一定要注意代碼,避免引入不必要的自動裝箱操作。
Java 按值傳遞按引用傳遞
指的是在方法調用時,傳遞的參數是按值的拷貝傳遞。
public class TempTest { private void test1(int a){ a = 5; System.out.println("test1方法中的a==="+a); } public static void main(String[] args) { TempTest t = new TempTest(); int a = 3; t.test1(a);//傳遞后,test1方法對變量值的改變不影響這里的a System.out.println(”main方法中的a===”+a); } } 運行結果是: test1方法中的a===5 main方法中的a===3
Java里面只有基本類型和按照下面這種定義方式的String是按值傳遞,其它的都是按引用傳遞。就是直接使用雙引號定義字符串方式:String str = “J
Java中的字符串常量池
String str1 = "droid";
JVM檢測這個字面量,這里我們認為沒有內容為droid的對象存在。JVM通過字符串常量池查找不到內容為droid的字符串對象存在,那么會創建這個字符串對象,然后將剛創建的對象的引用放入到字符串常量池中,並且將引用返回給變量str1。
String str3 = new String("droid");
當我們使用了new來構造字符串對象的時候,不管字符串常量池中有沒有相同內容的對象的引用,新的字符串對象都會創建。
https 原理
加密、解密、密鑰、加密算法(非對稱加密和hash算法)、證書
1、 客戶端發起一個https的請求,把自身支持的一系列Cipher Suite(密鑰算法套件,簡稱Cipher)發送給服務端
2、服務端,接收到客戶端所有的Cipher后與自身支持的對比,如果不支持則連接斷開,反之則會從中選出一種加密算法和HASH算法,以證書的形式返回給客戶端 證書中還包含了 公鑰 頒證機構 網址 失效日期等等
3、客戶端收到了服務器發來的數據包后,會做這么幾件事情:
1)驗證一下證書是否合法。一般來說,證書是用來標示一個站點是否合法的標志。如果說該證書由權威的第三方頒發和簽名的,則說明證書合法。
2)如果證書合法,或者客戶端接受和信任了不合法的證書,則客戶端就會隨機產生一串序列號,使用服務器發來的公鑰進行加密。
3) 用最開始約定好的HASH方式,把握手消息取HASH值, 然后用 隨機數加密 “握手消息+握手消息HASH值(簽名)” 並一起發送給服務端
4、服務器接受到客戶端發來的消息后,會做這么幾件事情:
1)使用私鑰解密上面第2)中公鑰加密的消息,得到客戶端產生的隨機序列號。
2)使用該隨機序列號,對該消息進行加密,驗證的到的校驗值是否與客戶端發來的一致。如果一致則說明消息未被篡改,可以信任。
3)最后,使用該隨機序列號,加上之前第2步中選擇的加密算法,加密一段握手消息,發還給客戶端。同時HASH值也帶上。( 在這里之所以要取握手消息的HASH值,主要是把握手消息做一個簽名,用於驗證握手消息在傳輸過程中沒有被篡改過。)
5、客戶端收到服務器端的消息后,接着做這么幾件事情:
1)計算HASH值是否與發回的消息一致
2)檢查消息是否為握手消息
6、握手結束后,客戶端和服務器端使用握手階段產生的隨機數以及挑選出來的算法進行對稱加解密的傳輸。
客戶端如何驗證 證書的合法性?
-
驗證證書是否在有效期內。
在服務端面返回的證書中會包含證書的有效期,可以通過失效日期來驗證 證書是否過期 -
驗證證書是否被吊銷了。
被吊銷后的證書是無效的。驗證吊銷有CRL(證書吊銷列表)和OCSP(在線證書檢查)兩種方法。
證書被吊銷后會被記錄在CRL中,CA會定期發布CRL。應用程序可以依靠CRL來檢查證書是否被吊銷了。
CRL有兩個缺點,一是有可能會很大,下載很麻煩。針對這種情況有增量CRL這種方案。二是有滯后性,就算證書被吊銷了,應用也只能等到發布最新的CRL后才能知道。
增量CRL也能解決一部分問題,但沒有徹底解決。OCSP是在線證書狀態檢查協議。應用按照標准發送一個請求,對某張證書進行查詢,之后服務器返回證書狀態。
OCSP可以認為是即時的(實際實現中可能會有一定延遲),所以沒有CRL的缺點。 -
驗證證書是否是上級CA簽發的。
windows中保留了所有受信任的根證書,瀏覽器可以查看信任的根證書,自然可以驗證web服務器的證書,
是不是由這些受信任根證書頒發的或者受信任根證書的二級證書機構頒發的(根證書機構可能會受權給底下的中級證書機構,然后由中級證書機構頒發中級證書)
在驗證證書的時候,瀏覽器會調用系統的證書管理器接口對證書路徑中的所有證書一級一級的進行驗證,只有路徑中所有的證書都是受信的,整個驗證的結果才是受信
http和https的區別
http是無狀態的,實際上cancel一個請求就是說在邏輯上做一些處理,比如說不走onResponse回調
http:通常和tcp直接通信
通信使用明文(不加密), 內容可能會被竊聽
不驗證通信方的身份, 因此有可能遭遇偽裝
無法證明報文的完整性, 所有有可能已遭篡改
https:先和ssl通信再由ssl和tcp通信
HTTP+加密+認證+完整性保護 = HTTPS
TCP
位碼即tcp標志位,有6種標示:SYN(synchronous建立聯機) ACK(acknowledgement 確認) PSH(push傳送) FIN(finish結束) RST(reset重置) URG(urgent緊急)Sequence number(順序號碼) Acknowledge number(確認號碼)
1、建立連接協議(三次握手)
(1)客戶 端發送一個帶SYN標志的TCP報文到服務器。這是三次握手過程中的報文1。
(2) 服務器端回應客戶端的,這是三次握手中的第2個報文,這個報文同時帶ACK標志和SYN標志。因此它表示對剛才客戶端SYN報文的回應;同時又標志SYN給客戶端,詢問客戶端是否准備好進行數據通 訊。
(3) 客戶必須再次回應服務段一個ACK報文,這是報文段3。
2、連接終止協議(四次握手)
由於TCP連 接是全雙工的,因此每個方向都必須單獨進行關閉。這原則是當一方完成它的數據發送任務后就能發送一個FIN來終止這個方向的連接。收到一個 FIN只意味着這一方向上沒有數據流動,一個TCP連接在收到一個FIN后仍能發送數據。首先進行關閉的一方將執行主動關閉,而另一方執行被動關閉。
(1) TCP客 戶端發送一個FIN,用來關閉客戶到服務器的數據傳送(報文段4)。
(2) 服務器收到這個FIN,它發回一個ACK,確認序號為收到的序號加1(報文段5)。和SYN一樣,一個FIN將占用一個序號。
(3) 服務器關閉客戶端的連接,發送一個FIN給客戶端(報文段6)。
(4) 客戶段發回ACK報文確認,並將確認序號設置為收到序號加1(報文段7)。
TCP建立連接的三次握手過程,以及關閉連接的四次握手過程
設計模式在安卓中的使用
1.單例模式
Android中最常見的單例是Application類,它全局只有一個。不過Application的構造函數並非是私有的,也就是說我們可以new一個新的Application對象,因此Application這個類並不符合單例的規范。但是,即使我們重新new出來一個Application,它並沒有綁定相關的上下文,也會是無效的,也就保證了全局只有一個有效的Application對象。
2.觀察者模式
觀察者模式在Android中無處不在,Android中的各種Listener,單擊、雙擊、觸摸事件的監聽處理,都是觀察者模式。另外ContentObserver、DataSetObserver這些用於數據變化觀察的類也屬於觀察者模式。
3.適配器模式
通過ListView或GridView的Adapter,不同的數據源都可以向同一個ListView或GridView提供數據,Adapter起到的就是適配的功能。
4.代理模式
Android大量使用了代理模式來向用戶層提供系統服務。由於系統服務是運行在單獨的遠程進程中,Android系統通過Binder為遠程服務提供了代理對象,應用可以通過代理對象來間接的訪問系統服務。比如ActivityManagerService的主要作用是進行Activity的管理,用戶無法直接訪問ActivityManagerService,系統為我們提供了一個代理對象ActivityManager,通過它就可以獲得ActivityManagerService提供的相關功能。
5.工廠方法模式
工廠方法模式在Android中比較明顯的使用就是BitmapFactory,通過各種decodeXXX()方法就可以用不同方式獲得Bitmap對象。
多進程:優缺點,多進程時其他進程能否回收
進程:是一個具有獨立功能的程序關於某個數據集合的一次運行活動。進程是系統進行資源分配和調度的一個獨立單位。
線程:線程比進程更小,基本上不擁有系統資源,故對它的調度所用資源小,能更高效的提高系統內多個程序間並發執行的程度。線程是進程的一個實體,是CPU調度和分派的基本單位,它是比進程更小的能獨立運行的基本單位.
<service android:name="com.qihoo.security.services.ScanEngineService" android:process=":engine"/>
設置android:process屬性,要注意:如果是android:process=”:deamon”,以:開頭的名字,表示這是一個應用程序的私有進程,否則它是一個全局進程。私有進程的進程名稱是會在冒號前自動加上包名,而全局進程則不會。一般我們都是有私有進程,很少使用全局進程。
進程間的內存空間是不可見的。開啟多進程后,會引發以下問題:
1)Application的多次重建。
2)靜態成員的失效。
3)文件共享問題。(多進程情況下會出現兩個進程在同一時刻訪問同一個數據庫文件的情況。解決辦法就是多進程的時候不並發訪問同一個文件,比如子進程涉及到操作數據庫,就可以考慮調用主進程進行數據庫的操作。)
4)斷點調試問題。
加密算法
非對稱加密算法:RSA, DSA/DSS
對稱加密算法: AES, 3DES
HASH算法:MD5, SHA1, SHA256
兩個程序間如何共享數據
1.aidl
2.可以安排兩個應用共享同一 Linux 用戶 ID,在這種情況下,它們能夠相互訪問彼此的文件(比如data目錄、組件信息等)。為了節省系統資源,可以安排具有相同用戶 ID 的應用在同一 Linux 進程中運行,並共享同一 VM,而這時兩個應用還要必須使用相同的證書簽署。
泛型中 extends 和 super 的區別?
上界通配符:Plate<? extends Fruit> = new Plate(new Apple());
下界通配符:Plate<? super Fruit> = new Plate(new Food());
頻繁往外讀取內容的,適合用上界Extends。經常往里插入的,適合用下界Super。
HashMap的實現原理
數組和鏈表
https://www.cnblogs.com/chengxiao/p/6059914.html
HashMap幾乎可以等價於Hashtable,除了HashMap是非synchronized的,並可以接受null(HashMap可以接受為null的鍵值(key)和值(value),而Hashtable則不行)。
哈希表:相比上述幾種數據結構,在哈希表中進行添加,刪除,查找等操作,性能十分之高,不考慮哈希沖突的情況下,僅需一次定位即可完成,時間復雜度為O(1),接下來我們就來看看哈希表是如何實現達到驚艷的常數階O(1)的。
我們知道,數據結構的物理存儲結構只有兩種:順序存儲結構和鏈式存儲結構(像棧,隊列,樹,圖等是從邏輯結構去抽象的,映射到內存中,也這兩種物理組織形式),而在上面我們提到過,在數組中根據下標查找某個元素,一次定位就可以達到,哈希表利用了這種特性,哈希表的主干就是數組。
比如我們要新增或查找某個元素,我們通過把當前元素的關鍵字 通過某個函數映射到數組中的某個位置,通過數組下標一次定位就可完成操作。
Parcelable比Serializable:
Parcelable比Serializable性能高,所以應用內傳遞數據推薦使用Parcelable,但是Parcelable不能使用在要將數據存儲在磁盤上的情況,因為Parcelable不能很好的保證數據的持續性在外界有變化的情況下。盡管Serializable效率低點,但此時還是建議使用Serializable
Parcel類是一種最快的序列化/反序列化機制,專為Android中的進程間通信而設計。該類提供了一些方法來將成員容納到容器中,以及從容器展開成員。
現在我們知道了如何傳遞自定義的對象,那么在兩個Activity之前傳遞對象還要注意什么呢?
一定要要注意對象的大小,Intent中的Bundle是在使用Binder機制進行數據傳遞的,能使用的Binder的緩沖區是有大小限制的(有些手機是2M),而一個進程默認有16個binder線程,所以一個線程能占用的緩沖區就更小了(以前做過測試,大約一個線程可以占用128KB)。所以當你看到“The Binder transaction failed because it was too large.”這類TransactionTooLargeException異常時,你應該知道怎么解決了。因此,使用Intent在Activity之間傳遞List和Bitmap對象是有風險的。
多態
多態的作用:消除類型之間的耦合關系。
多態存在的三個必要條件 一、要有繼承; 二、要有重寫; 三、父類引用指向子類對象。
classLoader
引導類加載器(bootstrap class loader)
—用來加載java 的核心庫(String 、Integer、List。。。)在jre/lib/rt.jar路徑下的內容,是用C代碼來實現的,並不繼承自java.lang.ClassLoader。
—加載擴展類和應用程序類加載器。並指定他們的父類加載器。
擴展類加載器(extensions class loader)
—用來加載java的擴展庫(jre/ext/*.jar路徑下的內容)java虛擬機的實現會自動提供一個擴展目錄。該類加載器在此目錄里面查找並加載java類。
應用程序類加載器(application class loader)
—它根據java應用的類路徑(classpath路徑),一般來說,java應用的類都是由它來完成加載的。
自定義類加載器
—開發人員可以通過繼承java.lang.ClassLoader類的方式實現自己的類加載器,以滿足一些特殊的需求。
Java 虛擬機不僅要看類的全名是否相同,還要看加載此類的類加載器是否一樣。
雙親委派過程:當一個類加載器收到類加載任務時,立即將任務委派給它的父類加載器去執行,直至委派給最頂層的啟動類加載器為止。如果父類加載器無法加載委派給它的類時,將類加載任務退回給它的下一級加載器去執行;除了啟動類加載器以外,每個類加載器擁有一個父類加載器,用戶的自定義類加載器的父類加載器是AppClassLoader;雙親委派模型可以保證全限名指定的類,只被加載一次;雙親委派模型不具有強制性約束,是Java設計者推薦的類加載器實現方式;
https://segmentfault.com/a/1190000008995781
事件傳遞
所有的Touch事件都封裝到MotionEvent里面
事件處理包括三種情況,分別為:傳遞—-dispatchTouchEvent()函數、攔截——onInterceptTouchEvent()函數、消費—-onTouchEvent()函數和OnTouchListener
事件類型分為ACTION_DOWN, ACTION_UP, ACTION_MOVE, ACTION_POINTER_DOWN, ACTION_POINTER_UP, ACTION_CANCEL等,每個事件都是以ACTION_DOWN開始ACTION_UP結束
Android事件傳遞流程:ong
事件都是從Activity.dispatchTouchEvent()開始傳遞
事件由父View傳遞給子View,ViewGroup可以通過onInterceptTouchEvent()方法對事件攔截,停止其向子view傳遞
如果事件從上往下傳遞過程中一直沒有被停止,且最底層子View沒有消費事件,事件會反向往上傳遞,這時父View(ViewGroup)可以進行消費,如果還是沒有被消費的話,最后會到Activity的onTouchEvent()函數。
如果View沒有對ACTION_DOWN進行消費,之后的其他事件不會傳遞過來,也就是說ACTION_DOWN必須返回true,之后的事件才會傳遞進來
OnTouchListener優先於onTouchEvent()對事件進行消費
Java並發編程-阻塞隊列(BlockingQueue)的實現原理
http://blog.csdn.net/chenchaofuck1/article/details/51660119
LinkedBlockingQueue的一個方法,通過Lock實現阻塞
/** * @throws NullPointerException {@inheritDoc} */ public boolean offerLast(E e) { if (e == null) throw new NullPointerException(); Node<E> node = new Node<E>(e); final ReentrantLock lock = this.lock; lock.lock(); try { return linkLast(node); } finally { lock.unlock(); } }
1.ArrayBlockingQueue:是一個基於數組結構的有界阻塞隊列,此隊列按 FIFO(先進先出)原則對元素進行排序。
2.LinkedBlockingQueue:一個基於鏈表結構的阻塞隊列,此隊列按FIFO (先進先出) 排序元素,吞吐量通常要高於ArrayBlockingQueue。靜態工廠方法Executors.newFixedThreadPool()使用了這個隊列。
3.SynchronousQueue:一個不存儲元素的阻塞隊列。每個插入操作必須等到另一個線程調用移除操作,否則插入操作一直處於阻塞狀態,吞吐量通常要高於LinkedBlockingQueue,靜態工廠方法Executors.newCachedThreadPool使用了這個隊列。
4.PriorityBlockingQueue:一個具有優先級的無限阻塞隊列。
postInvalidate() 和 invalidate()
利用invalidate()刷新界面
實例化一個Handler對象,並重寫handleMessage方法調用invalidate()實現界面刷新;而在線程中通過sendMessage發送界面更新消息。
使用postInvalidate()刷新界面
使用postInvalidate則比較簡單,不需要handler,直接在線程中調用postInvalidate即可。
Service
1.手動調用startService()啟動服務,自動調用內部方法:onCreate()、onStartCommand(),如果一個Service被startService()多次啟動,那么onCreate()也只會調用一次。
2.手動調用stopService()關閉服務,自動調用內部方法:onDestory(),如果一個Service被啟動且被綁定,如果在沒有解綁的前提下使用stopService()關閉服務是無法停止服務的。
3.手動調用bindService()后,自動調用內部方法:onCreate()、onBind()。
4.手動調用unbindService()后,自動調用內部方法:onUnbind()、onDestory()。
5.startService()和stopService()只能開啟和關閉Service,無法操作Service,調用者退出后Service仍然存在;bindService()和unbindService()可以操作Service,調用者退出后,Service隨着調用者銷毀。
Receiver
兩種注冊類型的區別是:
a.第一種是常駐型廣播,也就是說當應用程序關閉后,如果有信息廣播來,程序也會被系統調用自動運行。
b.第二種不是常駐廣播,也就是說廣播跟隨程序的生命周期。
因廣播數據在本應用范圍內傳播,不用擔心隱私數據泄露的問題。 不用擔心別的應用偽造廣播,造成安全隱患。 相比在系統內發送全局廣播,它更高效。
Android數字簽名
數字證書都是有有效期的,Android只是在應用程序安裝的時候才會檢查證書的有效期。如果程序已經安裝在系統中,即使證書過期也不會影響程序的正常功能。
廣播接受者的生命周期
廣播接收者的生命周期非常短。當執行onRecieve方法之后,廣播就會銷毀
在廣播接受者不能進行耗時較長的操作
在廣播接收者不要創建子線程。廣播接收者完成操作后,所在進程會變成空進程,很容易被系統回收
Activity Window View三者的差別
Activity像一個工匠(控制單元),Window像窗戶(承載模型),View像窗花(顯示視圖) LayoutInflater像剪刀,Xml配置像窗花圖紙。
網絡通信流程
正常的通信流程是:http請求——dns解析——獲取到ip——socket通信
數據結構:二叉樹:深度遍歷 廣度遍歷 求高度
二叉樹:對一棵相對平衡的有序二叉樹,對其進行插入,查找,刪除等操作,平均復雜度均為O(logn)。
[圖片上傳失敗…(image-ad0cd2-1536496112163)]
台階問題 兩個棧實現隊列 一個數組,求數組元素連續求和最大值
台階問題:
當n>2時,第一次跳的時候就有兩種選擇:一是第一次只跳1級,此時跳法數目等於后面剩下的n-1級台階的跳法數目,即f(n-1);另外一種就是第一次跳兩級,此時跳法數目等於后面剩下的n-2級台階的跳法數目,即為f(n-2)。因此n級台階時的不同跳法的總數f(n)= f(n-1) + f(n-2)
public long JumpFloor(long target) { if(target==1) return 1; if(target==2) return 2; long a = 2; long b = 1; long sum = 0; for(long i=3;i<=target;i++){ sum = a + b; b = a; a = sum; } return sum; }
Retroit
Retrofit:
Actually, it has one simple reason: we want to use the same objects (OkHttpClient, Retrofit, …) throughout the app to just open one socket connection that handles all the request and responses including caching and many more. It’s common practice to just use one OkHttpClient instance to reuse open socket connections. That means, we either need to inject the OkHttpClient to this class via dependency injection or use a static field. As you can see, we chose to use the static field. And because we use the OkHttpClient throughout this class, we need to make all fields and methods static.
插件化方案
插件可以提供一種動態擴展能力,使得應用程序在運行時加載原本不屬於該應用的功能,並且做到動態更新和替換。
1.讓用戶不用重新安裝 APK 就能升級應用功能,減少發版本頻率,增加用戶體驗。
2.提供一種快速修復線上 BUG 和更新的能力。
3.按需加載不同的模塊,實現靈活的功能配置,減少服務器對舊版本接口兼容壓力。
4.模塊化、解耦合、並行開發、 65535 問題。
熱修復
有沒有辦法以補丁的方式動態修復緊急Bug,不再需要重新發布App,不再需要用戶重新下載,覆蓋安裝?
於是涌現出來很多熱補丁方案。
binder原理
onTransAct,asInterface,stub,return binder stub
在Android系統用到最多的通信機制就是Binder,binder主要由Client,Sevice.ServiceManager和Binder 驅動程序組成。其中Client,Service和ServiceManager運行在用戶空間,而Binder驅動程序運行在內核空間。
在init進程啟動之后,servcieManager的進程啟動遠比zygote要早(啰嗦一下,在Anroid系統中所有的應用程序以及系統
服務進程SystemService都是由於zygote進程孕育出來的)因為在啟動zygote進程時需要用到serviceManager進程服務,
ServiceManager是一個守護進程,它維護着系統服務和客戶端的binder通訊。
recyclerview 緩存原理
java回調
https://www.cnblogs.com/snowbook/p/5802804.html
說說 LruCache 底層原理
LruCache 使用一個 LinkedHashMap 簡單的實現內存的緩存,沒有軟引用,都是強引用。
如果添加的數據大於設置的最大值,就刪除最先緩存的數據來調整內存。maxSize 是通過構造方法初始化的值,他表示這個緩存能緩存的最大值是多少。
size 在添加和移除緩存都被更新值, 他通過 safeSizeOf 這個方法更新值。 safeSizeOf 默認返回 1,但一般我們會根據 maxSize 重寫這個方法,比如認為 maxSize 代表是 KB 的話,那么就以 KB 為單位返回該項所占的內存大小。
除異常外,首先會判斷 size 是否超過 maxSize,如果超過了就取出最先插入的緩存,如果不為空就刪掉,並把 size 減去該項所占的大小。這個操作將一直循環下去,直到 size 比 maxSize 小或者緩存為空。
一般來說最大值的1/8左右就可以了。
從網絡加載一個10M的圖片,說下注意事項
圖片緩存、異常恢復、質量壓縮
retrofit中 怎么對 okhttp包裝,代理模式的使用,注解的使用
列舉java的集合和繼承關系
[圖片上傳失敗…(image-b1bae0-1536496112163)]
注解
其實同 classs 和 interface 一樣,注解也屬於一種類型。
注解的定義:
public @interface TestAnnotation { }
元注解:
元注解是可以注解到注解上的注解,或者說元注解是一種基本注解,但是它能夠應用到其它的注解上面。
元標簽有 @Retention、@Documented、@Target、@Inherited、@Repeatable 5 種。
http://blog.csdn.net/briblue/article/details/73824058
注解與反射。
注解通過反射獲取。首先可以通過 Class 對象的 isAnnotationPresent() 方法判斷它是否應用了某個注解
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {}
然后通過 getAnnotation() 方法來獲取 Annotation 對象。
public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {}
或者是 getAnnotations() 方法。
public Annotation[] getAnnotations() {}
前一種方法返回指定類型的注解,后一種方法返回注解到這個元素上的所有注解。
用法demo:(Retrofit中header注解)
/** Make a HEAD request. */ @Documented @Target(METHOD) @Retention(RUNTIME) public @interface HEAD { /** * A relative or absolute path, or full URL of the endpoint. This value is optional if the first * parameter of the method is annotated with {@link Url @Url}. * <p> * See {@linkplain retrofit2.Retrofit.Builder#baseUrl(HttpUrl) base URL} for details of how * this is resolved against a base URL to create the full endpoint URL. */ String value() default ""; }
動態代理
http://blog.csdn.net/scplove/article/details/52451899
個人觀點:代理一個類的所有自責,並可以進行拓展(通過代理層這一中間層,有效的控制對於真實委托類對象的直接訪問,同時可以實現自定義的控制策略,設計上獲得更大的靈活性。)
其目的就是為其他對象提供一個代理以控制對某個真實對象的訪問。代理類負責為委托類預處理消息,過濾消息並轉發消息,以及進行消息被委托類執行后的后續處理。
demo:
public <T> T create(final Class<T> service) { Utils.validateServiceInterface(service); if (validateEagerly) { eagerlyValidateMethods(service); } return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service }, new InvocationHandler() { private final Platform platform = Platform.get(); @Override public Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable { // If the method is a method from Object then defer to normal invocation. if (method.getDeclaringClass() == Object.class) { return method.invoke(this, args); } if (platform.isDefaultMethod(method)) { return platform.invokeDefaultMethod(method, service, proxy, args); } ServiceMethod<Object, Object> serviceMethod = (ServiceMethod<Object, Object>) loadServiceMethod(method); OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args); return serviceMethod.callAdapter.adapt(okHttpCall); } }); }
ANR
ANR的全稱application not responding 應用程序未響應。文件目錄/data/anr/
在android中Activity的最長執行時間是5秒。
BroadcastReceiver的最長執行時間則是10秒。
Service的最長執行時間則是20秒。
常用的排序
冒泡:
public static void bubbleSort(int[] array) { int temp = 0; for (int i = 0; i < array.length - 1; i++) { for (int j = 0; j < array.length - 1 - i; j++) { if (array[j] > array[j + 1]) { temp = array[j + 1]; array[j + 1] = array[j]; array[j] = temp; } } } }
選擇排序:
public static void selectSort(int[] array) { int temp = 0; for (int i = 0; i < array.length; i++) { for (int j = array.length - 1; j > i; j--) { if (array[i] > array[j]) { temp = array[i]; array[i] = array[j]; array[j] = temp; } } } }
二分查找:
public static int binarySearch(int[] array, int searchKey){ int begin = 0; int end = array.length - 1; while (begin <= end){ int middle = (begin + end) / 2; if (searchKey == array[middle]){ return middle; }else if(searchKey <= array[middle]){ begin = middle + 1; }else { end = middle -1; } } return -1; }
java JVM基本機構 內存分配 垃圾回收
https://www.cnblogs.com/LeonJ-Java/p/6497782.html
經過javac編譯器的編譯,我們需要將.java后綴的源碼編譯為.class后綴的字節碼,JVM作用就是將這些字節碼通過類加載器加載到內存當中,從而實現我們的業務邏輯需求.
JVM的內存區域分為5大塊:
1.虛擬機棧(Stack):一般俗稱棧區,是線程私有的.棧區一般與線程緊密相聯,一旦有新的線程被創建,JVM就會為該線程分配一個對應的java棧區,在這個棧區中會有許多棧幀,每運行一個方法就創建一個棧幀,用於存儲局部變量,方法返回值等.棧幀中存儲的局部變量隨着線程的結束而結束,其生命周期取決於線程的生命周期,所以講java棧中的變量都是線程私有的.
2.堆(Heap):真正存儲對象的區域,當進行Object obj = new Object()這樣一個操作時,真正的obj對象實例就會在heap中.
3.方法區(Method Area):包含常量池,靜態變量等,有人說常量池也屬於heap的一部分,但是嚴格上講方法區只是堆的邏輯部分,方法區還有個別名叫做非堆(non-heap),所以方法區和堆還是有不同的.
4.程序計數器(Program Couter Register):用於保存當前線程的執行的內存地址.因為JVM是支持多線程的,多線程同時執行的時候可能會輪流切換,為了保證線程切換回來后還能恢復到原先狀態,就需要一個獨立的計數器,記錄之前中斷的位置,由此可以看出程序計數器也是線程私有的.
5.本地方法棧(Native Method Stack):性質與虛擬機棧類似,是為了方便JVM去調用本地方法接口的棧區,此處開發者很少去關存儲機制:
http://www.yanwushu.com/post/38.html
https://www.cnblogs.com/zyj-bozhou/p/6723863.html
注,我也是了解有限,因此不深入探究其作用.
每個方法都會建立自己的內存棧,在這個方法定義的變量將會放到這塊棧內存里,隨着方法的結束,這個方法的內存棧也將自動銷毀。(不需要GC回收)
當我們在程序中創建一個對象時,這個對象會被保存到運行時數據區中,以便反復利用(復用,因為創建對象的成本通常較大),這個運行時數據區就是堆內存。堆內存中的對象不會隨着方法的結束而銷毀,即使方法結束后,這個對象還可能被另外一個引用變量所引用(在方法的參數傳遞時很常見),則這個對象依然不會被銷毀。只有當一個對象沒有任何引用變量去引用它時,系統的垃圾回收器(GC)才會在合適的時候回收它。
方法區:
1.又叫靜態區,跟堆一樣,被所有的線程共享。方法區包含所有的class文件和static變量,方法。 虛擬機必須為每個被裝載的類型維護一個常量池。常量池就是該類型所用到常量的一個有序集和,包括直接常量(string,integer和 floating point常量)和對其他類型,字段和方法的符號引用。
2.方法區中包含的都是在整個程序中永遠唯一的元素,如class文件,static變量,方法。
Java虛擬機運行時的數據區,包括方法區、虛擬機棧、堆、程序計數器、本地方法棧。
無論是虛擬機棧,程序計數器以及本地方法棧均屬於線程私有,其生命周期與線程的生命周期一致,當線程執行完畢,其所占用的內存空間也就隨之釋放,因此這三部分是不存在垃圾回收問題的.Java開發者平常所說的垃圾回收,主要針對的是堆區和方法區而言的。
JVM的內存分配一般是先一次性分配出一個較大的空間,內存申請一般分為靜態內存和動態內存.靜態內存比較容易理解,編譯時就能確定的內存就是靜態內存,即內存是固定的,系統可以直接進行分配,比如short,int類型的變量,其占用的內存大小是固定的.而動態內存分配是只有當程序運行時,才能知道所要分配的內存空間大小,在運行之前是不確定的,因此屬於動態內存.
垃圾回收:(如果一個對象,沒有一個引用指向它,那么它就被認為是一個垃圾。)
年輕代(Young):
在年輕代中,又划分為伊甸園區(Eden),幸存0區(Survivor0)和幸存1區(Survivor1).所有對象最初被創建的時候都會在Eden區,當Eden區被填滿時會進行Minor GC,如果還有對象存活,那么就會被JVM轉移到幸存區0區或幸存1區.一般地,幸存0區和幸存1區中總有一個是空的,當其中一個區被填滿時就會再進行Minor GC,就這樣還能存活的對象就會在幸存0區和幸存1區之間來回切換.在幸存區經過很多次GC后依然還能存活的對象會被轉移到年老代(一般需要設定一個存活閾值,可以通過參數 -XX:MaxTenuringThreshold 來設置),當超過這個臨界值時,就將還依舊存活的對象轉移到年老代當中.
年老代(Old):
處於該代的java對象都是在年輕代久經考驗而存活的對象,一般都是生命周期較長的對象.當年老代的空間被占滿時,則不會進行Minor GC,而會觸發Major GC/Full GC,回收整個堆內存.
public class Sample { int s1 = 0; Sample mSample1 = new Sample(); public void method() { int s2 = 1; Sample mSample2 = new Sample(); } } Sample mSample3 = new Sample();
Sample 類的局部變量 s2 和引用變量 mSample2 都是存在於棧中,但 mSample2 指向的對象是存在於堆上的。mSample3 指向的對象實體存放在堆上,包括這個對象的所有成員變量 s1 和 mSample1,而它自己存在於棧中。
結論:
局部變量的基本數據類型和引用存儲於棧中,引用的對象實體存儲於堆中。—— 因為它們屬於方法中的變量,生命周期隨方法而結束。
成員變量全部存儲與堆中(包括基本數據類型,引用和引用的對象實體)—— 因為它們屬於類,類對象終究是要被new出來使用的。
了解了 Java 的內存分配之后,我們再來看看 Java 是怎么管理內存的。
Java內存分配中的棧
在函數中定義的一些基本類型的變量數據和對象的引用變量都在函數的棧內存中分配。當在一段代碼塊定義一個變量時,Java就在棧中為這個變量分配內存空間,當該變量退出該作用域后,Java會自動釋放掉為該變量所分配的內存空間,該內存空間可以立即被另作他用。
Java內存分配中的堆
堆內存用來存放由new創建的對象和數組。 在堆中分配的內存,由Java虛擬機的自動垃圾回收器來管理。
java方法區在對內存嗎?
三種情況: java7之前,方法區位於永久代(PermGen),永久代和堆相互隔離,永久代的大小在啟動JVM時可以設置一個固定值,不可變; java7中,static變量從永久代移到堆中; java8中,取消永久代,方法存放於元空間(Metaspace),元空間仍然與堆不相連,但與堆共享物理內存,邏輯上可認為在堆中
Android系統啟動流程
-
1.啟動電源以及系統啟動
當電源按下時引導芯片代碼開始從預定義的地方(固化在ROM)開始執行。加載引導程序Bootloader到RAM,然后執行。 -
2.引導程序BootLoader
引導程序BootLoader是在Android操作系統開始運行前的一個小程序,它的主要作用是把系統OS拉起來並運行。 -
3.Linux內核啟動
內核啟動時,設置緩存、被保護存儲器、計划列表、加載驅動。當內核完成系統設置,它首先在系統文件中尋找init.rc文件,並啟動init進程。 -
4.init進程啟動
初始化和啟動屬性服務,並且啟動Zygote進程。 -
5.Zygote進程啟動
創建JavaVM並為JavaVM注冊JNI,創建服務端Socket,啟動SystemServer進程。 -
6.SystemServer進程啟動
啟動Binder線程池和SystemServiceManager,並且啟動各種系統服務。
7.Launcher啟動
被SystemServer進程啟動的ActivityManagerService會啟動Launcher,Launcher啟動后會將已安裝應用的快捷圖標顯示到界面上。 -