Android面試題整理(一)

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)鞋真。

Android系統(tǒng)架構(gòu)圖

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的生命周期

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生命周期圖

Fragment與Activity生命周期對比:

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

有效生命周期為:onStartCommandonDestroy之間

生命周期方法介紹:

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

有效生命周期為:onBindonUnbind之間

生命周期方法介紹:

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對比淺析--緩存機制

RecyclerView 必知必會

使用DiffUtil高效更新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)限。

適配方法:

  1. targetSdkVersion低于23
  2. 動態(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面試題整理(二)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末雨饺,一起剝皮案震驚了整個濱河市钳垮,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌额港,老刑警劉巖饺窿,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異锹安,居然都是意外死亡短荐,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門叹哭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來忍宋,“玉大人,你說我怎么就攤上這事风罩】放牛” “怎么了?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵超升,是天一觀的道長入宦。 經(jīng)常有香客問我哺徊,道長,這世上最難降的妖魔是什么乾闰? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任落追,我火速辦了婚禮,結(jié)果婚禮上涯肩,老公的妹妹穿的比我還像新娘轿钠。我一直安慰自己,他們只是感情好病苗,可當(dāng)我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布疗垛。 她就那樣靜靜地躺著,像睡著了一般硫朦。 火紅的嫁衣襯著肌膚如雪贷腕。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天咬展,我揣著相機與錄音泽裳,去河邊找鬼。 笑死挚赊,一個胖子當(dāng)著我的面吹牛诡壁,可吹牛的內(nèi)容都是我干的济瓢。 我是一名探鬼主播荠割,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼旺矾!你這毒婦竟也來了蔑鹦?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤箕宙,失蹤者是張志新(化名)和其女友劉穎嚎朽,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體柬帕,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡哟忍,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了陷寝。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片锅很。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖凤跑,靈堂內(nèi)的尸體忽然破棺而出爆安,到底是詐尸還是另有隱情,我是刑警寧澤仔引,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布扔仓,位于F島的核電站褐奥,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏翘簇。R本人自食惡果不足惜撬码,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望版保。 院中可真熱鬧耍群,春花似錦、人聲如沸找筝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽袖裕。三九已至曹抬,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間急鳄,已是汗流浹背谤民。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留疾宏,地道東北人张足。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像坎藐,于是被迫代替她去往敵國和親为牍。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,163評論 25 707
  • 作家是一個值得崇拜和尊敬的職業(yè)岩馍。如果沒有他們碉咆,那么我們的精神世界該從何處汲取營養(yǎng),被種種世俗困在囹圄的身心...
    叮咚_bbe7閱讀 288評論 1 1
  • 也許是冥冥中的注定幾個月前來到這座美麗的小城還未來得及好好欣賞她的美冬天就來了每天刺骨的寒風(fēng)眷顧著早起的學(xué)子校園里...
    倪雪閱讀 110評論 2 1
  • 寶貝你知道嗎? 當(dāng)生命的小種子出現(xiàn)在媽媽的身體里双谆, 媽媽高興得不知所措壳咕, 都不知道該如何走路了 , 生怕寶貝在身體...
    小池蓮閱讀 495評論 0 2
  • 詩刻枯榮際顽馋,悠悠歲月空谓厘。 江山新雨寂,天地故云曾趣避。 賦詠千秋意庞呕,詞吟萬古名。 驅(qū)馳臨勝跡,點墨寄深情住练。
    霙愔閱讀 1,195評論 2 4