1.Android系統(tǒng)的架構(gòu)
Android采用層次化系統(tǒng)架構(gòu)偏螺,官方公布的標(biāo)準(zhǔn)架構(gòu)如下圖所示撞芍。Android由底層往上分為4個主要功能層徘键,分別是linux內(nèi)核層(Linux Kernel)赃泡,系統(tǒng)運行時庫層(Libraries和Android Runtime),應(yīng)用程序架構(gòu)層(Application Framework)和應(yīng)用程序?qū)樱ˋpplications)鞋真。
1.Android系統(tǒng)架構(gòu)之應(yīng)用程序
Android會同一系列核心應(yīng)用程序包一起發(fā)布崇堰,該應(yīng)用程序包包括email客戶端,SMS短消息程序涩咖,日歷海诲,地圖,瀏覽器檩互,聯(lián)系人管理程序等特幔。所有的應(yīng)用程序都是使用JAVA語言編寫的。
2.Android系統(tǒng)架構(gòu)之應(yīng)用程序框架
開發(fā)人員可以完全訪問核心應(yīng)用程序所使用的API框架(android.jar)闸昨。該應(yīng)用程序的架構(gòu)設(shè)計簡化了組件的重用;任何一個應(yīng)用程序都可以發(fā)布它的功能塊并且任何其它的應(yīng)用程序都可以使用其所發(fā)布的功能塊蚯斯。
3.Android系統(tǒng)架構(gòu)之系統(tǒng)運行庫
Android 包含一些C/C++庫,這些庫能被Android系統(tǒng)中不同的組件使用零院。它們通過 Android 應(yīng)用程序框架為開發(fā)者提供服務(wù)溉跃。
4.Android系統(tǒng)架構(gòu)之Linux 內(nèi)核
Android 的核心系統(tǒng)服務(wù)依賴于 Linux 2.6 內(nèi)核,如安全性告抄,內(nèi)存管理撰茎,進程管理, 網(wǎng)絡(luò)協(xié)議棧和驅(qū)動模型打洼。 Linux 內(nèi)核也同時作為硬件和軟件棧之間的抽象層龄糊。
2.Activity的生命周期
3.Activity的四種啟動模式
基礎(chǔ)知識
Activity任務(wù)棧(Task):
Activity任務(wù)棧(Task)是一個標(biāo)準(zhǔn)的棧結(jié)構(gòu),具有“First In Last Out”的特性募疮,用于在ActivityManagerService側(cè)管理所有的Activity(AMS通過TaskRecord標(biāo)識一個任務(wù)棧炫惩,通過ActivityRecord標(biāo)識一個Activity)。
每當(dāng)我們打開一個Activity時阿浓,就會有一個Activity組件被添加到任務(wù)棧他嚷,每當(dāng)我們通過“back”鍵退出一個Activity時,就會有一個Activity組件從任務(wù)棧出棧芭毙。任意時刻筋蓖,只有位于棧頂?shù)腁ctivity才可以跟用戶進行交互。
同一時刻退敦,Android系統(tǒng)可以有多個任務(wù)棧粘咖;每個任務(wù)棧可能有一個或多個Activity侈百,這些Activity可能來自于同一個應(yīng)用程序瓮下,也可能來自于多個應(yīng)用程序翰铡。另外,同一個Activity可能只有一個實例讽坏,也可能有多個實例锭魔,而且這些實例既可能位于同一個任務(wù)棧,也可能位于不同的任務(wù)棧震缭。而這些行為都可以通過Activity啟動模式進行控制赂毯。
在Android系統(tǒng)的多個任務(wù)棧中,只有一個處于前臺拣宰,即前臺任務(wù)棧党涕,其它的都位于后臺,即后臺任務(wù)棧巡社。后臺任務(wù)棧中的Activity處于暫停狀態(tài)膛堤,用戶可以通過喚起后臺任務(wù)棧中的任意Activity,將后臺任務(wù)棧切換到前臺晌该。
android:taskAffinity屬性
android:taskAffinity是Activity的一個屬性肥荔,表示該Activity期望的任務(wù)棧的名稱。默認(rèn)情況下朝群,一個應(yīng)用程序中所有Activity的taskAffinity都是相同的燕耿,即應(yīng)用程序的包名。當(dāng)然姜胖,我們可以在配置文件中為每個Activity指定不同的taskAffinity(只有和已有包名不同誉帅,才有意義)。一般情況下右莱,該屬性主要和SingleTask啟動模式或者android:allowTaskReparenting屬性結(jié)合使用(下面會詳細(xì)介紹)蚜锨,在其他情況下沒有意義。
四種啟動模式
1.Standard:
標(biāo)準(zhǔn)模式慢蜓,也是系統(tǒng)的默認(rèn)模式亚再。該模式下,每次啟動Activity晨抡,都會創(chuàng)建一個新實例氛悬,并且將其加入到啟動該Activity的那個Activity所在的任務(wù)棧中,所以目標(biāo)Activity的多個實例可以位于不同的任務(wù)棧耘柱。例如:ActivityA啟動了標(biāo)準(zhǔn)模式的ActivityB圆雁,那么ActivityB就會在ActivityA所在的任務(wù)棧中。
啟動順序:A->B->C
回退順序:C->B->A.
![]()
2.SingleTop
棧頂復(fù)用模式帆谍。該模式下,若目標(biāo)Activity的實例已經(jīng)存在轴咱,但是沒有位于棧頂汛蝙,那么仍然會創(chuàng)建新的實例烈涮,并添加到任務(wù)棧;若目標(biāo)Activity的實例已經(jīng)存在窖剑,且位于棧頂坚洽,那么就不會創(chuàng)建新的實例,而是復(fù)用已有實例西土,并依次調(diào)用目標(biāo)Activity的onPause -> onNewIntent -> onResume方法讶舰。
例如A.B啟動模式為Standard,C啟動模式為SingleTop
啟動順序:A->B->C—>C
回退順序:C->B->A.而不是C->C->B->A
![]()
3.SingleTask
棧內(nèi)復(fù)用模式需了。該模式是對SingleTop的進一步加強跳昼,若Activity實例已經(jīng)存在,則不管是不是在棧頂鹅颊,都不會創(chuàng)建新的實例堪伍,而是復(fù)用已有Activity實例,即清除任務(wù)棧中目標(biāo)Activity之上的所有Activity帝雇,使其位于棧頂蛉拙,同時也會調(diào)用其onNewIntent方法尸闸;若Activity實例不存在,系統(tǒng)首先會確認(rèn)是否有目標(biāo)Activity期望的任務(wù)棧茧痕,如果沒有踪旷,就首先創(chuàng)建目標(biāo)Activity期望的任務(wù)棧,然后創(chuàng)建目標(biāo)Activity實例并添加到期望的任務(wù)棧中气破;相反低匙,若存在期望的任務(wù)棧顽冶,那么就直接創(chuàng)建目標(biāo)Activity實例并將其添加到期望的任務(wù)棧。
而Activity期望的任務(wù)棧名稱就是通過上面介紹的android:taskAffinity屬性進行設(shè)置的间景。
例如A.C.D啟動模式為Standard,B啟動模式為SingleTask
啟動順序:A->B->C—>D—>B
回退順序:B->A.而不是B—>D->C->B->A
以上例子未考慮android:taskAffinity屬性
4.SingleInstance
應(yīng)用場景:系統(tǒng)的發(fā)短信碗誉,打電話,來電尝苇,瀏覽器等。
單實例模式非竿。該模式是SingleTask的強化,除了具有SingleTask的所有特性外锤悄,還強調(diào)任意時刻只允許存在唯一的Activity實例,且該Activity實例獨自占有一個任務(wù)棧隶症。即該任務(wù)棧只能容納該Activity實例,不能再添加其他Activity實例到該任務(wù)棧,如果該Activity實例已經(jīng)存在于某個任務(wù)棧措嵌,則直接跳轉(zhuǎn)到該任務(wù)棧。
例如A.C啟動模式為Standard探孝,B啟動模式為SingleInstance
啟動順序:A->B->C顿颅;注意:此時產(chǎn)生了兩個任務(wù)棧庇配,B產(chǎn)生了一個新的任務(wù)棧,并處于其他任務(wù)棧的下面卿闹。
回退順序:C->A->B.而不是C->B->A
具體可參考:Activity啟動模式一
圖片來源:深入Activity揪漩,Activity啟動模式LaunchMode完全解析
4.Fragment的生命周期
Fragment與Activity生命周期對比:
5.Activity與Fragment之間如何進行通信冰更?
1.Activity中可以通過getFragmentManager().findFragmentById()拿到Fragment對象
2.通過接口
3.通過廣播接收者
6.Service的生命周期
7.Service的啟動方式
1.直接啟動的方式
startService啟動服務(wù),stopService停止服務(wù)谆刨。
完整生命周期回調(diào)順序為:onCreate -> onStartCommand -> onDestroy
有效生命周期為:onStartCommand和onDestroy之間
生命周期方法介紹:
onCreate 創(chuàng)建服務(wù)時回調(diào)。onCreate只會調(diào)用一次
onStartCommand 啟動服務(wù)時回調(diào)。一旦啟動番舆,服務(wù)即可在后臺運行,即使啟動服務(wù)的組件已被銷毀也不受影響拴事。每次調(diào)用startService()時都會回調(diào),允許多次調(diào)用。 傳參startId是請求的id唯一標(biāo)識坦袍,其返回值描述系統(tǒng)應(yīng)該如何在服務(wù)終止的情況下繼續(xù)運行服務(wù),是對系統(tǒng)的要求。
返回值取值為:
START_NOT_STICKY 如果系統(tǒng)在 onStartCommand() 返回后終止服務(wù)压真,則除非有掛起 Intent 要傳遞岳悟,否則系統(tǒng)不會重建服務(wù)。這是最安全的選項,可以避免在不必要時以及應(yīng)用能夠輕松重啟所有未完成的作業(yè)時運行服務(wù)。
START_STICKY 如果系統(tǒng)在 onStartCommand() 返回后終止服務(wù)潜沦,則會重建服務(wù)并調(diào)用 onStartCommand(),但絕對不會重新傳遞最后一個 Intent。相反臂痕,除非有掛起 Intent 要啟動服務(wù)(在這種情況下,將傳遞這些 Intent )澡绩,否則系統(tǒng)會通過空 Intent 調(diào)用 onStartCommand()昙读。這適用于不執(zhí)行命令、但無限期運行并等待作業(yè)的媒體播放器(或類似服務(wù))。
START_REDELIVER_INTENT 如果系統(tǒng)在 onStartCommand() 返回后終止服務(wù),則會重建服務(wù)调鲸,并通過傳遞給服務(wù)的最后一個 Intent 調(diào)用 onStartCommand()。任何掛起 Intent 均依次傳遞。這適用于主動執(zhí)行應(yīng)該立即恢復(fù)的作業(yè)(例如下載文件)的服務(wù)株依。
onDestroy 停止服務(wù)時回調(diào)。此方法用來清理所有資源,如線程商源、注冊的偵聽器、接收器等。需要調(diào)用stopSelf(int)或stopService()停止服務(wù)总寻。當(dāng)有多個請求時,stopSelf(int) 確保服務(wù)停止請求始終基于最近的啟動請求id肴沫,服務(wù)才能停止,否則服務(wù)會繼續(xù)運行。
2.綁定的方式
bindService綁定服務(wù)菱魔,unbindService解綁服務(wù)。
完整生命周期回調(diào)順序為:onCreate -> onBind -> onUnbind -> onDestroy
有效生命周期為:onBind和onUnbind之間
生命周期方法介紹:
onCreate 創(chuàng)建服務(wù)時回調(diào)既荚。onCreate只會調(diào)用一次
onBind 綁定服務(wù)時回調(diào)句各。綁定服務(wù)提供了一個客戶端-服務(wù)器接口兼蕊,允許組件與服務(wù)進行交互产禾、發(fā)送請求、獲取結(jié)果衫生,甚至是利用進程間通信 (IPC) 跨進程執(zhí)行這些操作,如果不允許綁定站故,則應(yīng)返回null。 僅當(dāng)與另一個應(yīng)用組件綁定時,綁定服務(wù)才會運行。 多個組件可以同時綁定到該服務(wù)粱甫,但全部取消綁定后宗挥,該服務(wù)即會被銷毀瞒大。只有在第一個客戶端綁定時,系統(tǒng)才會調(diào)用服務(wù)的 onBind() 方法來檢索 IBinder,系統(tǒng)隨后無需再次調(diào)用 onBind(),便可將同一 IBinder 傳遞至任何其他綁定的客戶端荷荤。該方法不一定在UI線程。
onUnbind 解綁服務(wù)時回調(diào)翻翩。當(dāng)所有客戶端都與Service斷開連接時調(diào)用塞椎。默認(rèn)返回false服傍,當(dāng)返回值為true時拉庵,后續(xù)有新Client綁定時會回調(diào)onRebind()
onRebind 重新綁定時回調(diào)。onUnbind()返回true,且有新Client綁定時調(diào)用
onDestroy 停止服務(wù)時回調(diào)。此方法用來清理所有資源,如線程振湾、注冊的偵聽器、接收器等续语。服務(wù)與所有客戶端之間的綁定全部取消時根暑,系統(tǒng)便會銷毀它
綁定方式詳細(xì)生命周期圖:
具體可參考:Service知識總結(jié)
8.# 6.Service 和 Activity 在同一個線程嗎
默認(rèn)情況下service與activity在同一個線程懂版,都在main Thread,或者ui線程中躏率。
如果在清單文件中指定service的process屬性躯畴,那么service就在另一個進程中運行。
9.進程鞭敝ィ活
1、開啟一個像素的Activity
據(jù)說這個是手Q的進程焙坏剑活方案嚷缭,基本思想,系統(tǒng)一般是不會殺死前臺進程的耍贾。所以要使得進程常駐阅爽,我們只需要在鎖屏的時候在本進程開啟一個Activity,為了欺騙用戶荐开,讓這個Activity的大小是1像素付翁,并且透明無切換動畫,在開屏幕的時候晃听,把這個Activity關(guān)閉掉百侧,所以這個就需要監(jiān)聽系統(tǒng)鎖屏廣播.
2、前臺服務(wù)
這種大部分人都了解能扒,據(jù)說這個微信也用過的進程庇犊剩活方案,移步微信Android客戶端后臺背醢撸活經(jīng)驗分享辛润,這方案實際利用了Android前臺service的漏洞。
3见秤、相互喚醒
相互喚醒的意思就是频蛔,假如你手機里裝了支付寶灵迫、淘寶、天貓晦溪、UC等阿里系的app瀑粥,那么你打開任意一個阿里系的app后,有可能就順便把其他阿里系的app給喚醒了三圆。這個完全有可能的狞换。此外,開機舟肉,網(wǎng)絡(luò)切換修噪、拍照、拍視頻時候路媚,利用系統(tǒng)產(chǎn)生的廣播也能喚醒app黄琼,不過Android N已經(jīng)將這三種廣播取消了。
4整慎、JobSheduler
JobSheduler是作為進程死后復(fù)活的一種手段脏款,native進程方式最大缺點是費電, Native 進程費電的原因是感知主進程是否存活有兩種實現(xiàn)方式裤园,在 Native 進程中通過死循環(huán)或定時器撤师,輪訓(xùn)判斷主進程是否存活,當(dāng)主進程不存活時進行拉活拧揽。其次5.0以上系統(tǒng)不支持剃盾。 但是JobSheduler可以替代在Android5.0以上native進程方式,這種方式即使用戶強制關(guān)閉淤袜,也能被拉起來.
5痒谴、粘性服務(wù)&與系統(tǒng)服務(wù)捆綁
這個是系統(tǒng)自帶的,onStartCommand方法必須具有一個整形的返回值铡羡,這個整形的返回值用來告訴系統(tǒng)在服務(wù)啟動完畢后积蔚,如果被Kill,系統(tǒng)將如何操作蓖墅,這種方案雖然可以库倘,但是在某些情況or某些定制ROM上可能失效临扮,我認(rèn)為可以多做一種保保守方案论矾。
具體可參考:Android進程保活的一般套路
10.BroadcastReceiver的注冊方式
首先寫一個類要繼承BroadCastReceiver
第一種:在清單文件中聲明杆勇,添加
<receive android:name=".BroadCastReceiverDemo">
<intent-filter>
<action android:name="android.provider.Telephony.SMS_RECEIVED">
</intent-filter>
</receiver>
第二種:使用代碼進行注冊如:
IntentFilter filter = new IntentFilter("android.provider.Telephony.SMS_RECEIVED");
BroadCastReceiverDemo receiver = new BroadCastReceiver();
registerReceiver(receiver, filter);
兩種注冊類型的區(qū)別是:
a.第一種是常駐型廣播贪壳,也就是說當(dāng)應(yīng)用程序關(guān)閉后,如果有信息廣播來蚜退,程序也會被系統(tǒng)調(diào)用自動運行闰靴。
b.第二種不是常駐廣播彪笼,也就是說廣播跟隨程序的生命周期。
11.ContentProvider
ContentProvider一般為存儲和獲取數(shù)據(jù)提供統(tǒng)一的接口蚂且,可以在不同的應(yīng)用程序之間共享數(shù)據(jù)配猫。
之所以使用ContentProvider,主要有以下幾個理由:
1杏死,ContentProvider提供了對底層數(shù)據(jù)存儲方式的抽象泵肄。比如下圖中,底層使用了SQLite數(shù)據(jù)庫淑翼,在用了ContentProvider封裝后腐巢,即使你把數(shù)據(jù)庫換成MongoDB,也不會對上層數(shù)據(jù)使用層代碼產(chǎn)生影響
2玄括,Android框架中的一些類需要ContentProvider類型數(shù)據(jù)冯丙。如果你想讓你的數(shù)據(jù)可以使用在如SyncAdapter, Loader, CursorAdapter等類上,那么你就需要為你的數(shù)據(jù)做一層ContentProvider封裝遭京。
3胃惜,第三個原因也是最主要的原因,是ContentProvider為應(yīng)用間的數(shù)據(jù)交互提供了一個安全的環(huán)境洁墙。它準(zhǔn)許你把自己的應(yīng)用數(shù)據(jù)根據(jù)需求開放給其他應(yīng)用進行增蛹疯、刪、改热监、查捺弦,而不用擔(dān)心直接開放數(shù)據(jù)庫權(quán)限而帶來的安全問題。
我們知道了ContentProvider是對數(shù)據(jù)層的封裝后孝扛,那么大家可能會問我們要如何對ContentProvider進行增列吼,刪,改苦始,查的操作呢寞钥?下面我們來介紹一個新的類ContentResolver,我們可以通過它陌选,來對不同的ContentProvider進行操作理郑。
具體實現(xiàn):
首先我們創(chuàng)建一個自己的TestProvider繼承ContentProvider。默認(rèn)該Provider需要實現(xiàn)如下六個方法咨油,onCreate(), query(Uri, String[], String, String[], String),insert(Uri, ContentValues), update(Uri, ContentValues, String, String[]), delete(Uri, String, String[]), getType(Uri)您炉,方法的具體介紹可以參考
http://developer.android.com/reference/android/content/ContentProvider.html
因為ContentProvider作為四大組件之一,所以還需要在AndroidManifest.xml中注冊一下役电。
然后你就可以使用getContentResolver()方法來對該ContentProvider進行操作了赚爵,ContentResolver對應(yīng)ContentProvider也有insert,query,delete等方法冀膝,詳情請參考:
http://developer.android.com/reference/android/content/ContentResolver.html
具體可參考:ContentProvider從入門到精通
12.Handler的消息機制
在整個Handler機制中所有使用到的類唁奢,主要包括Message,MessageQueue,Looper以及Handler窝剖。
Handler是Android中引入的一種讓開發(fā)者參與處理線程中消息循環(huán)的機制麻掸,Handler直接繼承自O(shè)bject,如果要使用Handler必須先調(diào)用Looper.prepare();方法,然后再初始化Handler,之后再調(diào)用Looper.loop();方法,每個Handler都關(guān)聯(lián)了一個線程赐纱,每個線程內(nèi)部都維護了一個消息隊列MessageQueue论笔,這樣Handler實際上也就關(guān)聯(lián)了一個消息隊列。這樣就可以通過Handler將Message和Runnable對象發(fā)送到該Handler所關(guān)聯(lián)線程的MessageQueue(消息隊列)中千所,然后該消息隊列通過Looper一直在循環(huán)拿出一個Message狂魔,對其進行處理,處理完之后拿出下一個Message淫痰,繼續(xù)處理.
Handler可以用來在多線程之間進行通信最楷,在另一個線程中去更新UI線程中的UI控件只是Handler使用中的一種典型案例,除此之外待错,Handler還可以做其他很多的事情籽孙,Handler是Thread的代言人,是多線程之間通信的橋梁火俄,通過Handler犯建,我們可以在一個線程中控制另一個線程去做某些事.
具體可參考:Handler 原理梳理
13.事件分發(fā)機制
主要涉及到以下三個方法:
public boolean dispatchTouchEvent(MotionEvent ev); 這個方法用來進行事件的分發(fā)
public boolean onInterceptTouchEvent(MotionEvent ev); 這個方法用來判斷是否攔截事件
onTouchEvent(MotionEvent ev); 這個方法用來處理點擊事件。
點擊事件的傳遞規(guī)則:
對于一個根ViewGroup瓜客,點擊事件產(chǎn)生后适瓦,首先會傳遞給他,這時候就會調(diào)用他的onDispatchTouchEvent方法谱仪,如果Viewgroup的onInterceptTouchEvent方法返回true表示他要攔截事件玻熙,接下來事件就會交給ViewGroup處理,調(diào)用ViewGroup的onTouchEvent方法疯攒;如果ViewGroup的onInteceptTouchEvent方法返回值為false嗦随,表示ViewGroup不攔截該事件,這時事件就傳遞給他的子View敬尺,接下來子View的dispatchTouchEvent方法枚尼,如此反復(fù)直到事件被最終處理。
當(dāng)一個View需要處理事件時砂吞,如果它設(shè)置了OnTouchListener署恍,那么onTouch方法會被調(diào)用,如果onTouch返回false呜舒,則當(dāng)前View的onTouchEvent方法會被調(diào)用锭汛,返回true則不會被調(diào)用,同時袭蝗,在onTouchEvent方法中如果設(shè)置了OnClickListener唤殴,那么他的onClick方法會被調(diào)用。由此可見處理事件時的優(yōu)先級關(guān)系: onTouchListener > onTouchEvent > onClickListener.
事件傳遞的機制到腥,一些結(jié)論:
1.一個事件系列以down事件開始,中間包含數(shù)量不定的move事件,最終以up事件結(jié)束.
2.正常情況下,一個事件序列只能由一個View攔截并消耗朵逝。
3.某個View攔截了事件后,該事件序列只能由它去處理,并且它的onInterceptTouchEvent不會再被調(diào)用.
4.某個View一旦開始處理事件,如果它不消耗ACTION_DOWN事件(onTouchEvnet返回false),那么同一事件序列中的其他事件都不會交給他處理,并且事件將重新交由他的父元素去處理,即父元素的onTouchEvent被調(diào)用.
5.如果View不消耗ACTION_DOWN以外的其他事件,那么這個事件將會消失,此時父元素的onTouchEvent并不會被調(diào)用,并且當(dāng)前View可以持續(xù)收到后續(xù)的事件,最終消失的點擊事件會傳遞給Activity去處理.
6.ViewGroup默認(rèn)不攔截任何事件.
7.View沒有onInterceptTouchEvent方法,一旦事件傳遞給它,它的onTouchEvent方法會被調(diào)用.
8.View的onTouchEvent默認(rèn)消耗事件,除非他是不可點擊的(clickable和longClickable同時為false).
9.onClick會發(fā)生的前提是當(dāng)前View是可點擊的,并且收到了down和up事件.
10.事件傳遞過程總是由外向內(nèi)的,即事件總是先傳遞給父元素,然后由父元素分發(fā)給子View,通過requestDisallowInterceptTouchEvent方法可以在子元素中干預(yù)父元素的分發(fā)過程,但是ACTION_DOWN事件除外.
具體可參考:View的事件體系(四)View 的事件分發(fā)機制
14.動畫
1乡范、View動畫
View動畫定義了漸變Alpha配名、旋轉(zhuǎn)Rotate、縮放Scale晋辆、平移Translate四種基本動畫.
使用:
a.xml+java代碼:
公有屬性:
android:duration 動畫持續(xù)時間
android:fillAfter 為true動畫結(jié)束時渠脉,View將保持動畫結(jié)束時的狀態(tài)
android:fillBefore 為true動畫結(jié)束時,View將還原到開始開始時的狀態(tài)
android:repeatCount 動畫重復(fù)執(zhí)行的次數(shù)
android:repeatMode 動畫重復(fù)模式 瓶佳,重復(fù)播放時restart重頭開始芋膘,reverse重復(fù)播放時倒敘回放,該屬性需要和android:repeatCount一起使用
android:interpolator 插值器霸饲,相當(dāng)于變速器为朋,改變動畫的不同階段的執(zhí)行速度
View動畫都要放在anim目錄下。
漸變view_anim_alpha.xml:
<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="2000"
android:fromAlpha="1.0"
android:toAlpha="0">
</alpha>
旋轉(zhuǎn)view_anim_rotate.xml:
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="2000"
android:fillAfter="true"
android:fromDegrees="0"
android:pivotX="50%"
android:pivotY="50%"
android:toDegrees="360">
</rotate>
縮放view_anim_scale.xml:
<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="2000"
android:fromXScale="1.0"
android:fromYScale="1.0"
android:pivotX="50%"
android:pivotY="50%"
android:toXScale="0.5"
android:toYScale="0.5">
</scale>
平移view_anim_translate.xml:
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="2000"
android:fromXDelta="0"
android:fromYDelta="0"
android:toXDelta="100%"
android:toYDelta="100%">
</translate>
代碼:
public void clickToAlpha(View view) {
Animation alphaAnim = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.view_anim_alpha);
mTargetView.startAnimation(alphaAnim);
}
public void clickToRotate(View view) {
Animation rotateAnim = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.view_anim_rotate);
mTargetView.startAnimation(rotateAnim);
}
public void clickToScale(View view) {
Animation scaleAnim = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.view_anim_scale);
mTargetView.startAnimation(scaleAnim);
}
public void clickToTranslate(View view) {
Animation translateAnim = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.view_anim_translate);
mTargetView.startAnimation(translateAnim);
}
public void clickToSet(View view) {
Animation setAnim = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.view_anim_set);
mTargetView.startAnimation(setAnim);
}
b.java代碼:
在平常的業(yè)務(wù)邏輯中也可以直接用Java代碼來實現(xiàn)Veiw動畫厚脉,Android系統(tǒng)給我們提供了AlphaAnimation习寸、RotateAnimation、ScaleAnimation傻工、TranslateAnimation四個動畫類分別來實現(xiàn)View的漸變霞溪、旋轉(zhuǎn)、縮放中捆、平移動畫威鹿。
漸變:
public void clickToAlpha(View view) {
AlphaAnimation alphaAnimation = new AlphaAnimation(1, 0);
alphaAnimation.setDuration(2000);
mTargetView.startAnimation(alphaAnimation);
}
旋轉(zhuǎn):
public void clickToRotate(View view) {
RotateAnimation rotateAnimation = new RotateAnimation(
0, 360,
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f);
rotateAnimation.setDuration(2000);
mTargetView.startAnimation(rotateAnimation);
}
縮放:
public void clickToScale(View view) {
ScaleAnimation scaleAnimation = new ScaleAnimation(
1, 0.5f,
1, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f);
scaleAnimation.setDuration(2000);
mTargetView.startAnimation(scaleAnimation);
}
平移:
public void clickToTranslate(View view) {
TranslateAnimation translateAnimation = new TranslateAnimation(
Animation.RELATIVE_TO_SELF, 0,
Animation.RELATIVE_TO_SELF, 1,
Animation.RELATIVE_TO_SELF, 0,
Animation.RELATIVE_TO_SELF, 1);
translateAnimation.setDuration(2000);
mTargetView.startAnimation(translateAnimation);
}
組合:
public void clickToSet(View view) {
AlphaAnimation alphaAnimation = new AlphaAnimation(1, 0);
alphaAnimation.setDuration(2000);
RotateAnimation rotateAnimation = new RotateAnimation(
0, 360,
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f);
rotateAnimation.setDuration(2000);
ScaleAnimation scaleAnimation = new ScaleAnimation(
1, 0.5f,
1, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f);
scaleAnimation.setDuration(2000);
TranslateAnimation translateAnimation = new TranslateAnimation(
Animation.RELATIVE_TO_SELF, 0,
Animation.RELATIVE_TO_SELF, 1,
Animation.RELATIVE_TO_SELF, 0,
Animation.RELATIVE_TO_SELF, 1);
translateAnimation.setDuration(2000);
AnimationSet animationSet = new AnimationSet(true);
animationSet.addAnimation(alphaAnimation);
animationSet.addAnimation(rotateAnimation);
animationSet.addAnimation(scaleAnimation);
animationSet.addAnimation(translateAnimation);
mTargetView.startAnimation(animationSet);
}
2、屬性動畫
所謂屬性動畫轨香,就是改變對象Object的屬性來實現(xiàn)動畫過程忽你。屬性動畫是對View的動畫的擴展,通過它可以實現(xiàn)更多漂亮的動畫效果臂容。同時屬性動畫的作用對象不僅僅是View科雳,任何對象都可以。
屬性動畫的作用效果就是:在一個指定的時間段內(nèi)將對象的一個屬性的屬性值動態(tài)地變化到另一個屬性值脓杉。
3糟秘、幀動畫
幀動畫需要開發(fā)者制定好動畫每一幀,系統(tǒng)一幀一幀的播放圖片球散。
使用:
a.java代碼:
private void start() {
AnimationDrawable ad = new AnimationDrawable();
for (int i = 0; i < 7; i++) {
Drawable drawable = getResources().getDrawable(getResources().getIdentifier("ic_fingerprint_" + i, "drawable", getPackageName()));
ad.addFrame(drawable, 100);
}
ad.setOneShot(false);
mImageView.setImageDrawable(ad);
ad.start();
}
b.xml+java代碼使用:
直接在工程drawable目錄新建animation-list標(biāo)簽:
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false">
<item android:drawable="@drawable/ic_fingerprint_0" android:duration="100"/>
<item android:drawable="@drawable/ic_fingerprint_1" android:duration="100"/>
<item android:drawable="@drawable/ic_fingerprint_2" android:duration="100"/>
<item android:drawable="@drawable/ic_fingerprint_3" android:duration="100"/>
<item android:drawable="@drawable/ic_fingerprint_4" android:duration="100"/>
<item android:drawable="@drawable/ic_fingerprint_5" android:duration="100"/>
<item android:drawable="@drawable/ic_fingerprint_6" android:duration="100"/>
</animation-list>
代碼中:
private void start() {
mImageView.setImageResource(R.drawable.frame_anim);
AnimationDrawable animationDrawable = (AnimationDrawable) mImageView.getDrawable();
animationDrawable.start();
}
具體可參考:Android動畫總結(jié)——View動畫尿赚、屬性動畫、幀動畫
15.ListView和RecyclerView
ListView和RecycleView的緩存原理大致相同,如下圖:
都是在內(nèi)部維護一個緩存池凌净,回收劃出列表的item悲龟,添加給將要進入列表的item。只不過ListView內(nèi)部是兩級緩存冰寻,分別是mActiveViews和mScrapViews.而RecycleView內(nèi)部有四級緩存须教。
ListView相比RecyclerView,有一些優(yōu)點:
addHeaderView(), addFooterView()添加頭視圖和尾視圖斩芭。
通過”android:divider”設(shè)置自定義分割線轻腺。
setOnItemClickListener()和setOnItemLongClickListener()設(shè)置點擊事件和長按事件。
這些功能在RecyclerView中都沒有直接的接口划乖,要自己實現(xiàn)(雖然實現(xiàn)起來很簡單)贬养,因此如果只是實現(xiàn)簡單的顯示功能,ListView無疑更簡單琴庵。
RecyclerView相比ListView煤蚌,有一些明顯的優(yōu)點:
默認(rèn)已經(jīng)實現(xiàn)了View的復(fù)用,不需要類似if(convertView == null)的實現(xiàn)细卧,而且回收機制更加完善尉桩。
默認(rèn)支持局部刷新。
容易實現(xiàn)添加item贪庙、刪除item的動畫效果蜘犁。
容易實現(xiàn)拖拽、側(cè)滑刪除等功能止邮。
DiffUtil可用于高效進行RecyclerView的數(shù)據(jù)更新这橙。
RecyclerView是一個插件式的實現(xiàn),對各個功能進行解耦导披,從而擴展性比較好.
具體可參考以下幾篇文章:
Android ListView與RecyclerView對比淺析--緩存機制
16.6.0權(quán)限
鑒于6.0之前的版本權(quán)限管理相對不那么安全屈扎,所以Android 6.0 采用新的權(quán)限模型,只有在需要權(quán)限的時候撩匕,才告知用戶是否授權(quán)鹰晨,是在runtime時候授權(quán),而不是在原來安裝的時候 止毕,同時默認(rèn)情況下每次在運行時打開頁面時候模蜡,需要先檢查是否有所需要的權(quán)限申請。這樣的用戶的自主性提高很多扁凛,比如用戶可以給APP賦予攝像的權(quán)限忍疾,也可以使用權(quán)限。
適配方法:
- targetSdkVersion低于23
- 動態(tài)權(quán)限管理
例子:
// 首先檢查權(quán)限
if(ContextCompat.checkSelfPermission(thisActivity,Manifest.permission.READ_CONTACTS)
!= PackageManager.PERMISSION_GRANTED) {
// 檢查用戶是否拒絕了這個權(quán)限
if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
Manifest.permission.READ_CONTACTS)) {
// 給出一個提示谨朝,告訴用戶為什么需要這個權(quán)限
} else {
// 用戶沒有拒絕卤妒,直接申請權(quán)限
ActivityCompat.requestPermissions(thisActivity,
new String[]{Manifest.permission.READ_CONTACTS},
MY_PERMISSIONS_REQUEST_READ_CONTACTS);
//用戶授權(quán)的結(jié)果會回調(diào)到FragmentActivity的onRequestPermissionsResult
}
}else {
//已經(jīng)擁有授權(quán)
//TODO: 正常業(yè)務(wù)邏輯
}
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
switch (requestCode) {
case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
readContacts();
} else {
// 權(quán)限拒絕了甥绿。
}
return;
}
}
}
17.大圖片加載的處理
1.壓縮
BitmapFactory這個類提供了多個解析方法(decodeByteArray, decodeFile, decodeResource等)用于創(chuàng)建Bitmap對象,我們應(yīng)該根據(jù)圖片的來源選擇合適的方法则披。比如SD卡中的圖片可以使用decodeFile方法共缕,網(wǎng)絡(luò)上的圖片可以使用decodeStream方法,資源文件中的圖片可以使用decodeResource方法收叶。這些方法會嘗試為已經(jīng)構(gòu)建的bitmap分配內(nèi)存,這時就會很容易導(dǎo)致OOM出現(xiàn)共苛。為此每一種解析方法都提供了一個可選的BitmapFactory.Options參數(shù)判没,將這個參數(shù)的inJustDecodeBounds屬性設(shè)置為true就可以讓解析方法禁止為bitmap分配內(nèi)存,返回值也不再是一個Bitmap對象隅茎,而是null澄峰。雖然Bitmap是null了,但是BitmapFactory.Options的outWidth辟犀、outHeight和outMimeType屬性都會被賦值俏竞。這個技巧讓我們可以在加載圖片之前就獲取到圖片的長寬值和MIME類型,從而根據(jù)情況對圖片進行壓縮堂竟。如下代碼所示:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;
現(xiàn)在圖片的大小已經(jīng)知道了魂毁,我們就可以決定是把整張圖片加載到內(nèi)存中還是加載一個壓縮版的圖片到內(nèi)存中。以下幾個因素是我們需要考慮的:
a.預(yù)估一下加載整張圖片所需占用的內(nèi)存出嘹。
b.為了加載這一張圖片你所愿意提供多少內(nèi)存席楚。
c.用于展示這張圖片的控件的實際大小。
d.當(dāng)前設(shè)備的屏幕尺寸和分辨率税稼。
通過設(shè)置BitmapFactory.Options中inSampleSize的值就可以實現(xiàn)對圖片進行壓縮.下面的方法可以根據(jù)傳入的寬和高烦秩,計算出合適的inSampleSize值:
public static int calculateInSampleSize(BitmapFactory.Options options,
int reqWidth, int reqHeight) {
// 源圖片的高度和寬度
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
// 計算出實際寬高和目標(biāo)寬高的比率
final int heightRatio = Math.round((float) height / (float) reqHeight);
final int widthRatio = Math.round((float) width / (float) reqWidth);
// 選擇寬和高中最小的比率作為inSampleSize的值,這樣可以保證最終圖片的寬和高
// 一定都會大于等于目標(biāo)的寬和高郎仆。
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
return inSampleSize;
}
使用這個方法只祠,首先你要將BitmapFactory.Options的inJustDecodeBounds屬性設(shè)置為true,解析一次圖片扰肌。然后將BitmapFactory.Options連同期望的寬度和高度一起傳遞到到calculateInSampleSize方法中抛寝,就可以得到合適的inSampleSize值了。之后再解析一次圖片曙旭,使用新獲取到的inSampleSize值墩剖,并把inJustDecodeBounds設(shè)置為false,就可以得到壓縮后的圖片了夷狰。
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
// 第一次解析將inJustDecodeBounds設(shè)置為true岭皂,來獲取圖片大小
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// 調(diào)用上面定義的方法計算inSampleSize值
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// 使用獲取到的inSampleSize值再次解析圖片
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
2.緩存
內(nèi)存緩存技術(shù)對那些大量占用應(yīng)用程序?qū)氋F內(nèi)存的圖片提供了快速訪問的方法。其中最核心的類是LruCache (此類在android-support-v4的包中提供) 沼头。這個類非常適合用來緩存圖片爷绘,它的主要算法原理是把最近使用的對象用強引用存儲在 LinkedHashMap 中书劝,并且把最近最少使用的對象在緩存值達到預(yù)設(shè)定值之前從內(nèi)存中移除。
下面是一個使用 LruCache 來緩存圖片的例子:
private LruCache<String, Bitmap> mMemoryCache;
@Override
protected void onCreate(Bundle savedInstanceState) {
// 獲取到可用內(nèi)存的最大值土至,使用內(nèi)存超出這個值會引起OutOfMemory異常涣狗。
// LruCache通過構(gòu)造函數(shù)傳入緩存值,以KB為單位态蒂。
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// 使用最大可用內(nèi)存值的1/8作為緩存的大小巷嚣。
int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
// 重寫此方法來衡量每張圖片的大小,默認(rèn)返回圖片數(shù)量楷扬。
return bitmap.getByteCount() / 1024;
}
};
}
public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemCache(key) == null) {
mMemoryCache.put(key, bitmap);
}
}
public Bitmap getBitmapFromMemCache(String key) {
return mMemoryCache.get(key);
}
當(dāng)向 ImageView 中加載一張圖片時,首先會在 LruCache 的緩存中進行檢查解幽。如果找到了相應(yīng)的鍵值,則會立刻更新ImageView 烘苹,否則開啟一個后臺線程來加載這張圖片躲株。
public void loadBitmap(int resId, ImageView imageView) {
final String imageKey = String.valueOf(resId);
final Bitmap bitmap = getBitmapFromMemCache(imageKey);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
} else {
imageView.setImageResource(R.drawable.image_placeholder);
BitmapWorkerTask task = new BitmapWorkerTask(imageView);
task.execute(resId);
}
}
BitmapWorkerTask 還要把新加載的圖片的鍵值對放到緩存中。
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
// 在后臺加載圖片镣衡。
@Override
protected Bitmap doInBackground(Integer... params) {
final Bitmap bitmap = decodeSampledBitmapFromResource(
getResources(), params[0], 100, 100);
addBitmapToMemoryCache(String.valueOf(params[0]), bitmap);
return bitmap;
}
}
具體可參考: Android高效加載大圖霜定、多圖解決方案,有效避免程序OOM
18.斷點續(xù)傳的實現(xiàn)原理
其實斷點續(xù)傳的原理很簡單廊鸥,從字面上理解望浩,所謂斷點續(xù)傳就是從停止的地方重新下載。
斷點:線程停止的位置惰说。
續(xù)傳:從停止的位置重新下載曾雕。
用代碼解析就是:
斷點 : 當(dāng)前線程已經(jīng)下載完成的數(shù)據(jù)長度。
續(xù)傳 : 向服務(wù)器請求上次線程停止位置之后的數(shù)據(jù)助被。
原理知道了剖张,功能實現(xiàn)起來也簡單。每當(dāng)線程停止時就把已下載的數(shù)據(jù)長度寫入記錄文件揩环,當(dāng)重新下載時搔弄,從記錄文件讀取已經(jīng)下載了的長度。而這個長度就是所需要的斷點丰滑。
續(xù)傳的實現(xiàn)也簡單顾犹,可以通過設(shè)置網(wǎng)絡(luò)請求參數(shù),請求服務(wù)器從指定的位置開始讀取數(shù)據(jù)褒墨。
而要實現(xiàn)這兩個功能只需要使用到httpURLconnection里面的setRequestProperty方法便可以實現(xiàn).
public void setRequestProperty(String field, String newValue)
使用:
conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);
具體使用:
public class MutilDownloader {
// 開啟的線程的個數(shù)
public static final int THREAD_COUNT = 3;
public static int runningThread = 3;// 記錄正在運行的下載文件的線程數(shù)
public static void main(String[] args) throws Exception {
String path = "文件下載地址";
// 1炫刷、連接服務(wù)器,獲取一個文件郁妈,獲取文件的長度浑玛,在本地創(chuàng)建一個大小跟服務(wù)器文件大小一樣的臨時文件
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(5000);
conn.setRequestMethod("GET");
int code = conn.getResponseCode();
if (code == 200) {
// 服務(wù)器返回的數(shù)據(jù)的長度,實際就是文件的長度
int length = conn.getContentLength();
System.out.println("----文件總長度----" + length);
// 在客戶端本地創(chuàng)建出來一個大小跟服務(wù)器端文件一樣大小的臨時文件
RandomAccessFile raf = new RandomAccessFile("temp.apk", "rwd");
// 指定創(chuàng)建的這個文件的長度
raf.setLength(length);
// 關(guān)閉raf
raf.close();
// 假設(shè)是3個線程去下載資源
// 平均每一個線程下載的文件的大小
int blockSize = length / THREAD_COUNT;
for (int threadId = 1; threadId <= THREAD_COUNT; threadId++) {
// 第一個線程開始下載的位置
int startIndex = (threadId - 1) * blockSize;
int endIndex = threadId * blockSize - 1;
if (threadId == THREAD_COUNT) {
endIndex = length;
}
System.out.println("----threadId---" + "--startIndex--"
+ startIndex + "--endIndex--" + endIndex);
new DownloadThread(path, threadId, startIndex, endIndex).start();
}
}
}
/**
* 下載文件的子線程噩咪,每一個線程下載對應(yīng)位置的文件
*
* @author loonggg
*
*/
public static class DownloadThread extends Thread {
private int threadId;
private int startIndex;
private int endIndex;
private String path;
/**
* @param path
* 下載文件在服務(wù)器上的路徑
* @param threadId
* 線程id
* @param startIndex
* 線程下載的開始位置
* @param endIndex
* 線程下載的結(jié)束位置
*/
public DownloadThread(String path, int threadId, int startIndex,int endIndex) {
this.path = path;
this.threadId = threadId;
this.startIndex = startIndex;
this.endIndex = endIndex;
}
@Override
public void run() {
try {
// 檢查是否存在記錄下載長度的文件顾彰,如果存在讀取這個文件的數(shù)據(jù)
File tempFile = new File(threadId + ".txt");
if (tempFile.exists() && tempFile.length() > 0) {
FileInputStream fis = new FileInputStream(tempFile);
byte[] temp = new byte[1024 * 10];
int leng = fis.read(temp);
// 已經(jīng)下載的長度
String downloadLen = new String(temp, 0, leng);
int downloadInt = Integer.parseInt(downloadLen);
startIndex = downloadInt;
fis.close();
}
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
// 重要:請求服務(wù)器下載部分的文件 指定文件的位置
conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);
conn.setConnectTimeout(5000);
// 從服務(wù)器請求全部資源的狀態(tài)碼200 ok 如果從服務(wù)器請求部分資源的狀態(tài)碼206 ok
int code = conn.getResponseCode();
System.out.println("---code---" + code);
InputStream is = conn.getInputStream();// 已經(jīng)設(shè)置了請求的位置极阅,返回的是當(dāng)前位置對應(yīng)的文件的輸入流
RandomAccessFile raf = new RandomAccessFile("temp.apk", "rwd");
// 隨機寫文件的時候從哪個位置開始寫
raf.seek(startIndex);// 定位文件
int len = 0;
byte[] buffer = new byte[1024];
int total = 0;// 記錄已經(jīng)下載的數(shù)據(jù)的長度
while ((len = is.read(buffer)) != -1) {
RandomAccessFile recordFile = new RandomAccessFile(threadId+ ".txt", "rwd");// 記錄每個線程的下載進度,為斷點續(xù)傳做標(biāo)記
raf.write(buffer, 0, len);
total += len;
recordFile.write(String.valueOf(startIndex + total)
.getBytes());
recordFile.close();
}
is.close();
raf.close();
System.out.println("線程:" + threadId + "下載完畢了涨享!");
} catch (Exception e) {
e.printStackTrace();
} finally {
runningThread--;
if (runningThread == 0) {// 所有的線程已經(jīng)執(zhí)行完畢
for (int i = 1; i <= THREAD_COUNT; i++) {
File file = new File(i + ".txt");
file.delete();
}
}
}
}
}
}
具體可參考:多線程系列之多線程下載之?dāng)帱c續(xù)傳(2)
19.自定義View
可分為三類:
自定義View筋搏,——繼承 View,然后自繪視圖內(nèi)容
自定義ViewGroup厕隧,——繼承ViewGroup奔脐,然后對子類視圖進行重新布局。
自定義已有View吁讨,——繼承已有的View髓迎,比如繼承ImageView
這里介紹下自定義視圖的主要步驟:
- 自定義屬性
- 繼承View重寫構(gòu)造方法
- 獲取自定義屬性
- 重寫測量控件的寬高
- 繪制控件顯示
- 提供自定義事件
1.自定義屬性
自定義屬性一共有10中定義類型,String挡爵,boolean等竖般,具體的類型
和使用對應(yīng)如下代碼:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="text" format="string"></attr>
<!--
定義:資源ID
使用:@drawable/圖片ID
-->
<attr name="msrc" format="reference"></attr>
<!--
定義: 顏色值
使用: android:mcolor = "#00FF00"
-->
<attr name="mcolor" format="color"></attr>
<!--
定義:布爾類型
使用:android:misfocus = "true"
-->
<attr name="misfocus" format="boolean"></attr>
<!--
定義:尺寸
使用: android:msize = "42dip"
-->
<attr name="msize" format="dimension"></attr>
<!--
定義:浮點值
使用: android:malpha = "0.1"
-->
<attr name="malpha" format="float"></attr>
<!--
定義:整形
使用:android:mcount = "12"
-->
<attr name="mcount" format="integer"></attr>
<!--
定義:字符串
使用:android:apiKey = "2223"
-->
<attr name="apikey" format="string"></attr>
<!--
定義:百分?jǐn)?shù)
使用:100%
-->
<attr name="mcurrent" format="fraction"></attr>
<!--
定義:枚舉
使用:type:1
-->
<attr name="type">
<enum name="cycle" value="1"></enum>
<enum name="round" value="2"></enum>
</attr>
<declare-styleable name="customView">
<attr name="text"/>
<attr name="mcolor"/>
<attr name="msize"/>
</declare-styleable>
</resources>
2.創(chuàng)建自定義View繼承View(重寫構(gòu)造方法)
在創(chuàng)建View的時候甚垦,需要重寫構(gòu)造方法茶鹃,一般重寫前三個構(gòu)造方法就可以了,但是如果我們的自定控件是通過布局文件的形式加載艰亮,則第二個構(gòu)造必須重寫闭翩,不然會報錯。
public MyCuntomView(Context context) {
this(context, null);
}
public MyCuntomView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyCuntomView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//獲取自定義屬性
initViewAtrr(context, attrs, defStyleAttr);
}
3.獲取自定屬性的值
在獲取自定義屬性值的時候迄埃,我們通過循環(huán)的方式來獲取值疗韵,這樣獲取到屬性值,就是我們xml文件中使用到的侄非,沒有使用到的就獲取不到蕉汪。而并獲取我們所有自定義的屬性。
private void initViewAtrr(Context context, AttributeSet attrs, int defStyleAttr) {
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.customView, defStyleAttr, 0);
//獲取有幾個自定義屬相
final int count = a.getIndexCount();
Log.e("TAG", "=====" + count);
for (int i = 0; i < count; i++) {
int type = a.getIndex(i);
switch (type) {
case R.styleable.customView_text:
text = a.getString(type);
if (TextUtils.isEmpty(text)) {
text = "我是文本";
}
break;
case R.styleable.customView_mcolor:
corlor = a.getColor(type, Color.RED);
break;
case R.styleable.customView_msize:
msize = a.getDimensionPixelSize(type, 15);
break;
}
}
a.recycle();
paint = new Paint();
//抗鋸齒
paint.setAntiAlias(true);
}
4.測量控件的大谐言埂(重寫onMeasure方法)
測量之前先了解MeasureSpec的specMode,mode共有三種情況者疤,取值分別為MeasureSpec.UNSPECIFIED, MeasureSpec.EXACTLY, MeasureSpec.AT_MOST。
MeasureSpec.EXACTLY是精確尺寸叠赦,當(dāng)我們將控件的layout_width或layout_height指定為具體數(shù)值時如andorid:layout_width=”50dp”驹马,或者為FILL_PARENT是,都是控件大小已經(jīng)確定的情況除秀,都是精確尺寸糯累。
MeasureSpec.AT_MOST是最大尺寸,當(dāng)控件的layout_width或layout_height指定為WRAP_CONTENT時册踩,控件大小一般隨著控件的子空間或內(nèi)容進行變化泳姐,此時控件尺寸只要不超過父控件允許的最大尺寸即可。因此暂吉,此時的mode是AT_MOST仗岸,size給出了父控件允許的最大尺寸允耿。
MeasureSpec.UNSPECIFIED是未指定尺寸,這種情況不多扒怖,一般都是父控件是AdapterView较锡,通過measure方法傳入的模式。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int mode = MeasureSpec.getMode(widthMeasureSpec);
int size = MeasureSpec.getSize(widthMeasureSpec);
bounds = new Rect();
if (mode == MeasureSpec.EXACTLY) {
mwidth = size;
} else {
paint.setTextSize(msize);
paint.getTextBounds(text, 0, text.length(), bounds);
mwidth = getPaddingLeft() + getPaddingRight() + bounds.width();
}
mode = MeasureSpec.getMode(heightMeasureSpec);
size = MeasureSpec.getSize(heightMeasureSpec);
if (mode == MeasureSpec.EXACTLY) {
mheight = size;
} else {
paint.getTextBounds(text, 0, text.length(), bounds);
mheight = getPaddingBottom() + getPaddingTop() + bounds.height();
}
r=Math.max(mwidth,mheight);
setMeasuredDimension(r, r);
}
5.繪制控件顯示(重寫onDraw方法)
以下代碼是繪制一個圓并繪制文字:
@Override
protected void onDraw(Canvas canvas) {
paint.setColor(corlor);
canvas.drawCircle(r/2,r/2,r/2,paint);
paint.setColor(Color.BLACK);
canvas.drawText(text,r/2-bounds.width()/2,r/2+bounds.height()/2,paint);
}
6.定義事件
一些根據(jù)手勢操作的代碼可以寫在此處
@Override
public boolean onTouchEvent(MotionEvent event) {
//手勢操作相關(guān)代碼
...
...
return super.onTouchEvent(event);
}
7.編寫自定義控件盗痒,使用自定義屬性
編寫自定義控件蚂蕴,使用自定義屬性,在跟布局添加如下代碼:
xmlns:app="http://schemas.android.com/apk/res-auto"
并以以下方式引用控件:
<包名.MyCuntomView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:xxx="xxx"
app:xxx="xxx"
app:xxx="xxx" />
完成以上步驟后,Activity和Fragment就可以使用相應(yīng)的控件來實現(xiàn)相應(yīng)的交互.
具體可參考:Android 自定義View(基礎(chǔ))
20.SQLite數(shù)據(jù)庫
SQLite是一個輕量級數(shù)據(jù)庫,支持SQL語言俯邓、事務(wù)處理等功能骡楼。SQLite沒有服務(wù)器進程,它通過文件保存數(shù)據(jù)稽鞭,該文件是跨平臺的鸟整,可以放在其他平臺中使用。
保存位置:
/data/data/應(yīng)用包名/databases/xxx.db
數(shù)據(jù)庫在創(chuàng)建的時候默認(rèn)會創(chuàng)建一張表(metadata.db)來保存系統(tǒng)語言環(huán)境
使用:
1.繼承SQLiteOpenHelper類朦蕴,并實現(xiàn)其中的方法
/**
* SQLiteOpenHelper
* 1.提供了onCreate() onUpgrade()等創(chuàng)建數(shù)據(jù)庫更新數(shù)據(jù)庫的方法
* 2.提供了獲取數(shù)據(jù)庫對象的函數(shù)
*/
public class MySqliteHple extends SQLiteOpenHelper{
public MySqliteHple(Context context) {
super(context, Constant.DATABASE_NAME, null, Constant.DATABASE_VERSION);
}
/**
* 構(gòu)造函數(shù)
* @param context 上下文對象
* @param name 表示創(chuàng)建數(shù)據(jù)庫的名稱
* @param factory 游標(biāo)工廠
* @param version 表示創(chuàng)建數(shù)據(jù)庫的版本 >=1
*/
public MySqliteHple(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
}
/**
* 當(dāng)數(shù)據(jù)庫創(chuàng)建時回調(diào)的函數(shù)
* @param db 數(shù)據(jù)庫對象
*/
@Override
public void onCreate(SQLiteDatabase db) {
Log.i("tag","------onCreate-------");
String sql="create table student(_id Integer primary key,name varchar(10),age Integer not null)";
Log.i("tag","sql:"+sql);
db.execSQL(sql);//執(zhí)行sql語句
}
/**
* 當(dāng)數(shù)據(jù)庫版本更新時回調(diào)的函數(shù)
* @param db 數(shù)據(jù)庫對象
* @param oldVersion 數(shù)據(jù)庫舊版本
* @param newVersion 數(shù)據(jù)庫新版本
*/
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.i("tag","------onUpgrade-------");
}
/**
* 當(dāng)數(shù)據(jù)庫打開時回調(diào)的函數(shù)
* @param db 數(shù)據(jù)庫對象
*/
@Override
public void onOpen(SQLiteDatabase db) {
super.onOpen(db);
Log.i("tag","------onOpen-------");
}
}
2.通過SQLiteOpenHelper獲取數(shù)據(jù)庫SQLiteDatabase對象
//getReadableDatabase()和getWritableDatabase() 創(chuàng)建或打開數(shù)據(jù)庫篮条,如果數(shù)據(jù)庫不存在則創(chuàng)建數(shù)據(jù)庫,如果數(shù)據(jù)庫存在則直接打開數(shù)據(jù)庫吩抓。默認(rèn)情況下兩個函數(shù)都表示打開或者創(chuàng)建可讀可寫的數(shù)據(jù)庫對象涉茧,如果磁盤已滿或者數(shù)據(jù)庫本身權(quán)限等情況下getReadableDatabase()打開的是只讀數(shù)據(jù)庫
SQLiteDatabase db=mHple.getWritableDatabase();
3.增刪改查
- 通過sql語句
- 通過api(底層也是調(diào)用了sql語句)
經(jīng)常使用的sql語句:
創(chuàng)建表的語句
create table 表名(字段名稱 數(shù)據(jù)類型 約束,字段名稱 數(shù)據(jù)類型 約束......)
刪除表的語句
drop table 表名
插入數(shù)據(jù)
insert into 表名[字段,字段] values(值1疹娶,值2......)
修改數(shù)據(jù)
update 表名 set 字段=新值 where 修改條
刪除數(shù)據(jù)
delete from 表名 where 刪除的條件
查詢數(shù)據(jù)
select 字段名 from 表名 where 查詢條件 group by 分組的字段 having 篩選條件 order by 排序字段
使用Api進行操作:
插入數(shù)據(jù)
/**
* insert(String table, String nullColumnHack, ContentValues values)
* String table 表示插入數(shù)據(jù)表的名字
* String nullColumnHack SQL要求插入的數(shù)據(jù)不能全為null伴栓,但有些字段可以為null。一般這個參數(shù)我們直接給null
* ContentValues values 鍵為String類型的HashMap集合
* 返回值為long類型 表示插入數(shù)據(jù)的列數(shù) 如果值為-1則表示插入失敗
*/
insert(String table, String nullColumnHack, ContentValues values)
更新數(shù)據(jù)
/**
* update(String table, ContentValues values, String whereClause, String[] whereArgs)
* String table 表示修改數(shù)據(jù)表的名字
* ContentValues values 鍵為String類型的HashMap集合
* String whereClause 表示修改條件
* String[] whereArgs 表示修改條件的占位符
*/
update(String table, ContentValues values, String whereClause, String[] whereArgs)
刪除數(shù)據(jù)
/**
* delete(String table, String whereClause, String[] whereArgs)
* String table 表示刪除數(shù)據(jù)表的名字
* String whereClause 表示刪除條件
* String[] whereArgs 表示刪除條件的占位符
*/
delete(String table, String whereClause, String[] whereArgs)
查詢數(shù)據(jù)
/**
* query(String table, String[] columns, String selection,
* String[] selectionArgs, String groupBy, String having,
* String orderBy)
* String table 表示查詢的表名
* String[] columns 表示查詢的表中的字段名字 null查詢所有
* String selection 表示查詢條件 where子句
* String[] selectionArgs 表示查詢條件占位符的取值
* String groupBy 表示分組條件 group by子句
* String having 表示篩選條件 having子句
* String orderBy 表示排序條件 order by子句
*/
query(String table, String[] columns, String selection,String[] selectionArgs, String groupBy, String having,String orderBy)
SQLite事務(wù)的使用(一般是在批量操作的時候使用)
1.數(shù)據(jù)庫顯式開啟事務(wù)
db.beginTransaction();
2.提交當(dāng)前事務(wù)
db.setTransactionSuccessful();
3.關(guān)閉事務(wù)
db.endTransaction();
SQLite數(shù)據(jù)庫分頁
//主要使用以下sql語句
select * from student limit ?,?
由于篇幅有限,其它知識點在以下文章中:
Android面試題整理(二)