[Android基礎]Android總結篇
本文來自於清華(瀟澗)的Java總結,已得到其本人允許轉載
Android公共技術:
1.Android的Framework和Android apk的打包過程
底層的Binder驅動,IPC的核心,SGL 2D繪圖,OpenGL 3D繪圖
2.多執行緒
AsyncTask:
關於執行緒池:asynctask對應的執行緒池ThreadPoolExecutor都是程序範圍內共享的,都是static的,所以是asynctask控制著程序範圍內所有的子類例項。由於這個限制的存在,當使用預設執行緒池時,如果執行緒數超過執行緒池的最大容量,執行緒池就會爆掉(3.0後預設序列執行,不會出現這個問題)。針對這種情況,可以嘗試自定義執行緒池,配合asynctask使用。
關於預設執行緒池:核心執行緒池中最多有CPU_COUNT+1個,最多有CPU_COUNT*2+1個,執行緒等待佇列的最大等待數為128,但是可以自定義執行緒池。執行緒池是由AsyncTask來管理的,執行緒池允許tasks並行執行,xuyao注意的是併發情況下資料的一致性問題,新資料可能會被老資料覆蓋掉,類似volatile變數。所以希望tasks能夠序列執行的話,使用SERIAL_EXECUTOR。
自定義執行緒池:executeOnExecutor(Executor exec,Params… params) 自定義Executor
execute(Params… params){return executeOnExecutor(sDefaultExecutor,params);}
AsyncTask在不同的SDK版本中的區別:
通過查閱官方文件發現,AsyncTask首次引入時,非同步任務是在一個獨立的執行緒中順序的執行,也就是說一次只能執行一個任務,不能並行的執行,從1.6開始,AsyncTask引入了執行緒池,支援同時執行5個非同步任務,也就是說同時只能有5個執行緒執行,超過的執行緒只能等待,等待前面的執行緒某個執行完了才被排程和執行。換句話說,如果一個程序中的AsyncTask例項個數超過5個,那麼假如前5個都執行很長時間的話,那麼第6個只能等待機會了。這是AsyncTask的一個限制,而且對於2.3以前的版本無法解決。如果你的應用需要大量的後臺執行緒去執行任務,那麼你只能放棄使用AsyncTask,自己建立執行緒池來管理Thread,或者乾脆不用執行緒池直接使用Thread也無妨。不得不說,雖然AsyncTask較Thread使用起來方便,但是它最多隻能同時執行5個執行緒,這也大大侷限了它的實力,你必須要小心設計你的應用,錯開使用AsyncTask的時間,盡力做到分時,或者保證數量不會大於5個,否則就會遇到上次提到的問題。可能是Google意識到了AsyncTask的侷限性了,從Android3.0開始對AsyncTask的API作出了一些調整:每次只啟動一個執行緒執行一個任務,完成之後再執行第二個任務,也就是相當於只有一個後臺執行緒在執行所提交的任務。
1、生命週期
很多開發者會認為一個在Activity中建立的AsyncTask會隨著Activity的銷燬而銷燬。然而事實並非如此。AsyncTask會一直執行,直到doInBackground()方法執行完畢。然後,如果cancel(boolean)被呼叫,那麼onCancelled(Result result)方法會被執行;否則,執行onPostExecute(Result result)方法。如果我們的Activity銷燬之前,沒有取消AsyncTask,這有可能讓我們的AsyncTask崩潰(crash)。因為它想要處理的view已經不在了。所以,我們總是必須確保在銷燬活動之前取消任務。總之,我們使用AsyncTask需要確保AsyncTask正確的取消。
2、記憶體洩漏
如果AsyncTask被宣告為Activity的非靜態的內部類,那麼AsyncTask會保留一個對Activity的引用。如果Activity已經被銷燬,AsyncTask的後臺執行緒還在執行,它將繼續在記憶體裡保留這個引用,導致Activity無法被回收,引起記憶體洩漏。
3、結果丟失
螢幕旋轉或Activity在後臺被系統殺掉等情況會導致Activity的重新建立,之前執行的AsyncTask會持有一個之前Activity的引用,這個引用已經無效,這時呼叫onPostExecute()再去更新介面將不再生效。
4、並行還是序列
在Android1.6之前的版本,AsyncTask是序列的,在1.6至2.3的版本,改成了並行的。在2.3之後的版本又做了 修改,可以支援並行和序列,當想要序列執行時,直接執行execute()方法,如果需要執行executeOnExecutor(Executor)。
3.Android機制
(1)Linux Sandbox 沙箱機制:Android將資料分為system和data兩個區。其中system是隻讀的,dada用來存放應用自己的資料,這保證了系統資料不會被隨意改寫。
應用之間的資料相互獨立,每個應用都會有一個user id和group id,只有相同的user id並且來自同一個作者,才能訪問它們的資料。作者通過對apk簽名來標識自己,簽名和uid構成了雙重的保證。
(2)使用者許可權機制:檔案許可權,UID,GID
(3)使用者許可權機制:android permission機制限制應用訪問特定的資源,例如照相機、網路、外部儲存等api
如何讓兩個app執行在同一個程序裡?
- 兩個app要用相同的private key來簽名
- 兩個app的Manifest檔案中要新增一樣的屬性 android:sharedUserId(設定成相同的UID)
4.Binder機制
跨程序間通訊(IPC):四大元件之間通過Intent互相跳轉,Android實現IPC的方式是binder機制。
In the Android platform, the binder is used for nearly everything that happens accross in the core plateform.
最底層的是Android的ashmen(Anonymous shared memoryy)機制,它負責輔助實現記憶體的分配,以及跨程序間通訊所需要的記憶體共享。AIDL(Android Interface Definition Language)對BInder的使用進行了封裝,可以讓開發者方便的進行方法的遠端呼叫,後面會詳細介紹。Intent是最高一層的抽象,方便開發者進行常用的跨程序呼叫。
從英文字面上意思看,Binder具有粘結劑的意思,那麼它把什麼東西粘結在一起呢?在Android系統的Binder機制中,由一系統元件組成,分別是Client、Server、Service Manager和Binder驅動程式,其中Client、Server和Service Manager執行在使用者區間,Binder驅動程式執行核心空間。Binder就是一種把這四個元件粘合在一起的粘結劑了,其中,核心元件便是Binder驅動了,ServiceManager提供了輔助管理的功能,Client和Server正是在Binder驅動和ServiceManager提供的基礎設施上,進行Client-Server之間的通訊。
- Client、Server和ServiceManager實現在使用者空間中,Binder驅動程式實現在核心空間中
- Binder驅動程式和ServiceManager在Android平臺已經實現,開發者只需要在使用者空間實現自己的Client和Server
- Binder驅動程式提供裝置檔案/dev/binder與使用者空間互動,Client、Server和ServiceManager通過open和ioctl檔案操作函式與Binder驅動程式進行通訊
- Client和Server之間的程序間通訊通過Binder驅動程式間接實現
- Service Manager是一個守護程序,用來管理Server,並向Client提供查詢Server介面的能力
伺服器端:一個Binder伺服器就是一個Binder類的物件。當建立一個Binder物件後,內部就會開啟一個執行緒,這個執行緒用語接收binder驅動傳送的資訊,收到訊息後,會執行相關的服務程式碼。
Binder驅動:當服務端成功建立一個Binder物件後,Binder驅動也會相應的建立一個mRemote物件,該物件的型別也是也是Binder類。客戶就可以藉助這個mRemote物件來訪問遠端服務。
客戶端:客戶想要訪問Binder的遠端服務,就必須獲取遠端服務的Binder物件在binder驅動層對應的mRemote引用。當獲取到mRemote物件的引用後,就可以呼叫相應Binder物件的服務了。
在這裡,我們可以看到,客戶端是通過Binder驅動來呼叫服務端的相關服務。首先,在服務端建立一個Binder物件,然後相應的在Binder驅動中建立一個Binder物件,接著客戶端通過獲取Binder物件的引用來呼叫服務端的服務。在Binder機制中正是藉著Binder驅動將不同程序間的元件bind(粘連)在一起,實現通訊。
mmap將一個檔案或者其他物件對映進記憶體。檔案被對映到多個頁上,如果檔案的大小不是所有頁的大小之和,最後一個頁不被使用的空間將會清零。munmap執行相反的操作,刪除特定地址區域的物件對映。
當使用mmap對映檔案到程序後,就可以直接操作這段虛擬記憶體進行檔案的讀寫等操作,不必再呼叫read,write等系統呼叫。但需注意,直接對該段記憶體寫時不會寫入超過當前檔案大小的內容。
採用共享記憶體通訊的一個顯而易見的好處是效率高,因為程序可以直接讀寫記憶體,而不需要任何資料的拷貝。對於像管道和訊息佇列等通訊方式,則需要在核心和使用者空間進行四次的資料拷貝,而共享記憶體只拷貝兩次資料:一次從輸入檔案到共享記憶體區,另一次從共享記憶體區輸出檔案。實際上,程序之間在共享記憶體時,並不總是讀寫少量資料後就解除對映,有新的通訊時,再重新建立共享記憶體區域。而是儲存共享區域,直到通訊完畢為止,這樣,資料內容一直儲存在共享記憶體中,並沒有寫回檔案。共享記憶體中的內容往往是在解除對映時才寫回檔案的。因此,採用共享記憶體的通訊方式效率是非常高的。
aidl主要就是幫助我們完成了包裝資料和解包的過程,並呼叫了transact過程,而用來傳遞的資料包我們就稱為parcel
AIDL: xxx.aidl->xxx.java,註冊service
- 用aidl定義需要被呼叫方法介面
- 實現這些方法
- 呼叫這些方法
5.NDK
Dalvik虛擬機器在呼叫一個成員函式的時候,如果發現該成員函式是一個JNI方法,那麼就會直接跳到它的地址去執行。也就是說,JNI方法是直接在本地作業系統執行的,而不是Dalvik虛擬機器直譯器執行。由此也可以看出,JNI方法是Android應用程式與本地作業系統直接進行通訊的一個手段。
JNI原理:
例子:當libnanosleep.so檔案被載入的時候,函式JNI_OnLoad就會被呼叫。在函式JNI_OnLoad中,引數vm描述的是當前執行緒中的Dalvik虛擬機器,通過呼叫它的成員函式GetEnv就可以獲得一個JNIEnv物件。有了這個JNIEnv物件之後,我們就可以呼叫另外一個函式jniRegisterNativeMethods來向當前程序的Dalvik虛擬機器註冊一個JNI方法。
6.Android系統啟動過程,App啟動過程
App啟動過程:
從桌面點選到activity啟動的過程
1、Launcher執行緒捕獲onclick的點選事件,呼叫Launcher.startActivitySafely,進一步呼叫Launcher.startActivity,最後呼叫父類Activity的startActivity。
2、Activity和ActivityManagerService互動,引入Instrumentation,將啟動請求交給Instrumentation,呼叫Instrumentation.execStartActivity。
3、呼叫ActivityManagerService的startActivity方法,這裡做了程序切換(具體過程請檢視原始碼)。
4、開啟Activity,呼叫onCreate方法
7.Activity,Fragment,Service生命週期
常見的例子:程式正執行著來電話了,這個程式咋辦呢?中止了唄,如果中止的時候新出的一個Activity是全屏的onPause->onStop,恢復的時候onStart->onResume,如果打斷這個應用程式的是一個Theme為Translucent或者Dialog的Activity那麼只是onPause,恢復的時候onResume。
- onPause:恢復的時候onResume
- onCreate:在這裡建立介面,做一些資料的初始化工作
- onStart:到這一步變成使用者可見不可互動的
- onPause:到這一步是可見但不可互動的,系統會停止動畫等消耗CPU的事情,應該在這裡儲存你的一些 資料,因為這個時候你的程式的優先順序降低,有可能被 系統回收。在這裡儲存的資料,應該在onResume裡讀出來。注意:這個方法裡做的事情時間要短,因為下一個Activity不會等到這個方法完成才啟動。
- onStop:變得不可見,被下一個Activity覆蓋了(onPause和onStop的區別是否可見)
- onDestroy:這是Activity被幹掉前最後一個被呼叫方法了,可能是外面類呼叫finish方法或者是系統為了節省空間將它暫時性的幹掉,可以用isFinishing()來判斷它,如果你有一個ProgressDialog線上程中轉動,請在onDestroy裡把它cancel掉,不然等執行緒結束的時候,呼叫Dialog的cancel會丟擲異常的。
onPause,onstop,onDestroy,三種狀態下,Activity都有可能被系統幹掉。
啟動另一個Activity然後finish,先呼叫舊Activity的onPause方法,然後呼叫新的Activity和onCreate->onStart->onResume方法,然後呼叫舊Activity的onStop->onDestroy方法。
如果沒有呼叫finish那麼onDestroy方法不會被呼叫,而且在onStop之前還會呼叫onSavedInstanceState方法
onRestart方法執行完了之後還會呼叫onStart方法
fragment:[SupportFragmentManager,childFragment]
service:
Service和Intent Service:沒啥區別,只是IntentService在onCreate方法中開啟新的HandlerThread去執行。
Service執行的程序和執行緒:當它執行的時候如果是LocalService,那麼對應的Service是執行在主程序的main執行緒上的。如onCreate,onStart這些函式都是在系統呼叫的時候在主程序的main執行緒上執行的。如果是RemoteSevice,那麼對應的Service則是執行在獨立的main執行緒上。
- 服務不是單一的程序,服務沒有自己的程序,應用程式可以不同,服務執行在相同的程序中
- 服務不是執行緒,可以線上程中工作
- 在應用中,如果是長時間的在後臺執行,而且不需要互動的情況下,使用服務
- 同樣是在後臺執行,不需要互動的情況下,如果只是完成某個任務,之後就不需要執行,而且可能是多個任務,需要長時間執行的情況下使用執行緒
- 如果任務佔用CPU時間多,資源大的情況下,要使用執行緒
Thread的執行是獨立於Activity的,也就是說當一個Activity被finish之後,如果你沒有主動停止Thread或者Thread裡的run方法沒有執行完畢的話,Thread就會一直執行。
8.View繪畫機制
View的繪製主要涉及三個方法:onMeasure()、onLayout()、onDraw()
- onMeasure主要用於計算view的大小,onLayout主要用於確定view在ContentView中的位置,onDraw主要是繪製View。
- 在執行onMeasure()、onLayout()方法時都會通過相應的標誌位或者對應的座標點來判斷是否需要執行對應的函式,如我們經常呼叫的invalidate方法就只會執行onDraw方法,因為此時的檢視大小和位置均未發生變化,除非呼叫requestLayout方法完整強制進行view的繪製,從而執行上面三個方法。
進度條元件:
9.事件傳遞機制
當手指觸控到螢幕時,系統就會呼叫相應View的onTouchEvent,並傳入一系列的action。
dispatchTouchEvent的執行順序為:
- 首先觸發ACTIVITY的dispatchTouchEvent,然後觸發ACTIVITY的onUserInteraction
- 然後觸發LAYOUT的dispatchTouchEvent,然後觸發LAYOUT的onInterceptTouchEvent
這就解釋了重寫ViewGroup時必須呼叫super.dispatchTouchEvent();
(1)dispatchTouchEvent:
此方法一般用於初步處理事件,因為動作是由此分發,所以通常會呼叫super.dispatchTouchEvent。這樣就會繼續呼叫onInterceptTouchEvent,再由onInterceptTouchEvent決定事件流向。
(2)onInterceptTouchEvent:
若返回值為true事件會傳遞到自己的onTouchEvent();若返回值為false傳遞到下一個View的dispatchTouchEvent();
(3)onTouchEvent():
若返回值為true,事件由自己消耗,後續動作讓其處理;若返回值為false,自己不消耗事件了,向上返回讓其他的父View的onTouchEvent接受處理
三大方法關係的虛擬碼:如果當前View攔截事件,就交給自己的onTouchEvent去處理,否則就丟給子View繼續走相同的流程。
public boolean dispatchTouchEvent(MotionEvent ev)
{
boolean consume = false;
if(onInterceptTouchEvent(ev))
{
consume = onTouchEvent(ev);
}
else
{
consume = child.dispatchTouchEvent(ev);
}
return consume;
}
onTouchEvent的傳遞:
當有多個層級的View時,在父層級允許的情況下,這個action會一直傳遞直到遇到最深層的View。所以touch事件最先呼叫的是最底層View的onTouchEvent,如果View的onTouchEvent接收到某個touch action並做了相應處理,最後有兩種返回方式return true和return false;return true會告訴系統當前的View需要處理這次的touch事件,以後的系統發出的ACTION_MOVE,ACTION_UP還是需要繼續監聽並接收的,並且這次的action已經被處理掉了,父層的View是不可能觸發onTouchEvent的了。所以每一個action最多隻能有一個onTouchEvent介面返回true。如果返回false,便會通知系統,當前View不關心這一次的touch事件,此時這個action會傳向父級,呼叫父級View的onTouchEvent。但是這一次的touch事件之後發出任何action,該View都不在接受,onTouchEvent在這一次的touch事件中再也不會觸發,也就是說一旦View返回false,那麼之後的ACTION_MOVE,ACTION_UP等ACTION就不會在傳入這個View,但是下一次touch事件的action還是會傳進來的。
父層的onInterceptTouchEvent
前面說了底層的View能夠接收到這次的事件有一個前提條件:在父層允許的情況下。假設不改變父層級的dispatch方法,在系統呼叫底層onTouchEvent之前會呼叫父View的onInterceptTouchEvent方法判斷,父層View是否要截獲本次touch事件之後的action。如果onInterceptTouchEvent返回了true,那麼本次touch事件之後的所有action都不會向深層的View傳遞,統統都會傳給父層View的onTouchEvent,就是說父層已經截獲了這次touch事件,之後的action也不必詢問onInterceptTouchEvent,在這次的touch事件之後發出的action時onInterceptTouchEvent不會再被呼叫,直到下一次touch事件的來臨。如果onInterceptTouchEvent返回false,那麼本次action將傳送給更深層的View,並且之後的每一次action都會詢問父層的onInterceptTouchEvent需不需要截獲本次touch事件。只有ViewGroup才有onInterceptTouchEvent方法,因為一個普通的View肯定是位於最深層的View,只有ViewGroup才有onInterceptTouchEvent方法,因為一個普通的View肯定是位於最深層的View,touch能夠傳到這裡已經是最後一站了,肯定會呼叫View的onTouchEvent()。
底層View的getParent().requestDisallowInterceptTouchEvent(true)
對於底層的View來說,有一種方法可以阻止父層的View獲取touch事件,就是呼叫getParent().requestDisallowInterceptTouchEvent(true)方法。一旦底層View收到touch的action後呼叫這個方法那麼父層View就不會再呼叫onInterceptTouchEvent了,也無法截獲以後的action(如果父層ViewGroup和最底層View需要截獲不同焦點,或不同手勢的touch,不能使用這個寫死)。
曾經開發過程中遇到的兩個示例:左邊是處理ViewPager和ListView的衝突,紀錄水平和垂直方向的偏移量,如果水平方向的偏移更多的話就讓ViewPager處理pager滑動
右邊處理的ViewPager和ImageBanner的滑動衝突,同樣是紀錄偏移量,如果發生在ImageBanner上的水平偏移量大於垂直偏移量的話就讓banner滾動
想想為什麼右邊是重寫dispatchTouchEvent方法而不是onInterceptTouchEvent方法?
FixedViewPager
@Override
public boolean onInterceptTouchEvent(MotionEvent ev)
{
switch(ev.getAction() & MotionEvent.ACTION_MASK)
{
case MotionEvent.ACTION_DOWN:
mX = ev.getX();
mY = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
float x = ev.getX();
float y = ev.getY();
float dX = x - mX;
float dY = y - mY;
float tmp = Math.abs(dX) / Math.abs(dY);
mX = x;
mY = y;
if(tmp > 1)
{
return true;
}
else
{
return super.omInterceptTouchEvent(ev);
}
}
}
FixedImageLoadBanner
@override
public boolean dispatchTouchEvent(MotionEvent ev)
{
if(mX != 0 || mY != 0)
{
float dY = ev.getRawY() - mY;
float dX = ev.getRawX() - mX;
if(Math.abs(dY) > Math.abs(dX))
{
requestDisallowInterceptTouchEvent(false);
}
else
{
requestDisallowInterceptTouchEvent(true);
}
}
mX = ev.getRawX();
mY = ev.getRawY();
return super.dispatchTouchEvent(ev);
}
10.ART和Dalvik區別
art上應用啟動快,執行快,但是耗費更多儲存空間,安裝時間長,總的來說ART的功效就是”空間換時間”。
ART: Ahead of Time Dalvik: Just in Time
什麼是Dalvik:Dalvik是Google公司自己設計用於Android平臺的Java虛擬機器。Dalvik虛擬機器是Google等廠商合作開發的Android移動裝置平臺的核心組成部分之一,它可以支援已轉換為.dex(即Dalvik Executable)格式的Java應用程式的執行,.dex格式是專為Dalvik應用設計的一種壓縮格式,適合記憶體和處理器速度有限的系統。Dalvik經過優化,允許在有限的記憶體中同時執行多個虛擬機器的例項,並且每一個Dalvik應用作為獨立的Linux程序執行。獨立的程序可以防止在虛擬機器崩潰的時候所有程式都被關閉。
什麼是ART:Android作業系統已經成熟,Google的Android團隊開始將注意力轉向一些底層元件,其中之一是負責應用程式執行的Dalvik執行時。Google開發者已經花了兩年時間開發更快執行效率更高更省電的替代ART執行時。ART代表Android Runtime,其處理應用程式執行的方式完全不同於Dalvik,Dalvik是依靠一個Just-In-Time(JIT)編譯器去解釋位元組碼。開發者編譯後的應用程式碼需要通過一個直譯器在使用者的裝置上執行,這一機制並不高效,但讓應用能更容易在不同硬體和架構上執行。ART則完全改變了這套做法,在應用安裝的時候就預編譯位元組碼到機器語言,這一機制叫Ahead-Of-Time(AOT)編譯。在移除解釋程式碼這一過程後,應用程式執行將更有效率,啟動更快。
ART優點:
- 系統性能的顯著提升
- 應用啟動更快、執行更快、體驗更流暢、觸感反饋更及時。
- 更長的電池續航能力
- 支援更低的硬體
ART缺點:
- 更大的儲存空間佔用,可能會增加10%-20%
- 更長的應用安裝時間
11.Scroller原理
Scroller執行流程裡面的三個核心方法
- mScroller.startScroll()
- mScroller.computeScrollOffset()
- view.computeScroll()
1、在mScroller.startScroll()中為滑動做了一些初始化準備,比如:起始座標,滑動的距離和方向以及持續時間(有預設值),動畫開始時間等。
2、mScroller.computeScrollOffset()方法主要是根據當前已經消逝的時間來計算當前的座標點。因為在mScroller.startScroll()中設定了動畫時間,那麼在computeScrollOffset()方法中依據已經消逝的時間就很容易得到當前時刻應該所處的位置並將其儲存在變數mCurrX和mCurrY中。除此之外該方法還可判斷動畫是否已經結束。
12.Activity Manager Service, ActivityThread
13.Android幾種程序
- 前臺程序: 即與使用者正在互動的Activity或者Activity用到的Service等,如果系統記憶體不足時前臺程序是最後被殺死的
- 可見執行緒:可以是處於暫停狀態(onPause)的Activity或者繫結在其上的Service,即被使用者可見,但由於失去了焦點而不能與使用者互動
- 服務程序:其中執行著使用startService方法啟動的Service,雖然不被使用者可見,但是卻是使用者關係的,例如使用者正在非音樂介面聽的音樂或者正在非下載頁面自己下載的檔案等,當系統要用空間執行前兩者程序時才會被終止
- 後臺程序:其中執行著執行onStop方法而停止的程式,但是卻不是使用者當前關心的,例如後臺掛著的QQ,這樣的程序系統一旦沒有記憶體就首先被殺死。
- 空程序:不包含任何應用程式的程式元件的程序,這樣的程序系統是一般不會讓他存在的。
如何避免後臺程序被殺死?
- 呼叫startForegound,讓你的Service所在的程序成為前臺程序
- Service的onStartCommand返回START_STICKY或START_REDELIVER_INTENT
- Service的onDestroy裡面重新啟動自己
14.Activity啟動模式
standard:Activity的預設載入方式,該方法會通過跳轉到一個新的Activity,同時將該例項壓入到棧中(不管該Activity是否已經存在在Task棧中,都是採用new操作,生命週期從onCreate()開始)。例如:棧中順序是A B C D,此時D通過Intent跳轉到A,那麼棧中結構就變成A B C D A,點選返回按鈕的顯示順序是D C B A,依次摧毀。
singleTop:singleTop模式下,當前Activity D位於棧頂的時候,如果通過Intent跳轉到它本身的Activity(D),那麼不會重新建立一個新的D例項(走onNewIntent()),所以棧中的結構依次為A B C D,如果跳轉到B,那麼由於B不處於棧頂,所以會新建一個B例項並壓入到棧中,結構就變成了A B C D B。應用例項:三條推送,點進去都是一個Activity,這肯定用singletop
singleTask:singleTask模式下,Task棧中只能有一個對應的Activity例項。例如:Task棧1中結構為:A B C D。此時D通過Intent跳轉到B(走onNewIntent()),則棧的結構變成了:A,B。其中的C和D被棧彈出銷燬了,也就是說位於B之上的例項都被銷燬了。通常應用於首頁,首頁肯定在棧底部,也只能在棧底部。
singleInstance:singleInstance模式下,會將開啟的Activity壓入一個新的任務棧中。例如:Task棧1中結構為:A B C,C通過Intent跳轉到了D(D的模式為singleInstance),那麼則會新建一個Task,棧1中結構依舊為A B C,棧2中結構為D。此時螢幕顯示D,之後D通過Intent跳轉到D,棧2不會壓入新的D,所以兩個棧中的情況沒發生改變。如果D跳轉到了C,那麼就會根據C對應的launchMode在棧1中進行對應的操作,C如果為standard,那麼D跳轉到C,棧1的結構為A B C C ,此時點選返回按鈕,還是在C,棧1的結構變為A B C,而不會回到D。
launchMode為singleTask的時候,通過Intent啟動到一個Activity,如果系統已經存在一個例項,系統就會將請求傳送到這個例項上,但這個時候,系統就不會再呼叫通常情況下我們處理請求資料的onCreate方法,而不是呼叫onNewIntent方法。
onSavedInstanceState的呼叫遵循一個重要原則,即當系統”未經你許可”時銷燬了你的Activity,則onSavedInstanceState會被系統呼叫,這時系統的責任,因為它必須要提供一個機會讓你儲存你的資料,至於onRestoreInstanceState方法,需要注意的是,onSavedInstanceState方法和onRestoreInstanceState方法”不一定”是成對呼叫的。
onRestoreInstanceState被呼叫的前提是,Activity A確實被系統銷燬了,而如果僅僅是停留在有這種可能性的情況下,則該方法不會被呼叫,例如,當正在顯示Activity A的時候,使用者按下HOME鍵回到主介面,然後使用者緊接著又返回到Activity A,這種情況下Activity A一般不會因為記憶體的原因被銷燬,故Activity的onRestoreInstanceState方法不會被執行。
另外,onRestoreInstanceStated的bundle引數也會傳遞到onCreate方法中,你也可以選擇在onCreate方法中做資料還原。
onSavedInstanceState(Bundle bundle)通常和onRestoreInstanceState(Bundle bundle)不會成對出現,onRestoreInstanceState這玩意不太好觸發,給大家提個好辦法,橫豎屏切換的時候100%會觸發。然後儲存在onRestoreInstanceState bundle裡面的資料,就是onCreate的那個引數bundle啦,要怎麼恢復就看開發者了。
15.TMImageView圖片庫的設計
Feature機制
16.ListView優化
- 首先,雖然大家都知道,還是提一下,利用好convertView來重用View,切忌每次getView都新建。ListView的核心原理就是重用View。ListView有一個回收器,Item滑出介面的時候view就會回收這裡,需要顯示新的Item的時候,就儘量重用回收器裡面的View。
- 利用好ViewType,例如你的ListView中有幾個型別的Item,需要給每個型別建立不同的View,這樣有利於ListView的回收,當然型別不能太多
- 儘量讓ItemView的Layout層次結構簡單,這時所有Layout都必須遵守的
- 善用自定義View,自定義View可以有效的減小Layout的層級,而且對繪製過程可以很好的控制
- 儘量保證Adapter的hasStableIds()返回true,這樣在notifyDataSetChanged()的時候,如果id不變,listView將不會重新繪製這個View,達到優化的目的。
- 每個item不能太高,特別是不要超出螢幕的高度,可以參考Facebook的優化方法,把特別複雜的Item分解為若干個小的Item.
- 為了保證ListView滑動的流暢性,getView()中要做盡量少的事情,不要有耗時的操作。特別是滑動的時候不要載入圖片,停下來再載入。
- 使用RecyclerView代替。ListView每次更新資料都要notifyDataSetChanged(),有些太暴力了。RecyclerView在效能和可定製性上都有很大的改善,推薦使用。
- 有時候,需要從根本上考慮,是否真的要使用listView來實現你的需求,或者是否有其他選擇?
17.webView
如何使用webview在js中呼叫java方法?
webView.addJavaScriptInterface(new Object(){xxx}, "xxx");
答案:可以使用WebView控制元件執行JavaScript指令碼,並且可以在JavaScript中執行Java程式碼。要想讓WebView控制元件執行JavaScript,需要呼叫WebSettings.setJavaScriptEnabled方法,程式碼如下:
WebView webView = (WebView)findViewById(R.id.webview);
WebSettings webSettings = webView.getSettings();
//設定WebView支援JavaScript
webSettings.setJavaScriptEnabled(true);
webView.setWebChromeClient(new WebChromeClient());
JavaScript呼叫Java方法需要使用WebView.addJavascriptInterface方法設定JavaScript呼叫的Java方法,程式碼如下:
webView.addJavascriptInterface(new Object()
{
//JavaScript呼叫的方法
public String process(String value)
{
//處理程式碼
return result;
}
}, "demo"); //demo是Java物件對映到JavaScript中的物件名
可以使用下面的JavaScript程式碼呼叫process方法,程式碼如下:
<script language="javascript">
function search()
{
//呼叫searchWord方法
result.innerHTML = "<font color='red'>" + window.demo.process('data') + "</font>";
}
18.SurfaceView和View的最本質的區別
SurfaceView是在一個新起的單獨執行緒中可以重新繪製畫面,而view必須在UI的主執行緒中更新畫面。
在UI的主執行緒中更新畫面可能會引發問題,比如你更新的時間過長,那麼你的主UI執行緒就會被你正在畫的函式阻塞。那麼將無法響應按鍵、觸屏等訊息。當使用SurfaceView由於是在新的執行緒中更新畫面所以不會阻塞你的UI主執行緒。但這也帶來了另外一個問題,就是事件同步。比如你觸屏了一下,你需要SurfaceView中thread處理,一般就需要有一個event queue的設計來儲存touchevent,這會稍稍複雜一點,因為涉及到執行緒安全。
19.標籤
簡言之,都是用來解決重複佈局的問題,但是標籤能夠在佈局重用的時候減少UI層級結構。
viewStub標籤是用來給其他的View事先佔據好位置,當需要的時候用inflater()或者是setVisible()方法顯示這些View。
20.ANR排錯
1、ANR排錯一般有三種類型
- KeyDispatchTimeout(5 seconds) –主要型別按鍵或觸控事件在特定時間內無響應
- BroadcastTimeout(10 secends) –BroadcastReceiver在特定時間內無法處理完成
- ServiceTimeout(20 secends) –小概率事件 Service在特定的時間內無法處理完成
2、如何避免
- UI執行緒儘量只做跟UI相關的工作
- 耗時的操作(比如資料庫操作,I/O,連線網路或者別的有可能阻塞UI執行緒的操作)把它放在單獨的執行緒處理
- 儘量用Handler來處理UIthread和別的thread之間的互動
3、如何排查
- 首先分析log
- 從trace.txt檔案檢視呼叫stack,adb pull data/anr/traces.txt ./mytraces.txt
- 看程式碼
- 仔細檢視ANR的成因(iowait?block?memoryleak?)
4、監測ANR的Watchdog
21.fragment生命週期
======================常見面試問題===============================
1、橫豎屏切換時候Activity的生命週期
- 不設定Activityd餓android:configChanges時,切屏會重新掉喲過各個生命週期,切橫屏時會執行一次,切豎屏時會執行兩次
- 設定Activity的android:configChanges=”orientation”時,切屏還是會呼叫各個生命週期,切換橫豎屏只會執行一次
- 設定Activity的android:configChanges=”orientation|keyboardHidden”時,切屏不會重新呼叫各個生命週期,只會執行onConfigurationChanged方法
2、(圖片相關的)OOM怎麼處理
- 在記憶體引用上做些處理,常用的有軟引用、弱引用
- 在記憶體中載入圖片時直接在記憶體中作處理,如:邊界壓縮
- 動態回收記憶體
- 優化Dalvik虛擬機器的堆記憶體分配
- 自定義堆記憶體大小
3、介面優化
太多重疊的背景(overdraw)
這個問題其實最容易解決,建議就是檢查你在佈局和程式碼中設定的背景,有些背景是隱藏在底下的,它永遠不可能顯示出來,這種沒必要的背景一定要移除,因為它很可能會嚴重影響到app的效能。如果採用的是selector的背景,將normal狀態的color設定為”@android:color/transparent”,也同樣可以解決問題。
太多重疊的View
第一個建議是 :是喲過ViewStub來載入一些不常用的佈局,它是一個輕量級且預設是不可見的檢視,可以動態的載入一個佈局,只要你用到這個重疊著的View的時候才載入,推遲載入的時間。
第二個建議是:如果使用了類似Viewpager+Fragment這樣的組合或者有多個Fragment在一個介面上,需要控制Fragment的顯示和隱藏,儘量使用動態的Inflation view,它的效能要比SetVisibility好。
複雜的Layout層級
這裡的建議比較多一些,首先推薦使用Android提供的佈局工具Hierarchy Viewer來檢查和優化佈局。第一個建議是:如果巢狀的線性佈局加深了佈局層次,可以使用相對佈局來取代。第二個建議是:用標籤來合併佈局。第三個建議是:用標籤來重用佈局,抽取通用的佈局可以讓佈局的邏輯更清晰明瞭。記住,這些建議的最終目的都是使得你的Layout在Hierarchy Viewer裡變得寬而淺,而不是窄而深。
4、移動端獲取網路資料優化的幾個點
連線複用:節省連線建立時間,如開啟 keep-alive。
對於Android來說預設情況下HttpURLConnection和HttpClient都開啟了keep-alive。只是2.2之前HttpURLConnection存在影響連線池的Bug,具體可見:Android HttpURLConnection及HttpClient選擇請求合併:即將多個請求合併為一個進行請求,比較常見的就是網頁中的CSS Image Sprites。如果某個頁面內請求過多,也可以考慮做一定的請求合併。
- 減少請求資料的大小:對於post請求,body可以做gzip壓縮的,header也可以做資料壓縮(不過只支援http 2.0)。
- 返回資料的body也可以做gzip壓縮,body資料體積可以縮小到原來的30%左右。(也可以考慮壓縮返回的json資料的key資料的體積,尤其是針對返回資料格式變化不大的情況,支付寶聊天返回的資料用到了)
- 根據使用者的當前的網路質量來判斷下載什麼質量的圖片(電商用的比較多)