第二个
1、啟動應用后,改變系統語言,應用的語言會改變么?
這個一般是不會的,一般需要重啟應用才能改變應用語言。但是對應應用來說如果做了國際化處理則支持如果沒有處理那系統語言再更改也是無用的。
2、請介紹下adb、ddms、aapt的作用
adb是Android Debug Bridge ,Android調試橋的意思,ddms是Dalvik Debug Monitor Service,dalvik調試監視服務。aapt即Android Asset Packaging Tool,在SDK的build-tools目錄下。該工具可以查看,創建, 更新ZIP格式的文檔附件(zip, jar, apk)。也可將資源文件編譯成二進制文件,盡管我們沒有直接使用過該工具,但是開發工具會使用這個工具打包apk文件構成一個Android 應用程序。
Android 的主要調試工具是adb(Android debuging bridge),ddms是一個在adb基礎上的一個圖形化工具。
adb,它是一個命令行工具。而ddms功能與adb相同,只是它有一個圖形化界面。對不喜歡命今操作方式的人來說是一個不錯的選擇。
3、ddms 和traceview的區別
ddms原意是:davik debug monitor service。簡單的說ddms是一個程序執行查看器,在里面可以看見線程和堆棧等信息,traceView是程序性能分析器。traceview是ddms中的一部分內容。
4、補充知識:TraceView的使用
一、TraceView簡介
?????Traceview是Android平臺特有的數據采集和分析工具,它主要用于分析Android中應用程序的hotspot(瓶頸)。Traceview本身只是一個數據分析工具,而數據的采集則需要使用Android SDK中的Debug類或者利用DDMS工具。二者的用法如下:
開發者在一些關鍵代碼段開始前調用Android SDK中Debug類的startMethodTracing函數,并在關鍵代碼段結束前調用stopMethodTracing函數。這兩個函數運行過程中將采集運行時間內該應用所有線程(注意,只能是Java線程)的函數執行情況,并將采集數據保存到/mnt/sdcard/下的一個文件中。開發者然后需要利用SDK中的Traceview工具來分析這些數據。
借助Android SDK中的DDMS工具。DDMS可采集系統中某個正在運行的進程的函數調用信息。對開發者而言,此方法適用于沒有目標應用源代碼的情況。DDMS工具中Traceview的使用如下圖所示。
?
點擊上圖中所示按鈕即可以采集目標進程的數據。當停止采集時,DDMS會自動觸發Traceview工具來瀏覽采集數據。
下面,我們通過一個示例程序介紹Traceview的使用。
實例程序如下圖所示:界面有4個按鈕,對應四個方法。
?
點擊不同的方法會進行不同的耗時操作。
我們分別點擊按鈕一次,要求找出最耗時的方法。點擊前通過DDMS 啟動 Start Method Profiling按鈕。
?
然后依次點擊4個按鈕,都執行后再次點擊上圖中紅框中按鈕,停止收集數據。
接下來我們開始對數據進行分析。
當我們停止收集數據的時候會出現如下分析圖表。該圖表分為2大部分,上面分不同的行,每一行代表一個線程的執行耗時情況。main線程對應行的的內容非常豐富,而其他線程在這段時間內干得工作則要少得多。圖表的下半部分是具體的每個方法執行的時間情況。顯示方法執行情況的前提是先選中某個線程。
?
?
我們主要是分析main線程。
上面方法指標參數所代表的意思如下:
| 列名 | 描述 |
| Name | 該線程運行過程中所調用的函數名 |
| Incl Cpu Time | 某函數占用的CPU時間,包含內部調用其它函數的CPU時間 |
| Excl Cpu Time | 某函數占用的CPU時間,但不含內部調用其它函數所占用的CPU時間 |
| Incl Real Time | 某函數運行的真實時間(以毫秒為單位),內含調用其它函數所占用的真實時間 |
| Excl Real Time | 某函數運行的真實時間(以毫秒為單位),不含調用其它函數所占用的真實時間 |
| Call+Recur Calls/Total | 某函數被調用次數以及遞歸調用占總調用次數的百分比 |
| Cpu Time/Call | 某函數調用CPU時間與調用次數的比。相當于該函數平均執行時間 |
| Real Time/Call | 同CPU Time/Call類似,只不過統計單位換成了真實時間 |
我們為了找到最耗時的操作,那么可以通過點擊Incl Cpu Time,讓其按照時間的倒序排列。我點擊后效果如下圖:
?
通過分析發現:method1最耗時,耗時2338毫秒。
?
那么有了上面的信息我們可以進入我們的method1方法查看分析我們的代碼了。
5、Android中數據存儲方式有哪些?
a)?文件存儲
b)?xml,SharedPreference
c)?SQLiteDatabase
d)?ContentProvider
e)?網絡
15、DVM和JVM的區別?
a)?dvm執行的是.dex文件,而jvm執行的是.class。Android工程編譯后的所有.class字節碼會被dex工具抽取到一個.dex文件中。
b)?dvm是基于寄存器的虛擬機? 而jvm執行是基于虛擬棧的虛擬機。寄存器存取速度比棧快的多,dvm可以根據硬件實現最大的優化,比較適合移動設備。
c)?.class文件存在很多的冗余信息,dex工具會去除冗余信息,并把所有的.class文件整合到.dex文件中。減少了I/O操作,提高了類的查找速度。
16、談一談Android的安全機制
1、Android是基于Linux內核的,因此Linux對文件權限的控制同樣適用于Android
在Android中每個應用都有自己的/data/data/包名 文件夾,該文件夾只能該應用訪問,而其他應用則無權訪問。
2、Android的權限機制保護了用戶的合法權益
如果我們的代碼想撥打電話、發送短信、訪問通信錄、定位、訪問sdcard等所有可能侵犯用于權益的行為都是必須要在AndroidManifest.xml中進行聲明的,這樣就給了用戶一個知情權。
3、Android的代碼混淆保護了開發者的勞動成果
17、Android的四大組件都需要在清單文件中注冊嗎?
Activity、Service、ContentProvider如果要使用則必須在AndroidManifest.xml中進行注冊,而BroadcastReceiver則有兩種注冊方式,靜態注冊和動態注冊。其中靜態注冊就是指在AndroidManifest.xml中進行注冊,而動態注冊時通過代碼注冊。
18、在Android中進程的級別有哪些?
a)?Foreground process
b)?Visible process
c)?Service process
d)?Background process
e)?Empty process
一、Activity
1、什么是Activity??
四大組件之一,通常一個用戶交互界面對應一個activity。activity 是Context的子類,同時實現了window.callback和keyevent.callback, 可以處理與窗體用戶交互的事件。
常見的Activity類型有FragmentActivitiy,ListActivity,TabAcitivty等。
如果界面有共同的特點或者功能的時候,還會自己定義一個BaseActivity。
2、請描述一下Activity 生命周期
Activity從創建到銷毀有多種狀態,從一種狀態到另一種狀態時會激發相應的回調方法,這些回調方法包括:onCreate onStart onResume onPause onStop onDestroy
其實這些方法都是兩兩對應的,onCreate創建與onDestroy銷毀;
onStart可見與onStop不可見;onResume可編輯(即焦點)與onPause;
這6個方法是相對應的,那么就只剩下一個onRestart方法了,這個方法在什么時候調用呢?
答案就是:在Activity被onStop后,但是沒有被onDestroy,在再次啟動此Activity時就調用onRestart(而不再調用onCreate)方法;
如果被onDestroy了,則是調用onCreate方法。
3、Activity的狀態都有哪些?
a)?foreground activity
b)?visible activity
c)?background activity
d)?empty process
4、如何保存Activity的狀態?
Activity的狀態通常情況下系統會自動保存的,只有當我們需要保存額外的數據時才需要使用到這樣的功能。
一般來說, 調用onPause()和onStop()方法后的activity實例仍然存在于內存中, activity的所有信息和狀態數據不會消失, 當activity重新回到前臺之后, 所有的改變都會得到保留。
但是當系統內存不足時, 調用onPause()和onStop()方法后的activity可能會被系統摧毀, 此時內存中就不會存有該activity的實例對象了。如果之后這個activity重新回到前臺, 之前所作的改變就會消失。為了避免此種情況的發生, 我們可以覆寫onSaveInstanceState()方法。onSaveInstanceState()方法接受一個Bundle類型的參數, 開發者可以將狀態數據存儲到這個Bundle對象中, 這樣即使activity被系統摧毀, 當用戶重新啟動這個activity而調用它的onCreate()方法時, 上述的Bundle對象會作為實參傳遞給onCreate()方法, 開發者可以從Bundle對象中取出保存的數據, 然后利用這些數據將activity恢復到被摧毀之前的狀態。
需要注意的是, onSaveInstanceState()方法并不是一定會被調用的, 因為有些場景是不需要保存狀態數據的. 比如用戶按下BACK鍵退出activity時, 用戶顯然想要關閉這個activity, 此時是沒有必要保存數據以供下次恢復的, 也就是onSaveInstanceState()方法不會被調用. 如果調用onSaveInstanceState()方法, 調用將發生在onPause()或onStop()方法之前。
5、兩個Activity之間跳轉時必然會執行的是哪幾個方法?(重要)
一般情況下比如說有兩個activity,分別叫A,B,當在A里面激活B組件的時候, A會調用 onPause()方法,然后B調用onCreate() ,onStart(), onResume()。
?這個時候B覆蓋了窗體, A會調用onStop()方法. ?如果B是個透明的,或者是對話框的樣式, 就不會調用A的onStop()方法。
如下圖,打開一個MainActivity,然后點擊MainActivity中的按鈕跳轉到SecondActivity的日志。
?
6、橫豎屏切換時Activity的生命周期
此時的生命周期跟清單文件里的配置有關系。
1、不設置Activity的android:configChanges時,切屏會重新調用各個生命周期
默認首先銷毀當前activity,然后重新加載。
如下圖,當橫豎屏切換時先執行onPause/onStop方法
?
2、設置Activity的android:configChanges="orientation|keyboardHidden|screenSize"時,切屏不會重新調用各個生命周期,只會執行onConfigurationChanged方法。
通常在游戲開發, 屏幕的朝向都是寫死的。
7、如何將一個Activity設置成窗口的樣式?
只需要給我們的Activity配置如下屬性即可。
?android:theme="@android:style/Theme.Dialog"
8、如何退出Activity?如何安全退出已調用多個Activity的Application?
1、通常情況用戶退出一個Activity只需按返回鍵,我們寫代碼想退出activity直接調用finish()方法就行。
2、記錄打開的Activity:
每打開一個Activity,就記錄下來。在需要退出時,關閉每一個Activity即可。
3、發送特定廣播:
在需要結束應用時,發送一個特定的廣播,每個Activity收到廣播后,關閉即可。
//給某個activity 注冊接受接受廣播的意圖
registerReceiver(receiver, filter)
//如果過接受到的是 關閉activity的廣播 ?就調用finish()方法 把當前的activity finish()掉
4、遞歸退出
在打開新的Activity時使用startActivityForResult,然后自己加標志,在onActivityResult中處理,遞歸關閉。
5、其實 也可以通過 intent的flag 來實現intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)激活一個新的activity。此時如果該任務棧中已經有該Activity,那么系統會把這個Activity上面的所有Activity干掉。其實相當于給Activity配置的啟動模式為SingleTop。
9、請描述一下Activity的啟動模式都有哪些以及各自的特點
啟動模式(launchMode)在多個Activity跳轉的過程中扮演著重要的角色,它可以決定是否生成新的Activity實例,是否重用已存在的Activity實例,是否和其他Activity實例公用一個task里。這里簡單介紹一下task的概念,task是一個具有棧結構的對象,一個task可以管理多個Activity,啟動一個應用,也就創建一個與之對應的task。
Activity一共有以下四種launchMode:
?1.standard
?2.singleTop
?3.singleTask
?4.singleInstance
我們可以在AndroidManifest.xml配置<activity>的android:launchMode屬性為以上四種之一即可。
下面我們結合實例一一介紹這四種lanchMode:
8.1 standard
standard模式是默認的啟動模式,不用為<activity>配置android:launchMode屬性即可,當然也可以指定值為standard。
我們將創建一個Activity,命名為FirstActivity,來演示一下標準的啟動模式。FirstActivity代碼如下:
FirstActivity界面中的TextView用于顯示當前Activity實例的序列號,Button用于跳轉到下一個FirstActivity界面。
然后我們連續點擊幾次按鈕,將會出現下面的現象:
我們注意到都是FirstActivity的實例,但序列號不同,并且我們需要連續按后退鍵兩次,才能回到第一個FirstActivity。standard模式的原理如下圖所示:
如圖所示,每次跳轉系統都會在task中生成一個新的FirstActivity實例,并且放于棧結構的頂部,當我們按下后退鍵時,才能看到原來的FirstActivity實例。
這就是standard啟動模式,不管有沒有已存在的實例,都生成新的實例。
8.2 singleTop
我們在上面的基礎上為<activity>指定屬性android:launchMode="singleTop",系統就會按照singleTop啟動模式處理跳轉行為。我們重復上面幾個動作,將會出現下面的現象:
我們看到這個結果跟standard有所不同,三個序列號是相同的,也就是說使用的都是同一個FirstActivity實例;如果按一下后退鍵,程序立即退出,說明當前棧結構中只有一個Activity實例。singleTop模式的原理如下圖所示:
正如上圖所示,跳轉時系統會先在棧結構中尋找是否有一個FirstActivity實例正位于棧頂,如果有則不再生成新的,而是直接使用。也許朋友們會有疑問,我只看到棧內只有一個Activity,如果是多個Activity怎么辦,如果不是在棧頂會如何?我們接下來再通過一個示例來證實一下大家的疑問。
我們再新建一個Activity命名為SecondActivity,如下:
然后將之前的FirstActivity跳轉代碼改為:
這時候,FirstActivity會跳轉到SecondActivity,SecondActivity又會跳轉到FirstActivity。演示結果如下:
我們看到,兩個FirstActivity的序列號是不同的,證明從SecondActivity跳轉到FirstActivity時生成了新的FirstActivity實例。原理圖如下:
我們看到,當從SecondActivity跳轉到FirstActivity時,系統發現存在有FirstActivity實例,但不是位于棧頂,于是重新生成一個實例。
這就是singleTop啟動模式,如果發現有對應的Activity實例正位于棧頂,則重復利用,不再生成新的實例。
8.3 singleTask
在上面的基礎上我們修改FirstActivity的屬性android:launchMode="singleTask"。演示的結果如下:
我們注意到,在上面的過程中,FirstActivity的序列號是不變的,SecondActivity的序列號卻不是唯一的,說明從SecondActivity跳轉到FirstActivity時,沒有生成新的實例,但是從FirstActivity跳轉到SecondActivity時生成了新的實例。singleTask模式的原理圖如下圖所示:
在圖中的下半部分是SecondActivity跳轉到FirstActivity后的棧結構變化的結果,我們注意到,SecondActivity消失了,沒錯,在這個跳轉過程中系統發現有存在的FirstActivity實例,于是不再生成新的實例,而是將FirstActivity之上的Activity實例統統出棧,將FirstActivity變為棧頂對象,顯示到幕前。也許朋友們有疑問,如果將SecondActivity也設置為singleTask模式,那么SecondActivity實例是不是可以唯一呢?在我們這個示例中是不可能的,因為每次從SecondActivity跳轉到FirstActivity時,SecondActivity實例都被迫出棧,下次等FirstActivity跳轉到SecondActivity時,找不到存在的SecondActivity實例,于是必須生成新的實例。但是如果我們有ThirdActivity,讓SecondActivity和ThirdActivity互相跳轉,那么SecondActivity實例就可以保證唯一。
這就是singleTask模式,如果發現有對應的Activity實例,則使此Activity實例之上的其他Activity實例統統出棧,使此Activity實例成為棧頂對象,顯示到幕前。
8.4 singleInstance
這種啟動模式比較特殊,因為它會啟用一個新的棧結構,將Activity放置于這個新的棧結構中,并保證不再有其他Activity實例進入。
我們修改FirstActivity的launchMode="standard",SecondActivity的launchMode="singleInstance",由于涉及到了多個棧結構,我們需要在每個Activity中顯示當前棧結構的id,所以我們為每個Activity添加如下代碼:
然后我們再演示一下這個流程:
我們發現這兩個Activity實例分別被放置在不同的棧結構中,關于singleInstance的原理圖如下
我們看到從FirstActivity跳轉到SecondActivity時,重新啟用了一個新的棧結構,來放置SecondActivity實例,然后按下后退鍵,再次回到原始棧結構;圖中下半部分顯示的在SecondActivity中再次跳轉到FirstActivity,這個時候系統會在原始棧結構中生成一個FirstActivity實例,然后回退兩次,注意,并沒有退出,而是回到了SecondActivity,為什么呢?是因為從SecondActivity跳轉到FirstActivity的時候,我們的起點變成了SecondActivity實例所在的棧結構,這樣一來,我們需要“回歸”到這個棧結構。
如果我們修改FirstActivity的launchMode值為singleTop、singleTask、singleInstance中的任意一個,流程將會如圖所示:
singleInstance啟動模式可能是最復雜的一種模式,為了幫助大家理解,我舉一個例子,假如我們有一個share應用,其中的ShareActivity是入口Activity,也是可供其他應用調用的Activity,我們把這個Activity的啟動模式設置為singleInstance,然后在其他應用中調用。我們編輯ShareActivity的配置:
然后我們在其他應用中這樣啟動該Activity:
當我們打開ShareActivity后再按后退鍵回到原來界面時,ShareActivity做為一個獨立的個體存在,如果這時我們打開share應用,無需創建新的ShareActivity實例即可看到結果,因為系統會自動查找,存在則直接利用。大家可以在ShareActivity中打印一下taskId,看看效果。關于這個過程,原理圖如下:
10、一個啟動模式為singleTop的activity,如果再試圖啟動會怎樣? ?面試官想問的是onNewIntent()(2015-11-24)
Activity有一個onNewIntent(Intent intent)回調方法,該方法我們幾乎很少使用,導致已經將其忽略掉。該方法的官方解釋如下:
This is called for activities that set launchMode to "singleTop" in their package, or if a client used the Intent.FLAG_ACTIVITY_SINGLE_TOP flag when calling startActivity. In either case, when the activity is re-launched while at the top of the activity stack instead of a new instance of the activity being started, onNewIntent() will be called on the existing instance with the Intent that was used to re-launch it.
An activity will always be paused before receiving a new intent, so you can count on onResume being called after this method.
Note that getIntent still returns the original Intent. You can use setIntent to update it to this new Intent.
上文大概意思如下:
該方法被啟動模式設置為“singleTop”的Activity回調,或者當通過設置Intent.FLAG_ACTIVITY_SINGLE_TOP 的Intent啟動Activity時被回調。在任何情況下,只要當棧頂的Activity被重新啟動時沒有重新創建一個新的Activity實例而是依然使用該Activity對象,那么onNewIntent(Intent)方法就會被回調。
當一個Activity接收到新Intent的時候會處于暫停狀態,因此你可以統計到onResume()方法會被再次執行,當然這個執行是在onNewIntent之后的。
注意:如果我們在Activity中調用了getIntent()方法,那么返回的Intent對象還是老的Intent(也就是第一次啟動該Activity時的傳入的Intent對象),但是如果想讓getIntent()返回最新的Intent,那么我們可以通過setIntent(Intent)方法設置。
二、Service
1、Service是否在main thread中執行, service里面是否能執行耗時的操作?
默認情況,如果沒有顯示的指service所運行的進程, Service和activity是運行在當前app所在進程的main thread(UI主線程)里面。
service里面不能執行耗時的操作(網絡請求,拷貝數據庫,大文件 )
特殊情況 ,可以在清單文件配置 service 執行所在的進程 ,讓service在另外的進程中執行
2、Activity怎么和Service綁定,怎么在Activity中啟動自己對應的Service?
Activity通過bindService(Intent service, ServiceConnection conn, int flags)跟Service進行綁定,當綁定成功的時候Service會將代理對象通過回調的形式傳給conn,這樣我們就拿到了Service提供的服務代理對象。
在Activity中可以通過startService和bindService方法啟動Service。一般情況下如果想獲取Service的服務對象那么肯定需要通過bindService()方法,比如音樂播放器,第三方支付等。如果僅僅只是為了開啟一個后臺任務那么可以使用startService()方法。
3、請描述一下Service的生命周期
Service有綁定模式和非綁定模式,以及這兩種模式的混合使用方式。不同的使用方法生命周期方法也不同。
非綁定模式:當第一次調用startService的時候執行的方法依次為onCreate()、onStartCommand(),(onStart())當Service關閉的時候調用onDestory方法。
綁定模式:第一次bindService()的時候,執行的方法為onCreate()、onBind()解除綁定的時候會執行onUnbind()、onDestory()。
上面的兩種生命周期是在相對單純的模式下的情形。我們在開發的過程中還必須注意Service實例只會有一個,也就是說如果當前要啟動的Service已經存在了那么就不會再次創建該Service當然也不會調用onCreate()方法。
一個Service可以被多個客戶進行綁定,只有所有的綁定對象都執行了onBind()方法后該Service才會銷毀,不過如果有一個客戶執行了onStart()方法,那么這個時候如果所有的bind客戶都執行了unBind()該Service也不會銷毀。
Service的生命周期圖如下所示,幫助大家記憶。
?
4、什么是IntentService?有何優點?
我們通常只會使用Service,可能IntentService對大部分同學來說都是第一次聽說。那么看了下面的介紹相信你就不再陌生了。如果你還是不了解那么在面試的時候你就坦誠說沒用過或者不了解等。并不是所有的問題都需要回答上來的。
一、IntentService簡介
IntentService是Service的子類,比普通的Service增加了額外的功能。先看Service本身存在兩個問題:
Service不會專門啟動一條單獨的進程,Service與它所在應用位于同一個進程中;
Service也不是專門一條新線程,因此不應該在Service中直接處理耗時的任務;
二、IntentService特征
會創建獨立的worker線程來處理所有的Intent請求;
會創建獨立的worker線程來處理onHandleIntent()方法實現的代碼,無需處理多線程問題;
所有請求處理完成后,IntentService會自動停止,無需調用stopSelf()方法停止Service;
為Service的onBind()提供默認實現,返回null;
為Service的onStartCommand提供默認實現,將請求Intent添加到隊列中;
三、使用IntentService
本人寫了一個IntentService的使用例子供參考。該例子中一個MainActivity一個MyIntentService,這兩個類都是四大組件當然需要在清單文件中注冊。這里只給出核心代碼:
MainActivity.java:
?
MyIntentService.java
運行后效果如下:
?
5、說說Activity、Intent、Service是什么關系
他們都是Android開發中使用頻率最高的類。其中Activity和Service都是Android四大組件之一。他倆都是Context類的子類ContextWrapper的子類,因此他倆可以算是兄弟關系吧。不過兄弟倆各有各自的本領,Activity負責用戶界面的顯示和交互,Service負責后臺任務的處理。Activity和Service之間可以通過Intent傳遞數據,因此可以把Intent看作是通信使者。
6、Service和Activity在同一個線程嗎
對于同一app來說默認情況下是在同一個線程中的,main Thread (UI Thread)。
7、Service里面可以彈吐司么?
可以的。彈吐司有個條件就是得有一個Context上下文,而Service本身就是Context的子類,因此在Service里面彈吐司是完全可以的。比如我們在Service中完成下載任務后可以彈一個吐司通知用戶。
8、如何讓一個Service成為前置進程?
在啟動該Service的時候可以在添加上如下方法:
9、Service的onStartCommand方法有幾種返回值?各代表什么意思?
有四種返回值,不同值代表的意思如下:
START_STICKY:如果service進程被kill掉,保留service的狀態為開始狀態,但不保留遞送的intent對象。隨后系統會嘗試重新創建service,由于服務狀態為開始狀態,所以創建服務后一定會調用onStartCommand(Intent,int,int)方法。如果在此期間沒有任何啟動命令被傳遞到service,那么參數Intent將為null。
START_NOT_STICKY:“非粘性的”。使用這個返回值時,如果在執行完onStartCommand后,服務被異常kill掉,系統不會自動重啟該服務。
START_REDELIVER_INTENT:重傳Intent。使用這個返回值時,如果在執行完onStartCommand后,服務被異常kill掉,系統會自動重啟該服務,并將Intent的值傳入。
START_STICKY_COMPATIBILITY:START_STICKY的兼容版本,但不保證服務被kill后一定能重啟。
10、Service的onRebind(Intent)方法在什么情況下會執行?
如果在onUnbind()方法返回true的情況下會執行,否則不執行。
官方解釋如下:
Called when new clients have connected to the service, after it had previously been notified that all had disconnected in its onUnbind. This will only be called if the implementation of onUnbind was overridden to return true.
11、Activity調用Service中的方法都有哪些方式?
Activity調用Service中的方法主要是通過綁定服務的模式實現的,綁定服務又分為三種方式。如下所示:
一、Extending the Binder class
通過Binder接口的形式實現,當Activity綁定Service成功的時候Activity會在ServiceConnection的類的onServiceConnected()回調方法中獲取到Service的onBind()方法return 過來的Binder的子類。
二、Using a Messenger
這是官方給出的另外一種溝通方式。原文如下:
If you need your interface to work across different processes, you can create an interface for the service with a Messenger. In this manner, the service defines a Handler that responds to different types of Message objects. This Handler is the basis for a Messenger that can then share an IBinder with the client, allowing the client to send commands to the service using Message objects. Additionally, the client can define a Messenger of its own so the service can send messages back.
?
This is the simplest way to perform interprocess communication (IPC), because the Messenger queues all requests into a single thread so that you don't have to design your service to be thread-safe.
?
Here's a summary of how to use a Messenger:
?
l?????The service implements a Handler that receives a callback for each call from a client.
l?????The Handler is used to create a Messenger object (which is a reference to the Handler).
l?????The Messenger creates an IBinder that the service returns to clients from onBind().
l?????Clients use the IBinder to instantiate the Messenger (that references the service's Handler), which the client uses to send Message objects to the service.
l?????The service receives each Message in its Handler—specifically, in the handleMessage() method.
?
In this way, there are no "methods" for the client to call on the service. Instead, the client delivers "messages" (Message objects) that the service receives in its Handler.
Here's a simple example service that uses a Messenger interface:
1.?public class MessengerService extends Service {
2.?????/** Command to the service to display a message */
3.?????static final int MSG_SAY_HELLO = 1;
4.?
5.?????/**
6.??????* Handler of incoming messages from clients.
7.??????*/
8.?????class IncomingHandler extends Handler {
9.?????????@Override
10.?????????public void handleMessage(Message msg) {
11.?????????????switch (msg.what) {
12.?????????????????case MSG_SAY_HELLO:
13.?????????????????????Toast.makeText(getApplicationContext(),
14.?"hello!", Toast.LENGTH_SHORT).show();
15.?????????????????????break;
16.?????????????????default:
17.?????????????????????super.handleMessage(msg);
18.?????????????}
19.?????????}
20.?????}
21.?
22.?????/**
23.??????* Target we publish for clients to send messages to IncomingHandler.
24.??????*/
25.?????final Messenger mMessenger = new Messenger(new IncomingHandler());
26.?
27.?????/**
28.??????* When binding to the service, we return an interface to our messenger
29.??????* for sending messages to the service.
30.??????*/
31.?????@Override
32.?????public IBinder onBind(Intent intent) {
33.?????????Toast.makeText(getApplicationContext(),
34.?"binding", Toast.LENGTH_SHORT).show();
35.?????????return mMessenger.getBinder();
36.?????}
37.?}
Notice that the handleMessage() method in the Handler is where the service receives the incoming Message and decides what to do, based on the what member.
?
All that a client needs to do is create a Messenger based on the IBinder returned by the service and send a message using send(). For example, here's a simple activity that binds to the service and delivers the MSG_SAY_HELLO message to the service:
1.?public class ActivityMessenger extends Activity {
2.?????/** Messenger for communicating with the service. */
3.?????Messenger mService = null;
4.?
5.?????/** Flag indicating whether we have called bind on the service. */
6.?????boolean mBound;
7.?
8.?????/**
9.??????* Class for interacting with the main interface of the service.
10.??????*/
11.?????private ServiceConnection mConnection = new ServiceConnection() {
12.?????????public void onServiceConnected(ComponentName className, IBinder service) {
13.?????????????// This is called when the connection with the service has been
14.?????????????// established, giving us the object we can use to
15.?????????????// interact with the service. ?We are communicating with the
16.?????????????// service using a Messenger, so here we get a client-side
17.?????????????// representation of that from the raw IBinder object.
18.?????????????mService = new Messenger(service);
19.?????????????mBound = true;
20.?????????}
21.?
22.?????????public void onServiceDisconnected(ComponentName className) {
23.?????????????// This is called when the connection with the service has been
24.?????????????// unexpectedly disconnected -- that is, its process crashed.
25.?????????????mService = null;
26.?????????????mBound = false;
27.?????????}
28.?????};
29.?
30.?????public void sayHello(View v) {
31.?????????if (!mBound) return;
32.?????????// Create and send a message to the service, using a supported 'what' value
33.?????????Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0);
34.?????????try {
35.?????????????mService.send(msg);
36.?????????} catch (RemoteException e) {
37.?????????????e.printStackTrace();
38.?????????}
39.?????}
40.?
41.?????@Override
42.?????protected void onCreate(Bundle savedInstanceState) {
43.?????????super.onCreate(savedInstanceState);
44.?????????setContentView(R.layout.main);
45.?????}
46.?
47.?????@Override
48.?????protected void onStart() {
49.?????????super.onStart();
50.?????????// Bind to the service
51.?????????bindService(new Intent(this, MessengerService.class), mConnection,
52.?????????????Context.BIND_AUTO_CREATE);
53.?????}
54.?
55.?????@Override
56.?????protected void onStop() {
57.?????????super.onStop();
58.?????????// Unbind from the service
59.?????????if (mBound) {
60.?????????????unbindService(mConnection);
61.?????????????mBound = false;
62.?????????}
63.?????}
64.?}
Notice that this example does not show how the service can respond to the client. If you want the service to respond, then you need to also create a Messenger in the client. Then when the client receives the onServiceConnected() callback, it sends a Message to the service that includes the client's Messenger in the replyTo parameter of the send() method.
三、Using AIDL
aidl比較適合當客戶端和服務端不在同一個應用下的場景。
?
12、Activity如何給Service發送Message?(2015.10.18)
該題和下一題,都是關于Activity和Service直接護發消息的知識,非常的有意思,但是也非常的不好回答。Activity給Service還算稍微簡單一點,但是Service給Activity發送消息就有點難度了,不是常人能所了解的。如果你沒有看過Android官方關于Bound Service的解釋,估計很難應付。
Activity如何給Service發送Message見《Activity調用Service中的方法都有哪些方式?》題目中Activity跟Service綁定的第二種方式:Using a Messenger。
13、Service如何給Activity發送Message?(2015.10.18)
Service和Activity如果想互發Message就必須使用使用Messenger機制。
Service如何給Activity發送Message見《Activity調用Service中的方法都有哪些方式?》題目中Activity跟Service綁定的第二種方式:Using a Messenger。
考慮到官方只給出了Activity給Service發送Message的代碼,在這里我給出一個Activity跟Service之間互相發送Message通信的示例代碼:
1.?MainActivity代碼
1.?package com.example.serviceAndActivity;
2.?
3.?import android.os.Bundle;
4.?import android.os.Handler;
5.?import android.os.IBinder;
6.?import android.os.Message;
7.?import android.os.Messenger;
8.?import android.os.RemoteException;
9.?import android.app.Activity;
10.?import android.app.Service;
11.?import android.content.ComponentName;
12.?import android.content.Intent;
13.?import android.content.ServiceConnection;
14.?import android.util.Log;
15.?import android.view.View;
16.?import android.widget.Toast;
17.?/**
18.??* Activity和 Service互發Message
19.??*
20.??* @author wzy 2015-11-25
21.??*
22.??*/
23.?public class MainActivity extends Activity {
24.?private Messenger messenger;
25.?//將該Handler發送Service
26.?private Messenger mOutMessenger = new Messenger(new OutgoingHandler());
27.?
28.?@Override
29.?protected void onCreate(Bundle savedInstanceState) {
30.?super.onCreate(savedInstanceState);
31.?setContentView(R.layout.activity_main);
32.?}
33.?//綁定服務
34.?public void click1(View view) {
35.?Intent intent = new Intent(this, MessengerService.class);
36.?ServiceConnection conn = new MyServiceConnection();
37.?bindService(intent, conn, Service.BIND_AUTO_CREATE);
38.?}
39.?
40.?//發送消息
41.?public void click2(View view) throws RemoteException {
42.?if (messenger == null) {
43.?Toast.makeText(this, "服務不可用!", Toast.LENGTH_SHORT).show();
44.?return;
45.?}
46.?Message message = new Message();
47.?message.obj="長江長江我是黃河";
48.?message.what =0;
49.?messenger.send(message);
50.?
51.?}
52.?
53.?class OutgoingHandler extends Handler{
54.?@Override
55.?public void handleMessage(Message msg) {
56.?Log.d("tag", msg.toString());
57.?}
58.?}
59.?
60.?class MyServiceConnection implements ServiceConnection {
61.?
62.?@Override
63.?public void onServiceConnected(ComponentName name, IBinder service) {
64.?Toast.makeText(MainActivity.this, "連接成功!", Toast.LENGTH_SHORT).show();
65.?messenger = new Messenger(service);
66.?Message message=new Message();
67.?message.what = 1;
68.?//Activity綁定Service的時候給Service發送一個消息,該消息的obj屬性是一個Messenger對象
69.?message.obj = mOutMessenger;
70.?try {
71.?messenger.send(message);
72.?} catch (RemoteException e) {
73.?e.printStackTrace();
74.?}
75.?}
76.?
77.?@Override
78.?public void onServiceDisconnected(ComponentName name) {
79.?Toast.makeText(MainActivity.this, "連接已經斷開!", Toast.LENGTH_SHORT).show();
80.?}
81.?
82.?}
83.?
84.?}
2.?MessengerService代碼
1. package com.example.serviceAndActivity;
?
import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.util.Log;
/**
?* 該Service接收到Activity的消息后會在返回一條消息
?
?* @author wzy 2015-11-25
?*
?*/
public class MessengerService extends Service {
private Messenger ?messenger = new Messenger(new IncomingHandler());
private Messenger mActivityMessenger ;
?
@Override
public IBinder onBind(Intent intent) {
IBinder binder = messenger.getBinder();
return binder;
}
//1.定義一個Handler對象,該Handler處理Activity發送過來的消息
class IncomingHandler extends Handler{
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 0:
Log.d("tag", msg.toString());
if (mActivityMessenger!=null) {
Message message= new Message();
message.what = 2;
message.obj="地瓜地瓜我是土豆";
try {
mActivityMessenger.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}
break;
case 1:
mActivityMessenger = ?(Messenger) msg.obj;
Log.d("tag", "已經獲取到Activity發送了的Messenger對象");
break;
default:
break;
}
}
}
?
}
?
運行界面以及日志如下圖所示:
?
?
?
三、BroadCastReceiver
1、請描述一下BroadcastReceiver
BroadCastReceiver是Android四大組件之一,主要用于接收系統或者app發送的廣播事件。
廣播分兩種:有序廣播和無序廣播。
內部通信實現機制:通過Android系統的Binder機制實現通信。
無序廣播:完全異步,邏輯上可以被任何廣播接收者接收到。優點是效率較高。缺點是一個接收者不能將處理結果傳遞給下一個接收者,并無法終止廣播intent的傳播。
有序廣播:按照被接收者的優先級順序,在被接收者中依次傳播。比如有三個廣播接收者A,B,C,優先級是A > B > C。那這個消息先傳給A,再傳給B,最后傳給C。每個接收者有權終止廣播,比如B終止廣播,C就無法接收到。此外A接收到廣播后可以對結果對象進行操作,當廣播傳給B時,B可以從結果對象中取得A存入的數據。
在通過Context.sendOrderedBroadcast(intent, receiverPermission, resultReceiver, scheduler, initialCode, initialData, initialExtras)時我們可以指定resultReceiver廣播接收者,這個接收者我們可以認為是最終接收者,通常情況下如果比他優先級更高的接收者如果沒有終止廣播,那么他的onReceive會被執行兩次,第一次是正常的按照優先級順序執行,第二次是作為最終接收者接收。如果比他優先級高的接收者終止了廣播,那么他依然能接收到廣播。
在我們的項目中經常使用廣播接收者接收系統通知,比如開機啟動、sd掛載、低電量、外播電話、鎖屏等。
如果我們做的是播放器,那么監聽到用戶鎖屏后我們應該將我們的播放之暫停等。
2、在manifest和代碼中如何注冊和使用BroadcastReceiver
在清單文件中注冊廣播接收者稱為靜態注冊,在代碼中注冊稱為動態注冊。靜態注冊的廣播接收者只要app在系統中運行則一直可以接收到廣播消息,動態注冊的廣播接收者當注冊的Activity或者Service銷毀了那么就接收不到廣播了。
靜態注冊:在清單文件中進行如下配置
動態注冊:在代碼中進行如下注冊
3、BroadCastReceiver的生命周期
a. 廣播接收者的生命周期非常短暫的,在接收到廣播的時候創建,onReceive()方法結束之后銷毀;
b. 廣播接收者中不要做一些耗時的工作,否則會彈出Application No Response錯誤對話框;
c. 最好也不要在廣播接收者中創建子線程做耗時的工作,因為廣播接收者被銷毀后進程就成為了空進程,很容易被系統殺掉;
d. 耗時的較長的工作最好放在服務中完成;
4、如何讓自己的廣播只讓指定的app接收(2015.09.02)
1、自己的應用(假設名稱為應用A)在發送廣播的時候給自己發送的廣播添加自定義權限,假設權限名為:com.itheima.android.permission,然后需要在應用A的AndroidManifest.xml中聲明如下權限:
??<permission?android:name="com.itheima.android.permission"android:protectionLevel="normal"></permission>
????<uses-permission?android:name="com.itheima.android.permission"/>
?
2、其他應用(假設名稱誒應用B)如果想接收該廣播,那么就必須知道應用A廣播使用的權限。然后在應用B的清單文件中如下配置:
????<uses-permission?android:name="com.itheima.android.permission"/>
或者在應用AndroidManifest.xml中的<receiver>標簽中進行如下配置:
<receiver?android:name="com.itheima.android.broadcastReceiver.MyReceiver"?android:permission="com.itheima.android.permission">
????????????<intent-filter?>
????????????????<action?android:name="com.itheima.mybroadcast"></action>
????????????</intent-filter>
????????</receiver>
?
每個權限通過 protectionLevel 來標識保護級別:
normal:低風險權限,只要申請了就可以使用(在AndroidManifest.xml中添加<uses-permission>標簽),安裝時不需要用戶確認;
dangerous:高風險權限,安裝時需要用戶的確認才可使用;
signature:只有當申請權限的應用程序的數字簽名與聲明此權限的應用程序的數字簽名相同時(如果是申請系統權限,則需要與系統簽名相同),才能將權限授給它;
signatureOrSystem:簽名相同,或者申請權限的應用為系統應用(在system image中)。
上述四類權限級別同樣可用于自定義權限中。如果開發者需要對自己的應用程序(或部分應用)進行訪問控制,則可以通過在AndroidManifest.xml中添加<permission>標簽,將其屬性中的protectionLevel設置為上述四類級別中的某一種來實現。
5、什么是最終廣播接收者?
最終廣播是我們自己應用發送有序廣播時通過ContextWrapper.sendOrderedBroadcast()方法指定的當前應用下的廣播,該廣播可能會被執行兩次,第一次是作為普通廣播按照優先級接收廣播,第二次是作為final receiver必須接收一次。
6、廣播的優先級對無序廣播生效嗎?
生效的。
7、動態注冊的廣播優先級誰高?
誰先注冊誰優先級高。
8、如何判斷當前BroadcastReceiver接收到的是有序廣播還是無序廣播?(2015-10-16)
在BroadcastReceiver類中onReceive()方法中,可以調用boolean b = isOrderedBroadcast();該方法是BroadcastReceiver類中提供的方法,用于告訴我們當前的接收到的廣播是否為有序廣播。
?
?
?
四、ContentProvider&數據庫
1、請介紹下ContentProvider是如何實現數據共享的?
在Android中如果想將自己應用的數據(一般多為數據庫中的數據)提供給第三發應用,那么我們只能通過ContentProvider來實現了。
ContentProvider是應用程序之間共享數據的接口。使用的時候首先自定義一個類繼承ContentProvider,然后覆寫query、insert、update、delete等方法。因為其是四大組件之一因此必須在AndroidManifest文件中進行注冊。
第三方可以通過ContentResolver來訪問該Provider。
2、為什么要用ContentProvider?它和sql的實現上有什么差別?
ContentProvider屏蔽了數據存儲的細節,內部實現對用戶完全透明,用戶只需要關心操作數據的uri就可以了,ContentProvider可以實現不同app之間共享。
Sql也有增刪改查的方法,但是sql只能查詢本應用下的數據庫。而ContentProvider 還可以去增刪改查本地文件. xml文件的讀取等。
3、說說ContentProvider、ContentResolver、ContentObserver之間的關系
ContentProvider 內容提供者,用于對外提供數據
ContentResolver.notifyChange(uri)發出消息
ContentResolver 內容解析者,用于獲取內容提供者提供的數據
ContentObserver 內容監聽器,可以監聽數據的改變狀態
ContentResolver.registerContentObserver()監聽消息。
4、如何訪問asserts資源目錄下的數據庫?
//1. 獲取到assert目錄下的db文件
AssetManager assetManager = getAssets();
InputStream is = assetManager.open("myuser.db");
//將文件拷貝到/data/data/com.itheima.android.asserts.sqlite/databases/myuser.db
//如果databases目錄不存在則創建
File file = new?File("/data/data/com.itheima.android.asserts.sqlite/databases");
if?(!file.exists()) {
file.mkdirs();
}
FileOutputStream fos = new?FileOutputStream(new?File(file,"myuser.db"));
byte[] buff = new?byte[1024*8];
int?len=-1;
while((len=is.read(buff))!=-1){
fos.write(buff, 0, len);
}
fos.close();
is.close();
//訪問數據庫
SQLiteDatabase database = openOrCreateDatabase("myuser.db",MODE_PRIVATE, null);
String sql = "select c_name from t_user";
Cursor cursor = database.rawQuery(sql , null);
while(cursor.moveToNext()){
String string = cursor.getString(0);
Log.d("tag", string);
}
cursor.close();
database.close();
}
5、如何在高并發下進行數據庫查詢?(2015-11-25)
(這個問題的回答很廣泛,可以自由發揮)
比如:不要關聯多表查詢,減少鏈接時間,創建索引、將查詢到的數據采用緩存策略等等。
五、Android中的布局
1、Android中常用的布局都有哪些
FrameLayout
RelativeLayout
LinearLayout
AbsoluteLayout
TableLayout
GrideLayout(Android 4.0推出)
2、談談UI中, Padding和Margin有什么區別?
android:padding和android:layout_margin的區別,其實概念很簡單,padding是站在父view的角度描述問題,它規定它里面的內容必須與這個父view邊界的距離。margin則是站在自己的角度描述問題,規定自己和其他(上下左右)的view之間的距離,如果同一級只有一個view,那么它的效果基本上就和padding一樣了。
3、使用權重如何讓一個控件的寬度為父控件的1/3?
可以在水平方向的LinearLayout中設置weightSum為3,然后讓其子控件的weight為1,那么該子控件就是父控件的1/3。
4、Android中布局的優化措施都有哪些?
1、盡可能減少布局的嵌套層級
可以使用sdk提供的hierarchyviewer工具分析視圖樹,幫助我們發現沒有用到的布局。
2、不用設置不必要的背景,避免過度繪制
比如父控件設置了背景色,子控件完全將父控件給覆蓋的情況下,那么父控件就沒有必要設置背景。
3、使用<include>標簽復用相同的布局代碼
4、使用<merge>標簽減少視圖層次結構
該標簽主要有兩種用法:
1)?因為所有的Activity視圖的根節點都是FrameLayout,因此如果我們的自定義的布局也是FragmenLayout的時候那么可以使用merge替換。
2)?當應用Include或者ViewStub標簽從外部導入xml結構時,可以將被導入的xml用merge作為根節點表示,這樣當被嵌入父級結構中后可以很好的將它所包含的子集融合到父級結構中,而不會出現冗余的節點。
<merge>只能作為xml布局的根元素。
5、通過<ViewStub>實現View的延遲加載
布局如下:
核心代碼:
5、android:layout_gravity和android:gravity的區別?
第一個是讓該布局在其父控件中的布局方式,第二個是該布局布置其字對象的布局方式。
?
?
?
六、ListView
1、ListView如何提高其效率?
① 復用ConvertView
② 自定義靜態類ViewHolder
③ 使用分頁加載
④ 使用WeakRefrence引用ImageView對象
2、ViewHolder為什么要聲明為靜態類?
非靜態內部類擁有外部類對象的強引用,因此為了避免對外部類(外部類很可能是Activity)對象的引用,那么最好將內部類聲明為static的。
3、在Activity中使用Handler的時候如何去除警告信息?
可以使用弱引用.
4、談談ListView中的MVC思想?
M:model
V:view
C:Controller
5、ListView使用了哪些設計模式?
1、適配器
2、觀察者
3、享元設計模式
6、當ListView數據集改變后,如何更新ListView?
使用該ListView的adapter的notifyDataSetChanged()方法。該方法會使ListView重新繪制。
7、ListView如何實現分頁加載
? ① 設置ListView的滾動監聽器:setOnScrollListener(new OnScrollListener{….})
在監聽器中有兩個方法: 滾動狀態發生變化的方法(onScrollStateChanged)和listView被滾動時調用的方法(onScroll)
? ② 在滾動狀態發生改變的方法中,有三種狀態:
手指按下移動的狀態: SCROLL_STATE_TOUCH_SCROLL: // 觸摸滑動
慣性滾動(滑翔(flging)狀態): SCROLL_STATE_FLING: // 滑翔
靜止狀態: SCROLL_STATE_IDLE: // 靜止
?對不同的狀態進行處理:
分批加載數據,只關心靜止狀態:關心最后一個可見的條目,如果最后一個可見條目就是數據適配器(集合)里的最后一個,此時可加載更多的數據。在每次加載的時候,計算出滾動的數量,當滾動的數量大于等于總數量的時候,可以提示用戶無更多數據了。
8、ListView可以顯示多種類型的條目嗎?
這個當然可以的,ListView顯示的每個條目都是通過baseAdapter的getView(int position, View convertView, ViewGroup parent)來展示的,理論上我們完全可以讓每個條目都是不同類型的view,除此之外adapter還提供了getViewTypeCount()和getItemViewType(int position)兩個方法。在getView方法中我們可以根據不同的viewtype加載不同的布局文件。
9、ListView如何定位到指定位置
可以通過ListView提供的lv.setSelection(48);方法。
10、如何在ScrollView中如何嵌入ListView
通常情況下我們不會在ScrollView中嵌套ListView,但是如果面試官非讓我嵌套的話也是可以的。
在ScrollView添加一個ListView會導致listview控件顯示不全,通常只會顯示一條,這是因為兩個控件的滾動事件沖突導致。所以需要通過listview中的item數量去計算listview的顯示高度,從而使其完整展示,如下提供一個方法供大家參考。
如下圖,在ScrollView中嵌套了ListView,ListView展現的是物流狀態。
?
布局文件片段:
1.?<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
2.?????android:layout_width="match_parent"
3.?????android:layout_height="match_parent"
4.?????android:orientation="vertical" >
5.?????<ScrollView
6.?????????android:id="@+id/sv"
7.?????????android:layout_width="match_parent"
8.?????????android:layout_height="0dp"
9.?????????android:layout_weight="1" >
10.?
11.?????????<LinearLayout
12.?????????????android:layout_width="match_parent"
13.?????????????android:layout_height="match_parent"
14.?????????????android:orientation="vertical" >
15.?
16.?????????????<ListView
17.?????????????????android:id="@+id/lv"
18.?????????????????android:layout_width="match_parent"
19.?????????????????android:layout_height="wrap_content" >
20.?????????????</ListView>
21.?????????</LinearLayout>
22.?????</ScrollView>
23.?
24.?</LinearLayout>
注意:如果直接將ListView放到ScrollView中,那么上面的代碼依然是沒有效果的.必須將ListVIew放到LinearLayout等其他容器中才行.
11、ListView中如何優化圖片
圖片的優化策略比較多。
1、處理圖片的方式:
如果ListView中自定義的Item中有涉及到大量圖片的,一定要對圖片進行細心的處理,因為圖片占的內存是ListView項中最頭疼的,處理圖片的方法大致有以下幾種:
①、不要直接拿路徑就去循環BitmapFactory.decodeFile;使用Options保存圖片大小、不要加載圖片到內存去。
②、對圖片一定要經過邊界壓縮尤其是比較大的圖片,如果你的圖片是后臺服務器處理好的那就不需要了
③、在ListView中取圖片時也不要直接拿個路徑去取圖片,而是以WeakReference(使用WeakReference代替強引用。比如可以使用WeakReference?mContextRef)、SoftReference、WeakHashMap等的來存儲圖片信息。
④、在getView中做圖片轉換時,產生的中間變量一定及時釋放
2、異步加載圖片基本思想:
1)、?先從內存緩存中獲取圖片顯示(內存緩沖)
2)、獲取不到的話從SD卡里獲取(SD卡緩沖)
3)、都獲取不到的話從網絡下載圖片并保存到SD卡同時加入內存并顯示(視情況看是否要顯示)
原理:
優化一:先從內存中加載,沒有則開啟線程從SD卡或網絡中獲取,這里注意從SD卡獲取圖片是放在子線程里執行的,否則快速滑屏的話會不夠流暢。
優化二:于此同時,在adapter里有個busy變量,表示listview是否處于滑動狀態,如果是滑動狀態則僅從內存中獲取圖片,沒有的話無需再開啟線程去外存或網絡獲取圖片。
優化三:ImageLoader里的線程使用了線程池,從而避免了過多線程頻繁創建和銷毀,如果每次總是new一個線程去執行這是非常不可取的,好一點的用的AsyncTask類,其實內部也是用到了線程池。在從網絡獲取圖片時,先是將其保存到sd卡,然后再加載到內存,這么做的好處是在加載到內存時可以做個壓縮處理,以減少圖片所占內存。
12、ListView中圖片錯位的問題是如何產生的
圖片錯位問題的本質源于我們的listview使用了緩存convertView,假設一種場景,一個listview一屏顯示九個item,那么在拉出第十個item的時候,事實上該item是重復使用了第一個item,也就是說在第一個item從網絡中下載圖片并最終要顯示的時候,其實該item已經不在當前顯示區域內了,此時顯示的后果將可能在第十個item上輸出圖像,這就導致了圖片錯位的問題。所以解決之道在于可見則顯示,不可見則不顯示。
?
13、scrollView嵌套listview方式除了測量還有什么方法?(2015-11-29)
1、手動設置ListView高度
經過測試發現,在xml中直接指定ListView的高度,是可以解決這個問題的,但是ListView中的數據是可變的,實際高度還需要實際測量。
于是手動代碼設置ListView高度的方法就誕生了。
/**
* 動態設置ListView的高度
* @param listView
*/
public static void setListViewHeightBasedOnChildren(ListView listView) {
if(listView == null) return;
ListAdapter listAdapter = listView.getAdapter();
if (listAdapter == null) {
// pre-condition
return;
}
int totalHeight = 0;
for (int i = 0; i < listAdapter.getCount(); i++) {
View listItem = listAdapter.getView(i, null, listView);
listItem.measure(0, 0);
totalHeight += listItem.getMeasuredHeight();
}
ViewGroup.LayoutParams params = listView.getLayoutParams();
params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
listView.setLayoutParams(params);
}
2、使用單個ListView取代ScrollView中所有內容
如果滿足頭布局和腳布局的UI設計,直接使用listview替代scrollview
3、使用LinearLayout取代ListView
既然ListView不能適應ScrollView,那就換一個可以適應ScrollView的控件,干嘛非要吊死在ListView這一棵樹上呢?
而LinearLayout是最好的選擇。但如果我仍想繼續使用已經定義好的Adater呢?我們只需要自定義一個類繼承自LinearLayout,為其加上對BaseAdapter的適配。
4、自定義可適應ScrollView的ListView
這個方法和上面的方法是異曲同工,方法3是自定義了LinearLayout以取代ListView的功能,但如果我脾氣就是倔,就是要用ListView怎么辦?
那就只好自定義一個類繼承自ListView,通過重寫其onMeasure方法,達到對ScrollView適配的效果。
5、參考博客
?http://bbs.anzhuo.cn/thread-982250-1-1.html
?
七、JNI&NDK
1、在Android中如何調用C語言
當我們的Java需要調用C語言的時候可以通過JNI的方式,Java Native Interface。Android提供了對JNI的支持,因此我們在Android中可以使用JNI調用C語言。在Android開發目錄的libs目錄下添加xxx.so文件,不過xxx.so文件需要放在對應的CPU架構名目錄下,比如armeabi,x86等。
在Java代碼需要通過System.loadLibrary(libName);加載so文件。同時C語言中的方法在java中必須以native關鍵字來聲明。普通Java方法調用這個native方法接口,虛擬機內部自動調用so文件中對應的方法。
2、請介紹一下NDK
1.NDK 是一系列工具的集合
NDK 提供了一系列的工具,幫助開發者快速開發C(或C++)的動態庫,并能自動將so 和java 應用一起打包成apk。NDK 集成了交叉編譯器,并提供了相應的mk 文件隔離CPU、平臺、ABI 等差異,開發人員只需要簡單修改mk 文件(指出“哪些文件需要編譯”、“編譯特性要求”等),就可以創建出so。
2.NDK 提供了一份穩定、功能有限的API 頭文件聲明
Google 明確聲明該API 是穩定的,在后續所有版本中都穩定支持當前發布的API。從該版本的NDK 中看出,這些API 支持的功能非常有限,包含有:C 標準庫(libc)、標準數學庫(libm)、壓縮庫(libz)、Log 庫(liblog)。
3、JNI調用常用的兩個參數
?JNIEnv *env, jobject obj
第一個是指向虛擬機對象的指針,是一個二級指針。里面封裝了很多方法和中間變量供我們使用。
第二個代表著調用該方法的Java對象的C語言表示。
八、Android中的網絡訪問
1、Android中如何訪問網絡
Android提供了org.apache.http.HttpClientConnection和java.net.HttpURLConnection兩個連接網絡對象。使用哪個都行,具體要看企業領導的要求了。
除此之外一般我比較喜歡使用xUtils中的HttpUtils功能,該模塊底層使用的就是org.apache.http.client.HttpClient,使用起來非常方便。
2、如何解析服務器傳來的JSON文件
在Android中內置了JSON的解析API,在org.json包中包含了如下幾個類:JSONArray、JSONObject、JSONStringer、JSONTokener和一個異常類JSONException。
?
1、JSON解析步驟
1)、讀取網絡文件數據并轉為一個json字符串
InputStream in = conn.getInputStream();
String jsonStr = DataUtil.Stream2String(in);//將流轉換成字符串的工具類
2)、將字符串傳入相應的JSON構造函數中
①、通過構造函數將json字符串轉換成json對象
JSONObject ?jsonObject = new JSONObject(jsonStr);
②、通過構造函數將json字符串轉換成json數組:
JSONArray array = new JSONArray(jsonStr);
3)、解析出JSON中的數據信息:
①、從json對象中獲取你所需要的鍵所對應的值
JSONObject ?json=jsonObject.getJSONObject("weatherinfo");
String city = json.getString("city");
String temp = json.getString("temp")
②、遍歷JSON數組,獲取數組中每一個json對象,同時可以獲取json對象中鍵對應的值
for (int i = 0; i < array.length(); i++) {
JSONObject obj = array.getJSONObject(i);
String title=obj.getString("title");
String description=obj.getString("description");
}
2、生成JSON對象和數組
1)生成JSON:
方法1、創建一個map,通過構造方法將map轉換成json對象
Map<String, Object> map = new HashMap<String, Object>();
map.put("name", "zhangsan");
map.put("age", 24);
JSONObject json = new JSONObject(map);
方法2、創建一個json對象,通過put方法添加數據
JSONObject json=new JSONObject();
json.put("name", "zhangsan");
json.put("age", 24);
2)生成JSON數組:
創建一個list,通過構造方法將list轉換成json對象
Map<String, Object> map1 = new HashMap<String, Object>();
map1.put("name", "zhangsan");
map1.put("age", 24);
Map<String, Object> map2 = new HashMap<String, Object>();
map2.put("name", "lisi");
map2.put("age", 25);
List<Map<String, Object>> list=new ArrayList<Map<String,Object>>();
list.add(map1);
list.add(map2);
JSONArray array=new JSONArray(list);
System.out.println(array.toString());
3、如何解析服務器傳來的XML格式數據
Android為我們提供了原生的XML解析和生成支持。
1、XML解析
獲取解析器: Xml.newPullParser()
設置輸入流: setInput()
獲取當前事件類型: getEventType()
解析下一個事件, 獲取類型: next()
獲取標簽名: getName()
獲取屬性值: getAttributeValue()
獲取下一個文本: nextText()
獲取當前文本: getText()
5種事件類型: START_DOCUMENT, END_DOCUMENT, START_TAG, END_TAG, TEXT
示例代碼:
public List<Person> getPersons(InuptStream in){
XmlPullParser parser=Xml.newPullParser();//獲取解析器
parser.setInput(in,"utf-8");
for(int type=){ ??//循環解析
}
}
2、XML生成
獲取生成工具: Xml.newSerializer()
設置輸出流: setOutput()
開始文檔: startDocument()
結束文檔: endDocument()
開始標簽: startTag()
結束標簽: endTag()
屬性: attribute()
文本: text()
示例代碼:
XmlSerializer serial=Xml.newSerializer();//獲取xml序列化工具
serial.setOuput(put,"utf-8");
serial.startDocument("utf-8",true);
serial.startTag(null,"persons");
for(Person p:persons){
serial.startTag(null,"persons");
serial.attribute(null,"id",p.getId().toString());
serial.startTag(null,"name");
serial.attribute(null,"name",p.getName().toString());
serial.endTag(null,"name");
serial.startTag(null,"age");
serial.attribute(null,"age",p.getAge().toString());
serial.endTag(null,"age");
serial.endTag(null,"persons");
}
4、如何從網絡上加載一個圖片顯示到界面
可以通過BitmapFactory.decodeStream(inputStream);方法將圖片轉換為bitmap,然后通過
imageView.setImageBitmap(bitmap);將該圖片設置到ImageView中。這是原生的方法,還可以使用第三方開源的工具來實現,比如使用SmartImageView作為ImageView控件,然后直接設置一個url地址即可。也可以使用xUtils中的BitmapUtils工具。
5、如何播放網絡視頻
除了使用Android提供的MediaPlayer和VideoView外通常還可以使用第三方開源萬能播放器,VitamioPlayer。該播放器兼容性好,支持幾乎所有主流視頻格式。
6、常見的訪問網絡API都有哪些?
Android系統自帶的HttpUrlConnection/HttpClient。
java.net.HttpURLConnection;
?
org.apache.http.client.HttpClient;
OKhttp:
http://blog.csdn.net/lmj623565791/article/details/47911083
xUtils:
https://github.com/wyouflf/xUtils
?
九、Intent
1、Intent傳遞數據時,可以傳遞哪些類型數據?
Intent可以傳遞的數據類型非常的豐富,java的基本數據類型和String以及他們的數組形式都可以,除此之外還可以傳遞實現了Serializable和Parcelable接口的對象。
2、Serializable和Parcelable的區別
1.在使用內存的時候,Parcelable 類比Serializable性能高,所以推薦使用Parcelable類。
2.Serializable在序列化的時候會產生大量的臨時變量,從而引起頻繁的GC。
3.Parcelable不能使用在要將數據存儲在磁盤上的情況。盡管Serializable效率低點,但在這種情況下,還是建議你用Serializable 。
實現:
1 Serializable 的實現,只需要繼承Serializable 即可。這只是給對象打了一個標記,系統會自動將其序列化。
2 Parcelabel 的實現,需要在類中添加一個靜態成員變量 CREATOR,這個變量需要繼承 Parcelable.Creator 接口。
3、請描述一下Intent 和 IntentFilter
Android 中通過 Intent 對象來表示一條消息,一個 Intent 對象不僅包含有這個消息的目的地,還可以包含消息的內容,這好比一封 Email,其中不僅應該包含收件地址,還可以包含具體的內容。對于一個 Intent 對象,消息“目的地”是必須的,而內容則是可選項。
通過Intent 可以實現各種系統組件的調用與激活. ?
IntentFilter: 可以理解為郵局或者是一個信箋的分揀系統…
這個分揀系統通過3個參數來識別
Action: 動作 ???view
Data: 數據uri ??uri
Category : 而外的附加信息
Action 匹配
Action 是一個用戶定義的字符串,用于描述一個 Android 應用程序組件,一個 IntentFilter 可以包含多個 Action。在 AndroidManifest.xml 的 Activity 定義時可以在其 <intent-filter >節點指定一個 Action 列表用于標示 Activity 所能接受的“動作”,例如:
?<intent-filter >
?<action android:name="android.intent.action.MAIN" />
?<action android:name="cn.itheima.action" />
……
?</intent-filter>
如果我們在啟動一個 Activity 時使用這樣的 Intent 對象:
?Intent intent =new Intent();
?intent.setAction("cn.itheima.action");
那么所有的 Action 列表中包含了“cn.itheima”的 Activity 都將會匹配成功。
Android 預定義了一系列的 Action 分別表示特定的系統動作。這些 Action 通過常量的方式定義在 android.content. Intent中,以“ACTION_”開頭。我們可以在 Android 提供的文檔中找到它們的詳細說明。
URI 數據匹配
一個 Intent 可以通過 URI 攜帶外部數據給目標組件。在 <intent-filter >節點中,通過 <data/>節點匹配外部數據。
mimeType 屬性指定攜帶外部數據的數據類型,scheme 指定協議,host、port、path 指定數據的位置、端口、和路徑。如下:
?<data android:mimeType="mimeType" android:scheme="scheme"
?android:host="host" android:port="port" android:path="path"/>
電話的uri ??tel: 12345
???http://www.baidu.com
自己定義的uri ?itcast://cn.itcast/person/10
如果在 Intent Filter 中指定了這些屬性,那么只有所有的屬性都匹配成功時 URI 數據匹配才會成功。
Category 類別匹配
<intent-filter >節點中可以為組件定義一個 Category 類別列表,當 Intent 中包含這個列表的所有項目時 Category 類別匹配才會成功。
4、under what condition could the code sample below crash your application?How would you modify the code to avoid this potential problem?Explain your answer?(重要)
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT,textMessage);
sendIntent.setType(HTTP.PLAIN_TEXT_TYEP);//"text/plain" MIME type
context.startActivity(sendIntent);
answer:
if startActivity(Intent ) method is not called from in Activity ,for example,from service or receiver,it will crash your application.
I can add a flag “Intent_ACTIVITY_NEW_TASK”on sendIntent to avoid this error.
5、what are Activity and Fragment?where and why should you use one over the other?
?
6、can you use an intent to provide data to a ContentProvider ? if not ,what would be the proper mechanism for doing this?
SQLiteDatabase is often used to provide data to a ContenProvider.
十、Fragment
1、Fragment跟Activity之間是如何傳值的?
1) 當Fragment跟Activity綁定之后,在Fragment中可以直接通過getActivity()方法獲取到其綁定的Activity對象,這樣就可以調用Activity的方法了。在Activity中可以通過如下方法獲取到Fragment實例
獲取到Fragment之后就可以調用Fragment的方法。也就實現了通信功能。
2)另外也可以調用fragment.setArguments(Bundle)方法,將數據綁定到Fragment域中。
2、描述一下Fragment的生命周期
3、Fragment的replace和add方法的區別(2015.8.30)
Fragment本身并沒有replace和add方法,這里的理解應該為使用FragmentManager的replace和add兩種方法切換Fragment時有什么不同。
我們經常使用的一個架構就是通過RadioGroup切換Fragment,每個Fragment就是一個功能模塊。
實現這個功能可以通過replace和add兩種方法。
Fragment的容器一個FrameLayout,add的時候是把所有的Fragment一層一層的疊加到了FrameLayout上了,而replace的話首先將該容器中的其他Fragment去除掉然后將當前Fragment添加到容器中。
一個Fragment容器中只能添加一個Fragment種類,如果多次添加則會報異常,導致程序終止,而replace則無所謂,隨便切換。
因為通過add的方法添加的Fragment,每個Fragment只能添加一次,因此如果要想達到切換效果需要通過Fragment的的hide和show方法結合者使用。將要顯示的show出來,將其他hide起來。這個過程Fragment的生命周期沒有變化。
通過replace切換Fragment,每次都會執行上一個Fragment的onDestroyView,新Fragment的onCreateView、onStart、onResume方法。
基于以上不同的特點我們在使用的使用一定要結合著生命周期操作我們的視圖和數據。
4、Fragment如何實現類似Activity棧的壓棧和出棧效果的?(2015.8.30)
Fragment的事物管理器內部維持了一個雙向鏈表結構,該結構可以記錄我們每次add的Fragment和replace的Fragment,然后當我們點擊back按鈕的時候會自動幫我們實現退棧操作。
除此之外因為我們要使用FragmentManger用的是FragmentActivity,因此FragmentActivity的onBackPress方法必定重新覆寫了。打開看一下,發現確實如此。
1.?Android高級(★★★)
一、Android性能優化
1、如何對Android應用進行性能分析
一款App流暢與否安裝在自己的真機里,玩幾天就能有個大概的感性認識。不過通過專業的分析工具可以使我們更好的分析我們的應用。
如果不考慮使用其他第三方性能分析工具的話,我們可以直接使用ddms中的工具,其實ddms工具已經非常的強大了。ddms中有traceview、heap、allocation tracker等工具都可以幫助我們分析應用的方法執行時間效率和內存使用情況。
?traceview工具在本文章中已經有詳細的介紹,因此這里就不再贅述。
?heap
heap工具可以幫助我們檢查代碼中是否存在會造成內存泄漏的地方。
用heap監測應用進程使用內存情況的步驟如下:
1. 啟動eclipse后,切換到DDMS透視圖,并確認Devices視圖、Heap視圖都是打開的;
2. 點擊選中想要監測的進程,比如system_process進程;
3. 點擊選中Devices視圖界面中最上方一排圖標中的“Update Heap”圖標;
6. 點擊Heap視圖中的“Cause GC”按鈕;
7. 此時在Heap視圖中就會看到當前選中的進程的內存使用量的詳細情況。
?說明:
a) 點擊“Cause GC”按鈕相當于向虛擬機請求了一次gc操作;
b) 當內存使用信息第一次顯示以后,無須再不斷的點擊“Cause GC”,Heap視圖界面會定時刷新,在對應用的不斷的操作過程中就可以看到內存使用的變化;
c) 內存使用信息的各項參數根據名稱即可知道其意思,在此不再贅述。
??如何才能知道我們的程序是否有內存泄漏的可能性呢。這里需要注意一個值:Heap視圖中部有一個Type叫做data object,即數據對象,也就是我們的程序中大量存在的類類型的對象。在data object一行中有一列是“Total Size”,其值就是當前進程中所有Java數據對象的內存總量,一般情況下,這個值的大小決定了是否會有內存泄漏。可以這樣判斷:
a) 不斷的操作當前應用,同時注意觀察data object的Total Size值;
b) 正常情況下Total Size值都會穩定在一個有限的范圍內,也就是說由于程序中的的代碼良好,沒有造成對象不被垃圾回收的情況,所以說雖然我們不斷的操作會不斷的生成很多對象,而在虛擬機不斷的進行GC的過程中,這些對象都被回收了,內存占用量會會落到一個穩定的水平;
c) 反之如果代碼中存在沒有釋放對象引用的情況,則data object的Total Size值在每次GC后不會有明顯的回落,隨著操作次數的增多Total Size的值會越來越大,
??直到到達一個上限后導致進程被kill掉。
d) 此處以system_process進程為例,在我的測試環境中system_process進程所占用的內存的data object的Total Size正常情況下會穩定在2.2~2.8之間,而當其值超過3.55后進程就會被kill。
??總之,使用DDMS的Heap視圖工具可以很方便的確認我們的程序是否存在內存泄漏的可能性。
?allocation tracker
運行DDMS,只需簡單的選擇應用進程并單擊Allocation tracker標簽,就會打開一個新的窗口,單擊“Start Tracing”按鈕;
然后,讓應用運行你想分析的代碼。運行完畢后,單擊“Get Allocations”按鈕,一個已分配對象的列表就會出現第一個表格中。
單擊第一個表格中的任何一項,在表格二中就會出現導致該內存分配的棧跟蹤信息。通過allocation tracker,不僅知道分配了哪類對象,還可以知道在哪個線程、哪個類、哪個文件的哪一行。
2、什么情況下會導致內存泄露
Android的虛擬機是基于寄存器的Dalvik,它的最大堆大小一般是16M,有的機器為24M。因此我們所能利用的內存空間是有限的。如果我們的內存占用超過了一定的水平就會出現OutOfMemory的錯誤。
內存溢出的幾點原因:
1、資源釋放問題
程序代碼的問題,長期保持某些資源,如Context、Cursor、IO流的引用,資源得不到釋放造成內存泄露。
2、對象內存過大問題
保存了多個耗用內存過大的對象(如Bitmap、XML文件),造成內存超出限制。
3、static關鍵字的使用問題
static是Java中的一個關鍵字,當用它來修飾成員變量時,那么該變量就屬于該類,而不是該類的實例。所以用static修飾的變量,它的生命周期是很長的,如果用它來引用一些資源耗費過多的實例(Context的情況最多),這時就要謹慎對待了。
public class ClassName { ?
?????private static Context mContext; ?
?????//省略
}
以上的代碼是很危險的,如果將Activity賦值到mContext的話。那么即使該Activity已經onDestroy,但是由于仍有對象保存它的引用,因此該Activity依然不會被釋放。
我們舉Android文檔中的一個例子。
?
?
?
?
sBackground是一個靜態的變量,但是我們發現,我們并沒有顯式的保存Contex的引用,但是,當Drawable與View連接之后,Drawable就將View設置為一個回調,由于View中是包含Context的引用的,所以,實際上我們依然保存了Context的引用。這個引用鏈如下:
????Drawable->TextView->Context
????所以,最終該Context也沒有得到釋放,發生了內存泄露。
?針對static的解決方案
① 應該盡量避免static成員變量引用資源耗費過多的實例,比如Context。
????② Context盡量使用ApplicationContext,因為Application的Context的生命周期比較長,引用它不會出現內存泄露的問題。
????③ 使用WeakReference代替強引用。比如可以使用WeakReference<Context> mContextRef;
4、線程導致內存溢出
線程產生內存泄露的主要原因在于線程生命周期的不可控。我們來考慮下面一段代碼。
?
?
這段代碼很平常也很簡單,是我們經常使用的形式。我們思考一個問題:假設MyThread的run函數是一個很費時的操作,當我們開啟該線程后,將設備的橫屏變為了豎屏,一 般情況下當屏幕轉換時會重新創建Activity,按照我們的想法,老的Activity應該會被銷毀才對,然而事實上并非如此。
由于我們的線程是Activity的內部類,所以MyThread中保存了Activity的一個引用,當MyThread的run函數沒有結束時,MyThread是不會被銷毀的,因此它所引用的老的Activity也不會被銷毀,因此就出現了內存泄露的問題。
有些人喜歡用Android提供的AsyncTask,但事實上AsyncTask的問題更加嚴重,Thread只有在run函數不結束時才出現這種內存泄露問題,然而AsyncTask內部的實現機制是運用了ThreadPoolExcutor,該類產生的Thread對象的生命周期是不確定的,是應用程序無法控制的,因此如果AsyncTask作為Activity的內部類,就更容易出現內存泄露的問題。
針對這種線程導致的內存泄露問題的解決方案:
????第一、將線程的內部類,改為靜態內部類(因為非靜態內部類擁有外部類對象的強引用,而靜態類則不擁有)。
????第二、在線程內部采用弱引用保存Context引用。
3、如何避免OOM異常
想要避免OOM異常首先我們要知道什么情況下會導致OOM異常。
1、圖片過大導致OOM
Android 中用bitmap時很容易內存溢出,比如報如下錯誤:Java.lang.OutOfMemoryError : bitmap size exceeds VM budget。
解決方法:
方法1: 等比例縮小圖片
以上代碼可以優化內存溢出,但它只是改變圖片大小,并不能徹底解決內存溢出。
方法2:對圖片采用軟引用,及時地進行recyle()操作
2、界面切換導致OOM
有時候我們會發現這樣的問題,橫豎屏切換N次后 OOM了。
這種問題沒有固定的解決方法,但是我們可以從以下幾個方面下手分析。
1、看看頁面布局當中有沒有大的圖片,比如背景圖之類的。
去除xml中相關設置,改在程序中設置背景圖(放在onCreate()方法中):
????在Activity destory時注意,drawable.setCallback(null);?防止Activity得不到及時的釋放。
?2、跟上面方法相似,直接把xml配置文件加載成view 再放到一個容器里,然后直接調用 this.setContentView(View view);方法,避免xml的重復加載。
3、 在頁面切換時盡可能少地重復使用一些代碼
比如:重復調用數據庫,反復使用某些對象等等......
常見的內存使用不當的情況
3、查詢數據庫沒有關閉游標
程序中經常會進行查詢數據庫的操作,但是經常會有使用完畢Cursor后沒有關閉的情況。如果我們的查詢結果集比較小,對內存的消耗不容易被發現,只有在常時間大量操作的情況下才會出現內存問題,這樣就會給以后的測試和問題排查帶來困難和風險。
4、構造Adapter時,沒有使用緩存的 convertView
在使用ListView的時候通常會使用Adapter,那么我們應該盡可能的使用ConvertView。
5、Bitmap對象不再使用時調用recycle()釋放內存
有時我們會手工的操作Bitmap對象,如果一個Bitmap對象比較占內存,當它不再被使用的時候,可以調用Bitmap.recycle()方法回收此對象的像素所占用的內存,但這不是必須的,視情況而定。
6、其他
??Android應用程序中最典型的需要注意釋放資源的情況是在Activity的生命周期中,在onPause()、onStop()、 onDestroy()方法中需要適當的釋放資源的情況。
4、Android中如何捕獲未捕獲的異常(重要)
1、自定義一個Application,比如叫MyApplication繼承Application實現UncaughtExceptionHandler。
2、覆寫UncaughtExceptionHandler的onCreate和uncaughtException方法。
注意:上面的代碼只是簡單的將異常打印出來。
在onCreate方法中我們給Thread類設置默認異常處理handler,如果這句代碼不執行則一切都是白搭。
在uncaughtException方法中我們必須新開辟個線程進行我們異常的收集工作,然后將系統給殺死。
3、在AndroidManifest中配置該Application
4、blog分享
關于異常數據的收集在網上有一篇不錯的blog可以推薦給大家。
http://blog.csdn.net/jdsjlzx/article/details/7606423
?
5、Android性能優化博客
給大家推薦一篇Android性能優化的blog,其目錄如下:
1.背景
2.應用UI性能問題分析
2-1 應用UI卡頓原理
2-2 應用UI卡頓常見原因
2-3 應用UI卡頓分析解決方法
2-3-1 使用HierarchyViewer分析UI性能
2-3-2 使用GPU過度繪制分析UI性能
2-3-3 使用GPU呈現模式圖及FPS考核UI性能
2-3-4 使用Lint進行資源及冗余UI布局等優化
2-3-5 使用Memory監測及GC打印與Allocation Tracker進行UI卡頓分析
2-3-6 使用Traceview和dmtracedump進行分析優化
2-3-7 使用Systrace進行分析優化
2-3-8 使用tracestxt文件進行ANR分析優化
2-4 應用UI性能分析解決總結
3.應用開發Memory內存性能分析優化
3-1 Android內存管理原理
3-2 Android內存泄露性能分析
3-2-1 Android應用內存泄露概念
3-2-2 Android應用內存泄露察覺手段
3-2-3 Android應用內存泄露leakcanary工具定位分析
3-2-4 Android應用內存泄露MAT工具定位分析
3-2-5 Android應用開發規避內存泄露建議
3-3 Android內存溢出OOM性能分析
3-3-1 Android應用內存溢出OOM概念
3-3-2 Android應用內存溢出OOM性能分析
3-3-3 Android應用規避內存溢出OOM建議
3-4 Android內存性能優化總結
4.Android應用API使用及代碼邏輯性能分析
4-1 Android應用StringStringBuilderStringBuffer優化建議
4-2 Android應用OnTrimMemory實現性能建議
4-3 Android應用HashMap與ArrayMap及SparseArray優化建議
4-4 Android應用ContentProviderOperation優化建議
4-5 Android應用其他邏輯優化建議
5.Android應用移動設備電池耗電性能分析
5-1 Android應用耗電量概念
5-2 Android應用耗電量優化建議
6.Android應用開發性能優化總結
blog原文如下:
http://blog.csdn.net/yanbober/article/details/48394201
?
?
?
?
二、Android屏幕適配
1、屏幕適配方式都有哪些
1.1 適配方式之dp
名詞解釋:
?分辨率:eg:480*800,1280*720。表示物理屏幕區域內像素點的總和。(切記:跟屏幕適配沒有任何關系)
???因為我們既可以把1280*720的分辨率做到4.0的手機上面。我也可以把1280*720的分辨率做到5.0英寸的手機上面,如果分辨率相同,手機屏幕越小清晰。
?px(pix):像素,就是屏幕中最小的一個顯示單元
?dpi(像素密度):即每英寸屏幕所擁有的像素數,像素密度越大,顯示畫面細節就越豐富。
計算公式:像素密度=√{(長度像素數^2+寬度像素數^2)}/ 屏幕尺寸
注:屏幕尺寸單位為英寸 例:分辨率為1280*720 屏幕寬度為6英寸 計算所得像素密度約等于245,屏幕尺寸指屏幕對角線的長度。
在Android手機中dpi分類:
| ldpi | Resources for low-density (ldpi) screens (~120dpi). |
| mdpi | Resources for medium-density (mdpi) screens (~160dpi). (This is the baseline density.) |
| hdpi | Resources for high-density (hdpi) screens (~240dpi). |
| xhdpi | Resources for extra high-density (xhdpi) screens (~320dpi). |
在我們的Android工程目錄中有如下drawable-*dpi目錄,這些目錄是用來適配不同分辨率手機的。
?
Android應用在查找圖片資源時會根據其分辨率自動從不同的文件目錄下查找(這本身就是Android系統的適配策略),如果在低分辨的文件目錄中比如drawable-mdpi中沒有圖片資源,其他目錄中都有,當我們將該應用部署到mdpi分辨率的手機上時,那么該應用會查找分辨率較高目錄下的資源文件,如果較高分辨率目錄下也沒有資源則只好找較低目錄中的資源了。
常見手機屏幕像素及對應分別率級別:
?ldpi ?320*240 ????
?mdpi ?480*320 ????
?hdpi ?800*480
?xhdpi ?1280*720
?xxhdpi ?1920*1080
dp和px之間的簡單換算關系:
?
?ldpi的手機 1dp=0.75px
?mdpi的手機 1dp=1.0px
?hdpi的手機 1dp=1.5px
?xhdpi的手機 1dp=2.0px
?xxhdpi的手機 1dp=3.0px
?
:根據上面的描述我們得出如下結論,對于mdpi的手機,我們的布局通過dp單位可以達到適配效果。
1.2 適配方式之dimens
跟drawable目錄類似的,在Android工程的res目錄下有values目錄,這個是默認的目錄,同時為了適配不同尺寸手機我們可以創建一個values-1280x720的文件夾,同時將dimens.xml文件拷貝到該目錄下。
?
在dimens.xml中定義一個尺寸,如下圖所示。
?
在values-1280x720目錄中的dimens.xml中定義同樣的尺寸名稱,但是使用不同的尺寸,如下圖所示。
?
當我們在布局文件中使用長或者寬度單位時,比如下圖所示,應該使用@dimen/width來靈活的定義寬度。
?
:在values-1280x720中,中間的是大寫字母X的小寫形式x,而不是加減乘除的乘號。如果我們在values-1280x720中放置了dimens常量,一定記得也將該常量的對應值在values目錄下的dimens.xml中放一份,因為該文件是默認配置,當用戶的手機不是1280*720的情況下系統應用使用的是默認values目錄中的dimens.xml。
1.3 適配方式之layout
跟values一樣,在Android工程目錄中layout目錄也支持類似values目錄一樣的適配,在layout中我們可以針對不同手機的分辨率制定不同的布局,如下圖所示。
?
1.4 適配方式之java代碼適配
為了演示用java代碼控制適配的效果,因此假設有這樣的需求,讓一個TextView控件的寬和高分別為屏幕的寬和高的一半。
我們新創建一個Android工程,修改main_activity.xml,布局文件清單如下:
在MainActivity.java類中完成用java代碼控制TextView的布局效果,其代碼清單如下:
其中Constant類是一個常量類,很簡單,只有兩個常量用來記錄屏幕的寬和高,其代碼清單如下:
1.5適配方式之weight權重適配
在控件中使用屬性android:layout_weight="1"可以起到適配效果,但是該屬性的使用有如下規則:
1、只能用在線性控件中,比如LinearLayout。
2、豎直方向上使用權重的控件高度必須為0dp(Google官方的推薦用法)
3、水平方向上使用權重的控件寬度必須為0dp(Google官方的推薦用法)
2、屏幕適配的處理技巧都有哪些
手機自適應主要分為兩種情況:橫屏和豎屏的切換,以及分辨率大小的不同。
2.1橫屏和豎屏的切換
1、Android應用程序支持橫豎屏幕的切換,Android中每次屏幕的切換動會重啟Activity,所以應該在Activity銷毀(執行onPause()方法和onDestroy()方法)前保存當前活動的狀態;在Activity再次創建的時候載入配置,那樣,進行中的游戲就不會自動重啟了!有的程序適合從豎屏切換到橫屏,或者反過來,這個時候怎么辦呢?可以在配置Activity的地方進行如下的配置android:screenOrientation="portrait"(landscape是橫向,portrait是縱向)。這樣就可以保證是豎屏總是豎屏了。
2、而有的程序是適合橫豎屏切換的。如何處理呢?首先要在配置Activity的時候進行如下的配置:
android:configChanges="keyboardHidden|orientation",另外需要重寫Activity的onConfigurationChanged方法。實現方式如下:
2.2分辨率大小不同
對于分辨率問題,官方給的解決辦法是創建不同的layout文件夾,這就需要對每種分辨率的手機都要寫一個布局文件,雖然看似解決了分辨率的問題,但是如果其中一處或多處有修改了,就要每個布局文件都要做出修改,這樣就造成很大的麻煩。那么可以通過以下幾種方式解決:
一)使用layout_weight
目前最為推薦的Android多屏幕自適應解決方案。
? ? 該屬性的作用是決定控件在其父布局中的顯示權重,一般用于線性布局中。其值越小,則對應的layout_width或layout_height的優先級就越高(一般到100作用就不太明顯了);一般橫向布局中,決定的是layout_width的優先級;縱向布局中,決定的是layout_height的優先級。
? ? 傳統的layout_weight使用方法是將當前控件的layout_width和layout_height都設置成fill_parent,這樣就可以把控件的顯示比例完全交給layout_weight;這樣使用的話,就出現了layout_weight越小,顯示比例越大的情況(即權重越大,顯示所占的效果越小)。不過對于2個控件還好,如果控件過多,且顯示比例也不相同的時候,控制起來就比較麻煩了,畢竟反比不是那么好確定的。于是就有了現在最為流行的0px設值法。看似讓人難以理解的layout_height=0px的寫法,結合layout_weight,卻可以使控件成正比例顯示,輕松解決了當前Android開發最為頭疼的碎片化問題之一。
二)清單文件配置:【不建議使用這種方式,需要對不同的界面寫不同的布局】
需要在AndroidManifest.xml文件的<manifest>元素如下添加子元素
<supports-screensandroid:largeScreens="true"
android:normalScreens="true"
android:anyDensity="true"
android:smallScreens="true"
android:xlargeScreens="true">
</supports-screens>
以上是為我們的屏幕設置多分辨率支持(更準確的說是適配大、中、小三種密度)。
Android:anyDensity="true",這一句對整個的屏幕都起著十分重要的作用,值為true,我們的應用程序當安裝在不同密度的手機上時,程序會分別加載hdpi,mdpi,ldpi文件夾中的資源。相反,如果值設置為false,即使我們在hdpi,mdpi,ldpi,xdpi文件夾下擁有同一種資源,那么應用也不會自動地去相應文件夾下尋找資源。而是會在大密度和小密度手機上加載中密度mdpi文件中的資源。
有時候會根據需要在代碼中動態地設置某個值,可以在代碼中為這幾種密度分別設置偏移量,但是這種方法最好不要使用,最好的方式是在xml文件中不同密度的手機進行分別設置。這里地圖的偏移量可以在values-xpdi,values-hpdi,values-mdpi,values-ldpi四種文件夾中的dimens.xml文件進行設置。
三)、其他:
說明:
在不同分辨率的手機模擬器下,控件顯示的位置會稍有不同
通過在layout中定義的布局設置的參數,使用dp(dip),會根據不同的屏幕分辨率進行適配
但是在代碼中的各個參數值,都是使用的像素(px)為單位的
技巧:
1、盡量使用線性布局,相對布局,如果屏幕放不下了,可以使用ScrollView(可以上下拖動)
ScrowView使用的注意:
在不同的屏幕上顯示內容不同的情況,其實這個問題我們往往是用滾動視圖來解決的,也就是ScrowView;需要注意的是ScrowView中使用layout_weight是無效的,既然使用ScrowView了,就把它里面的控件的大小都設成固定的吧。
2、指定寬高的時候,采用dip的單位,dp單位動態匹配
3、由于android代碼中寫的單位都是像素,所有需要通過工具類進行轉化
4、盡量使用9-patch圖,可以自動的依據圖片上面顯示的內容被拉伸和收縮。其中在編輯的時候,灰色區域是被拉伸的,上下兩個點控制水平方向的拉伸,左右兩點控制垂直方向的拉伸
3、dp和px之間的關系
dp:是dip的簡寫,指密度無關的像素。
指一個抽象意義上的像素,程序用它來定義界面元素。一個與密度無關的,在邏輯尺寸上,與一個位于像素密度為160dpi的屏幕上的像素是一致的。要把密度無關像素轉換為屏幕像素,可以用這樣一個簡單的公式:pixels=dips*(density/160)。舉個例子,在DPI為240的屏幕上,1個DIP等于1.5個物理像素。
布局時最好使用dp來定義我們程序的界面,因為這樣可以保證我們的UI在各種分辨率的屏幕上都可以正常顯示。
三、AIDL
1、什么是AIDL以及如何使用
①aidl是Android interface definition Language 的英文縮寫,意思Android 接口定義語言。
②使用aidl可以幫助我們發布以及調用遠程服務,實現跨進程通信。
③將服務的aidl放到對應的src目錄,工程的gen目錄會生成相應的接口類
我們通過bindService(Intent,ServiceConnect,int)方法綁定遠程服務,在bindService中有一個ServiceConnec接口,我們需要覆寫該類的onServiceConnected(ComponentName,IBinder)方法,這個方法的第二個參數IBinder對象其實就是已經在aidl中定義的接口,因此我們可以將IBinder對象強制轉換為aidl中的接口類。
我們通過IBinder獲取到的對象(也就是aidl文件生成的接口)其實是系統產生的代理對象,該代理對象既可以跟我們的進程通信,又可以跟遠程進程通信,作為一個中間的角色實現了進程間通信。
四、自定義控件
1、如何自定義一個控件
自定義控件可以分為兩種自定義組合控件和自定義view。
?自定義組合控件
自定義組合控件就是把多個控件做為一個整體看待、處理。這樣的好處不僅可以減輕xml的代碼量,也提高了代碼的復用性。
在手機衛士項目中我們第一次接觸了自定義組合控件。
1. 聲明一個View 對象,繼承相對布局,或者線性布局或者其他的ViewGroup。
2. 在自定義的View 對象里面重寫它的構造方法,在構造方法里面就把布局都初始化完畢。
3. 根據業務需求添加一些api 方法,擴展自定義的組合控件;
4. 希望在布局文件里面可以自定義一些屬性。
5. 聲明自定義屬性的命名空間。
xmlns:itheima="http://schemas.android.com/apk/res/com.itheima.mobilesafe"
6. 在res 目錄下的values 目錄下創建attrs.xml 的文件聲明我們寫的屬性。
7. 在布局文件中寫自定義的屬性。
8. 使用這些定義的屬性。自定義View 對象的構造方法里面有一個帶兩個參數的構造方法布局文件里面定義的屬性都放在AttributeSet attrs,獲取那些定義的屬性。
?自定義view
自定義View首先要實現一個繼承自View的類。添加類的構造方法,通常是三個構造方法,不過從Android5.0開始構造方法已經添加到4個了。override父類的方法,如onDraw,(onMeasure)等。如果自定義的View有自己的屬性,需要在values下建立attrs.xml文件,在其中定義屬性,同時代碼也要做修改。
blog分享:http://blog.csdn.net/lmj623565791/article/details/24252901
2、請描述一下View的繪制流程
整個View樹的繪圖流程是在ViewRoot.java類(該類位于Android源碼下面:D:\AndroidSource_GB\AndroidSource_GB\frameworks\base\core\java\android\view)的performTraversals()函數展開的,該函數做的執行過程可簡單概況為根據之前設置的狀態,判斷是否需要重新計算視圖大小(measure)、是否重新需要安置視圖的位置(layout)、以及是否需要重繪 (draw),其框架過程如下:
?
1、mesarue()過程
??主要作用:為整個View樹計算實際的大小,即設置實際的高(對應屬性:mMeasuredHeight)和寬(對應屬性:
??mMeasureWidth),每個View的控件的實際寬高都是由父視圖和本身視圖決定的。
??具體的調用鏈如下: ViewRoot根對象的屬性mView(其類型一般為ViewGroup類型)調用measure()方法去計算View樹的大小,回調View/ViewGroup對象的onMeasure()方法,該方法實現的功能如下: ???
?????????1、設置本View視圖的最終大小,該功能的實現通過調用setMeasuredDimension()方法去設置實際的高(對應屬性:mMeasuredHeight)和寬(對應屬性:mMeasureWidth)。
?????????2 、如果該View對象是個ViewGroup類型,需要重寫該onMeasure()方法,對其子視圖進行遍歷的measure()過程。對每個子視圖的measure()過程,是通過調用父類ViewGroup.java類里的measureChildWithMargins()方法去實現,該方法內部只是簡單地調用了View對象的measure()方法。
2、layout布局過程
主要作用:為將整個根據子視圖的大小以及布局參數將View樹放到合適的位置上。
具體的調用鏈如下:
??????????1、layout方法會設置該View視圖位于父視圖的坐標軸,即mLeft,mTop,mLeft,mBottom(調用setFrame()函數去實現)接下來回調onLayout()方法(如果該View是ViewGroup對象,需要實現該方法,對每個子視圖進行布局)。
??????2、如果該View是個ViewGroup類型,需要遍歷每個子視圖chiildView,調用該子視圖的layout()方法去設置它的坐標值。
3、draw()繪圖過程
??由ViewRoot對象的performTraversals()方法調用draw()方法發起繪制該View樹,值得注意的是每次發起繪圖時,并不會重新繪制每個View樹的視圖,而只會重新繪制那些“需要重繪”的視圖,View類內部變量包含了一個標志位DRAWN,當該視圖需要重繪時,就會為該View添加該標志位。
調用流程 :
??????????1 、繪制該View的背景
??????????2 、為顯示漸變框做一些準備操作(大多數情況下,不需要改漸變框) ?????????
??????????3、調用onDraw()方法繪制視圖本身(每個View都需要重載該方法,ViewGroup不需要實現該方法)
??????????4、調用dispatchDraw ()方法繪制子視圖(如果該View類型不為ViewGroup,即不包含子視圖,不需要重載該方法)
值得說明的是,ViewGroup類已經為我們重寫了dispatchDraw ()的功能實現,應用程序一般不需要重寫該方法,但可以重載父類函數實現具體的功能。
參考blog分享:http://blog.csdn.net/qinjuning/article/details/7110211
五、Android中的事件處理
1、Handler機制
Android中主線程也叫UI線程,那么從名字上我們也知道主線程主要是用來創建、更新UI的,而其他耗時操作,比如網絡訪問,或者文件處理,多媒體處理等都需要在子線程中操作,之所以在子線程中操作是為了保證UI的流暢程度,手機顯示的刷新頻率是60Hz,也就是一秒鐘刷新60次,每16.67毫秒刷新一次,為了不丟幀,那么主線程處理代碼最好不要超過16毫秒。當子線程處理完數據后,為了防止UI處理邏輯的混亂,Android只允許主線程修改UI,那么這時候就需要Handler來充當子線程和主線程之間的橋梁了。
我們通常將Handler聲明在Activity中,然后覆寫Handler中的handleMessage方法,當子線程調用handler.sendMessage()方法后handleMessage方法就會在主線程中執行。
這里面除了Handler、Message外還有隱藏的Looper和MessageQueue對象。
在主線程中Android默認已經調用了Looper.preper()方法,調用該方法的目的是在Looper中創建MessageQueue成員變量并把Looper對象綁定到當前線程中。當調用Handler的sendMessage(對象)方法的時候就將Message對象添加到了Looper創建的MessageQueue隊列中,同時給Message指定了target對象,其實這個target對象就是Handler對象。主線程默認執行了Looper.looper()方法,該方法從Looper的成員變量MessageQueue中取出Message,然后調用Message的target對象的handleMessage()方法。這樣就完成了整個消息機制。
2、事件分發機制
2.1 事件分發中的onTouch和onTouchEvent有什么區別,又該如何使用?
這兩個方法都是在View的dispatchTouchEvent中調用的,onTouch優先于onTouchEvent執行。如果在onTouch方法中通過返回true將事件消費掉,onTouchEvent將不會再執行。
另外需要注意的是,onTouch能夠得到執行需要兩個前提條件,第一mOnTouchListener的值不能為空,第二當前點擊的控件必須是enable的。因此如果你有一個控件是非enable的,那么給它注冊onTouch事件將永遠得不到執行。對于這一類控件,如果我們想要監聽它的touch事件,就必須通過在該控件中重寫onTouchEvent方法來實現。
2.2 請描述一下Android的事件分發機制
Android的事件分發機制主要是Touch事件分發,有兩個主角:ViewGroup和View。Activity的Touch事件事實上是調用它內部的ViewGroup的Touch事件,可以直接當成ViewGroup處理。
View在ViewGroup內,ViewGroup也可以在其他ViewGroup內,這時候把內部的ViewGroup當成View來分析。
先分析ViewGroup的處理流程:首先得有個結構模型概念:ViewGroup和View組成了一棵樹形結構,最頂層為Activity的ViewGroup,下面有若干的ViewGroup節點,每個節點之下又有若干的ViewGroup節點或者View節點,依次類推。如圖:
?
當一個Touch事件(觸摸事件為例)到達根節點,即Acitivty的ViewGroup時,它會依次下發,下發的過程是調用子View(ViewGroup)的dispatchTouchEvent方法實現的。簡單來說,就是ViewGroup遍歷它包含著的子View,調用每個View的dispatchTouchEvent方法,而當子View為ViewGroup時,又會通過調用ViwGroup的dispatchTouchEvent方法繼續調用其內部的View的dispatchTouchEvent方法。上述例子中的消息下發順序是這樣的:①-②-⑤-⑥-⑦-③-④。dispatchTouchEvent方法只負責事件的分發,它擁有boolean類型的返回值,當返回為true時,順序下發會中斷。在上述例子中如果⑤的dispatchTouchEvent返回結果為true,那么⑥-⑦-③-④將都接收不到本次Touch事件。
1.Touch事件分發中只有兩個主角:ViewGroup和View。ViewGroup包含onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent三個相關事件。View包含dispatchTouchEvent、onTouchEvent兩個相關事件。其中ViewGroup又繼承于View。
2.ViewGroup和View組成了一個樹狀結構,根節點為Activity內部包含的一個ViwGroup。
3.觸摸事件由Action_Down、Action_Move、Aciton_UP組成,其中一次完整的觸摸事件中,Down和Up都只有一個,Move有若干個,可以為0個。
4.當Acitivty接收到Touch事件時,將遍歷子View進行Down事件的分發。ViewGroup的遍歷可以看成是遞歸的。分發的目的是為了找到真正要處理本次完整觸摸事件的View,這個View會在onTouchuEvent結果返回true。
5.當某個子View返回true時,會中止Down事件的分發,同時在ViewGroup中記錄該子View。接下去的Move和Up事件將由該子View直接進行處理。由于子View是保存在ViewGroup中的,多層ViewGroup的節點結構時,上級ViewGroup保存的會是真實處理事件的View所在的ViewGroup對象:如ViewGroup0-ViewGroup1-TextView的結構中,TextView返回了true,它將被保存在ViewGroup1中,而ViewGroup1也會返回true,被保存在ViewGroup0中。當Move和UP事件來時,會先從ViewGroup0傳遞至ViewGroup1,再由ViewGroup1傳遞至TextView。
6.當ViewGroup中所有子View都不捕獲Down事件時,將觸發ViewGroup自身的onTouch事件。觸發的方式是調用super.dispatchTouchEvent函數,即父類View的dispatchTouchEvent方法。在所有子View都不處理的情況下,觸發Acitivity的onTouchEvent方法。
7.onInterceptTouchEvent有兩個作用:1.攔截Down事件的分發。2.中止Up和Move事件向目標View傳遞,使得目標View所在的ViewGroup捕獲Up和Move事件。
總結
- 上一篇: 优惠券秒杀的优化
- 下一篇: 面向对象--多态,接口