背景
最近在準(zhǔn)備面試,結(jié)合之前的工作經(jīng)驗(yàn)和近期在網(wǎng)上收集的一些面試資料,準(zhǔn)備將Android開發(fā)崗位的知識(shí)點(diǎn)做一個(gè)系統(tǒng)的梳理,整理成一個(gè)系列:Android應(yīng)用開發(fā)崗 面試匯總。本系列將分為以下幾個(gè)大模塊:
Java基礎(chǔ)篇、Java進(jìn)階篇、常見設(shè)計(jì)模式
Android基礎(chǔ)篇、Android進(jìn)階篇、性能優(yōu)化
網(wǎng)絡(luò)相關(guān)、數(shù)據(jù)結(jié)構(gòu)與算法
常用開源庫(kù)、Kotlin、Jetpack
注1:以上文章將陸續(xù)更新,直到我找到滿意的工作為止,有跳轉(zhuǎn)鏈接的表示已發(fā)表的文章。
注2:該系列屬于個(gè)人的總結(jié)和網(wǎng)上東拼西湊的結(jié)果,每個(gè)知識(shí)點(diǎn)的內(nèi)容并不一定完整,有不正確的地方歡迎批評(píng)指正。
注3:部分摘抄較多的段落或有注明出處。如有侵權(quán),請(qǐng)聯(lián)系本人進(jìn)行刪除。
Fragment相關(guān)
Activity與Fragment之間生命周期比較
Activity于Fragment的生命周期的不同主要表現(xiàn)在onCreate和onDestroy兩個(gè)生命周期方法中:
- 在Activity的onCreate方法回調(diào)時(shí),在回調(diào)期間,F(xiàn)ragment首先會(huì)執(zhí)行
onAttach()-->onCreate()-->onCreateView()-->onActivityCreated() - 在Activity的onDestroy()回調(diào)時(shí),在回調(diào)期間,F(xiàn)ragment首先會(huì)執(zhí)行onDestroyView()-->onDestroy()-->onDetach()
Fragment 如何實(shí)現(xiàn)類似 Activity 棧的壓棧和出棧效果?
Fragment 的事物管理器(FragmentManager )內(nèi)部維持了一個(gè)雙向鏈表結(jié)構(gòu),該結(jié)構(gòu)可以記錄我們每次 add 的Fragment 和 replace 的 Fragment,然后當(dāng)我們點(diǎn)擊 back 按鈕的時(shí)候會(huì)自動(dòng)幫我們實(shí)現(xiàn)退棧操作。
Fragment與Activity之間是如何傳值的?
- Activity向Fragment傳值:將要傳的值,放到bundle對(duì)象里,在Activity中創(chuàng)建該Fragment的對(duì)象fragment,
通過調(diào)用 fragment.setArguments()傳遞到fragment中;在該Fragment中通過調(diào)用getArguments()得到bundle對(duì)象,就能得到里面的值。 - Fragment向Activity傳值:在Activity中拿到Fragment的實(shí)例,通過回調(diào)的方式,在Activity中實(shí)現(xiàn)Fragment的回調(diào)。
- 通過在Activity中生成ViewModel,在Fragment使用該ViewModel
Fragment懶加載
為什么要實(shí)現(xiàn)懶加載?
在沒有添加懶加載之前,只要使用 add+show+hide 的方式控制并顯示 Fragment, 那么不管 Fragment 是否嵌套,在初始化后,如果只調(diào)用了add+show,同級(jí)下的 Fragment 的相關(guān)生命周期函數(shù)都會(huì)被調(diào)用。且調(diào)用的生命周期函數(shù)如下所示:(不管該Fragment是否用戶可見,都會(huì)調(diào)用以下函數(shù),常見在viewpager中)
onAttach -> onCreate -> onCreatedView -> onActivityCreated -> onStart -> onResume
因此,為了避免性能和流量的浪費(fèi),需要對(duì)當(dāng)前不可見的Fragment進(jìn)行懶加載處理,即Fragment真正被用戶看到時(shí)才開始去加載數(shù)據(jù)。
add+show+hide 模式下的老方案
場(chǎng)景:通過show+hide方法控制兩個(gè)fragment的顯示,A和B,當(dāng)從A切換到B時(shí),兩個(gè)Fragment的可見狀態(tài)發(fā)生了改變,F(xiàn)ragment會(huì)回調(diào)onHiddenChanged()方法,A中isHidden()方法的值為 true,B中為false。
方案:
- 只要通過 show+hide 方式控制 Fragment 的顯隱,那么在第一次初始化后,F(xiàn)ragment 任何的生命周期方法都不會(huì)調(diào)用,只有 onHiddenChanged 方法會(huì)被調(diào)用。
- 另,假如我們要在 add+show+hide 模式下控制 Fragment 的懶加載,我們只需要做這兩步:
因此
- 1、我們需要在 onResume() 函數(shù)中調(diào)用 isHidden() 函數(shù),來(lái)處理默認(rèn)顯示的 Fragment;
- 2、在 onHiddenChanged 函數(shù)中控制其他不可見的Fragment,即:
abstract class LazyFragment:Fragment(){
private var isLoaded = false //控制是否執(zhí)行懶加載
override fun onResume() {
super.onResume()
judgeLazyInit()
}
override fun onHiddenChanged(hidden: Boolean) {
super.onHiddenChanged(hidden)
judgeLazyInit()
}
private fun judgeLazyInit() {
if (!isLoaded && !isHidden()) {
lazyInit()
isLoaded = true
}
}
override fun onDestroyView() {
super.onDestroyView()
isLoaded = false
}
//懶加載方法
abstract fun lazyInit()
}
ViewPager+Fragment 模式下的老方案
ViewPager+Fragment 模式下的老方案
使用傳統(tǒng)方式處理 ViewPager 中 Fragment 的懶加載,我們需要控制 setUserVisibleHint(boolean isVisibleToUser) 函數(shù),該函數(shù)與之前我們介紹的 onHiddenChanged() 作用非常相似,都是通過傳入的參數(shù)值來(lái)判斷當(dāng)前 Fragment 是否對(duì)用戶可見,只是 onHiddenChanged() 是在 add+show+hide 模式下使用,而 setUserVisibleHint 是在 ViewPager+Fragment 模式下使用。public void setUserVisibleHint(boolean isVisibleToUser) {}
因此:
abstract class LazyFragment : Fragment() {
//是否執(zhí)行懶加載
private var isLoaded = false
//當(dāng)前Fragment是否對(duì)用戶可見
private var isVisibleToUser = false
/**
* 當(dāng)使用ViewPager+Fragment形式會(huì)調(diào)用該方法時(shí),setUserVisibleHint會(huì)優(yōu)先Fragment生命周期函數(shù)調(diào)用,
* 所以這個(gè)時(shí)候就,會(huì)導(dǎo)致在setUserVisibleHint方法執(zhí)行時(shí)就執(zhí)行了懶加載,
* 而不是在onResume方法實(shí)際調(diào)用的時(shí)候執(zhí)行懶加載。所以需要這個(gè)變量
*/
private var isCallResume = false
override fun onResume() {
super.onResume()
isCallResume = true
judgeLazyInit()
}
override fun onHiddenChanged(hidden: Boolean) {
super.onHiddenChanged(hidden)
isVisibleToUser = !hidden
judgeLazyInit()
}
private fun judgeLazyInit() {
if (!isLoaded && isVisibleToUser && isCallResume) {
lazyInit()
Log.d(TAG, "lazyInit:!!!!!!!")
isLoaded = true
}
}
//在Fragment銷毀View的時(shí)候,重置狀態(tài)
override fun onDestroyView() {
super.onDestroyView()
isLoaded = false
isVisibleToUser = false
isCallResume = false
}
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
super.setUserVisibleHint(isVisibleToUser)
this.isVisibleToUser = isVisibleToUser
judgeLazyInit()
}
abstract fun lazyInit()
}
Androidx 下的懶加載
雖然之前的方案就能解決輕松的解決 Fragment 的懶加載,但這套方案有一個(gè)最大的弊端,就是不可見的 Fragment 執(zhí)行了 onResume() 方法。onResume 方法設(shè)計(jì)的初衷,難道不是當(dāng)前 Fragment 可以和用戶進(jìn)行交互嗎?你他媽既不可見,又不能和用戶進(jìn)行交互,你執(zhí)行 onResume 方法干嘛?
基于此問題,Google 在 Androidx 在 FragmentTransaction 中增加了 setMaxLifecycle 方法來(lái)控制 Fragment 所能調(diào)用的最大的生命周期函數(shù)。即Fragment被用戶可見和可操作時(shí)才調(diào)用onResume方法:
add+show+hide 模式下的新方案
通過FragmentTransaction 設(shè)置setMaxLifecycle的值:setMaxLifecycle(Lifecycle.State.STARTED),可以使Fragment生命周期降級(jí)到onStart。
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
homefragment = new HomeFragment();
ft.add(R.id.fl_container, homefragment);
ft.setMaxLifecycle(homefragment, Lifecycle.State.STARTED);
ft.commit();
ViewPager+Fragment 模式下的老方案
該場(chǎng)景下,通過調(diào)用FragmentStatePagerAdapter中的setPrimaryItem(FragmentStatePagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT)
注:該方法最終也是調(diào)setMaxLifecycle()。
以上兩種方式只需要在onResume中增加是否已經(jīng)加載過數(shù)據(jù)的判斷,來(lái)決定是否需要執(zhí)行初次加載。
參考鏈接
Service相關(guān)
簡(jiǎn)介:
Service(服務(wù))是一個(gè)可以在后臺(tái)執(zhí)行長(zhǎng)時(shí)間運(yùn)行操作而沒有用戶界面的應(yīng)用組件。Service是運(yùn)行在主線程中的(跟Activity同一個(gè)線程),不能進(jìn)行耗時(shí)操作。我們一般都是在Service中創(chuàng)建一個(gè)新的線程來(lái)處理一些耗時(shí)工作,這樣就不會(huì)阻塞主線程。
啟動(dòng)Service的兩種方式
- startService方式啟動(dòng)
如果運(yùn)行在后臺(tái)的Service甚至不需要和UI(主)線程間進(jìn)行交互,這種情況下,一般是調(diào)用startService來(lái)啟動(dòng)Service。通過 startService啟動(dòng),Service 會(huì)經(jīng)歷 onCreate 到 onStartCommand,然后處于運(yùn)行狀態(tài),stopService的時(shí)候調(diào)用 onDestroy方法。 如果是調(diào)用者自己直接退出而沒有調(diào)用 stopService 的話,Service 會(huì)一直在后臺(tái)運(yùn)行。 - bindService方式啟動(dòng)
兩個(gè)不同進(jìn)程間通信 或者 某個(gè)應(yīng)用中Service方法的暴露出去(同個(gè)進(jìn)程間),一般是調(diào)用bindService來(lái)啟動(dòng)Service。通過 bindService啟動(dòng),Service 會(huì)運(yùn)行 onCreate,然后是調(diào)用 onBind, 這個(gè)時(shí)候調(diào)用者和 Service綁定在一起。調(diào)用者退出了,Srevice 就會(huì)調(diào)用 onUnbind->onDestroyed 方法。
所謂綁定在一起就共存亡了,調(diào)用者也可以通過調(diào)用 unbindService 方法來(lái)停止服務(wù)。
Service的生命周期

回調(diào)方法說(shuō)明
- onCreate:如果多次執(zhí)行了Context的startService方法啟動(dòng)Service,Service方法的onCreate方法只會(huì)在第一次創(chuàng)建Service的時(shí)候調(diào)用一次,以后均不會(huì)再次調(diào)用。我們可以在onCreate方法中完成一些Service初始化相關(guān)的操作
- onStartCommand:如果多次執(zhí)行了Context的startService方法,那么Service的onStartCommand方法也會(huì)相應(yīng)的多次調(diào)用。onStartCommand方法很重要,我們?cè)谠摲椒ㄖ懈鶕?jù)傳入的Intent參數(shù)進(jìn)行實(shí)際的操作,比如會(huì)在此處創(chuàng)建一個(gè)子線程用于下載數(shù)據(jù)或播放音樂等
- onBind:Service中的onBind方法是個(gè)抽象方法,所以Service類本身就是一個(gè)抽象類,也就是說(shuō)onBind方法必須要重寫,即使用不到。通過startService使用Service時(shí),我們?cè)谥貙憃nBind方法時(shí),只需要將其返回值設(shè)為null即可。onBind方法主要是用于給bindService方法調(diào)用Service時(shí)才使用到。
- onDestroy:Service銷毀時(shí)回調(diào)函數(shù)
常見面試題
1、什么是 IntentService?有何優(yōu)點(diǎn)?
- IntentService 是 Service 的子類,比普通的 Service 增加了額外的功能。
先看 Service 本身存在兩個(gè)問題:
- Service 不會(huì)專門啟動(dòng)一條單獨(dú)的進(jìn)程,Service 與它所在應(yīng)用位于同一個(gè)進(jìn)程中;
- Service 也不是專門一條新線程,因此不應(yīng)該在 Service 中直接處理耗時(shí)的任務(wù);
- IntentService 特征
1、會(huì)創(chuàng)建獨(dú)立的 worker 線程來(lái)處理所有的 Intent 請(qǐng)求;
2、會(huì)創(chuàng)建獨(dú)立的 worker 線程來(lái)處理 onHandleIntent()方法實(shí)現(xiàn)的代碼,無(wú)需處理多線程問題;
3、所有請(qǐng)求處理完成后,IntentService 會(huì)自動(dòng)停止,無(wú)需調(diào)用 stopSelf()方法停止 Service;
4、為Service 的 onBind()提供默認(rèn)實(shí)現(xiàn),返回 null;
5、為Service 的 onStartCommand 提供默認(rèn)實(shí)現(xiàn),將請(qǐng)求 Intent 添加到隊(duì)列中;
2、怎么保證應(yīng)用盡可能不被殺死
- 通過保證Service不被銷毀的方式(提高Service所在進(jìn)程的優(yōu)先級(jí))
- 手機(jī)廠商將應(yīng)用加入白名單
保證Service不被銷毀
- 1、在onStartCommand方法中將flag設(shè)置為START_STICKY,即return Service.START_STICKY;
- 2、在xml中設(shè)置了android:priority
//設(shè)置服務(wù)的優(yōu)先級(jí)為MAX_VALUE
<service android:name=".MyService"
android:priority="2147483647" >
</service>
- 3、在onStartCommand方法中設(shè)置為前臺(tái)進(jìn)程
通過設(shè)置一個(gè)狀態(tài)欄顯示來(lái)實(shí)現(xiàn)
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Notification notification = new Notification(R.mipmap.ic_launcher, "服務(wù)正在運(yùn)行",System.currentTimeMillis());
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,notificationIntent,0);
RemoteViews remoteView = new RemoteViews(this.getPackageName(),R.layout.notification);
remoteView.setImageViewResource(R.id.image, R.mipmap.ic_launcher);
remoteView.setTextViewText(R.id.text , "Hello,this message is in a custom expanded view");
notification.contentView = remoteView;
notification.contentIntent = pendingIntent;
startForeground(1, notification);
return Service.START_STICKY;
}
- 4、在onDestroy方法中重啟service
@Override
public void onDestroy() {
super.onDestroy();
startService(new Intent(this, MyService.class));
}
- 5、用AlarmManager.setRepeating(…)方法循環(huán)發(fā)送鬧鐘廣播,接收的時(shí)候調(diào)用service的onstart方法
Intent intent = new Intent(MainActivity.this,MyAlarmReciver.class);
PendingIntent sender = PendingIntent.getBroadcast( MainActivity.this, 0, intent, 0);
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(System.currentTimeMillis());
calendar.add(Calendar.SECOND, 1);
AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE);
//重復(fù)鬧鐘
/**
* @param triggerAtMillis t 鬧鐘的第一次執(zhí)行時(shí)間,以毫秒為單位
* @param intervalMillis 表示兩次鬧鐘執(zhí)行的間隔時(shí)間,也是以毫秒為單位
* @param operation 綁定了鬧鐘的執(zhí)行動(dòng)作,比如發(fā)送一個(gè)廣播、給出提示等等
*/
am.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), 2 * 1000, sender);
Broadcast Receiver相關(guān)
如何注冊(cè) BroadcastReceiver
在清單文件中注冊(cè)廣播接收者稱為靜態(tài)注冊(cè),在代碼中注冊(cè)稱為動(dòng)態(tài)注冊(cè)。
- 靜態(tài)注冊(cè)的廣播接收者只要 app 在系統(tǒng)中運(yùn)行則一直可以接收到廣播消息,
- 動(dòng)態(tài)注冊(cè)的廣播接收者當(dāng)注冊(cè)的 Activity 或者 Service 銷毀了那么就接收不到廣播了。
靜態(tài)注冊(cè):在清單文件中進(jìn)行如下配置
<receiver android:name=".BroadcastReceiver1" >
<intent-filter>
<action android:name="android.intent.action.CALL" > </action>
</intent-filter>
</receiver>
動(dòng)態(tài)注冊(cè):在代碼中進(jìn)行如下注冊(cè)
receiver = new BroadcastReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(CALL_ACTION);
context.registerReceiver(receiver, intentFilter);
Android 引入廣播機(jī)制的用意?
- 程序間互通消息(例如在自己的應(yīng)用程序內(nèi)監(jiān)聽系統(tǒng)來(lái)電)
- 效率上(參考 UDP 的廣播協(xié)議在局域網(wǎng)的方便性)
- 設(shè)計(jì)模式上(反轉(zhuǎn)控制的一種應(yīng)用,類似觀察者模式)
兩種注冊(cè)各有什么特點(diǎn)
靜態(tài)注冊(cè)
- 常駐,當(dāng)應(yīng)用程序關(guān)閉后如果有信息廣播來(lái),程序也會(huì)被系統(tǒng)調(diào)用,自己運(yùn)行。
- 無(wú)需擔(dān)憂廣播接收器是否被關(guān)閉,只要設(shè)備是開啟狀態(tài),廣播接收器就是打開著的。
動(dòng)態(tài)注冊(cè)
- 不常駐,廣播會(huì)跟隨程序的生命周期。
- 在 Android 的廣播機(jī)制中,動(dòng)態(tài)注冊(cè)優(yōu)先級(jí)高于靜態(tài)注冊(cè)優(yōu)先級(jí),因此在必要情況下,是需要?jiǎng)討B(tài)注冊(cè)廣播接收者的。
當(dāng)用來(lái)注冊(cè)的 Activity 關(guān)掉后,廣播也就失效了。
BrocastReceiver 的生命周期和注意事項(xiàng)
- BroadCastReceiver 的生命周期很短暫,當(dāng)接收到廣播的時(shí)候創(chuàng)建,當(dāng)onReceive()方法結(jié)束后銷毀
- 因?yàn)锽roadCastReceiver的聲明周期很短暫,所以不要在廣播接收器中去創(chuàng)建子線程做耗時(shí)的操作,因?yàn)閺V播接受者被銷毀后,這個(gè)子進(jìn)程就會(huì)成為空進(jìn)程,很容易被殺死
- BroadCastReceiver是運(yùn)行在主線程的,所以不能直接在BroadCastReceiver中去做耗時(shí)的操作,否則就會(huì)出現(xiàn)ANR異常
參考鏈接
動(dòng)畫相關(guān)
動(dòng)畫有哪幾類,各有什么特點(diǎn)?
三類:補(bǔ)間動(dòng)畫、幀動(dòng)畫、屬性動(dòng)畫。補(bǔ)間動(dòng)畫和Drawable動(dòng)畫可統(tǒng)稱為視圖動(dòng)畫。也可以將這三類動(dòng)畫都?xì)w為屬性動(dòng)畫。
補(bǔ)間動(dòng)畫(View Animation)
在xml中定義,有縮放、平移、漸變、旋轉(zhuǎn)等,可組合使用。動(dòng)畫集合 AnimationSet用來(lái)存放上面四種動(dòng)畫的集合
幀動(dòng)畫
也叫Frame動(dòng)畫,可以劃分到視圖動(dòng)畫的類別。專門用來(lái)一個(gè)一個(gè)的顯示Drawable的resources,就像放幻燈片一樣
屬性動(dòng)畫
通過改變視圖的屬性,來(lái)達(dá)到動(dòng)畫的效果的動(dòng)畫。
- 支持對(duì)所有View能更新的屬性的動(dòng)畫(需要屬性的setXxx()和getXxx())。
- 更改的是View實(shí)際的屬性,所以不會(huì)影響其在動(dòng)畫執(zhí)行后所在位置的正常使用。
參考鏈接
過渡動(dòng)畫和MotionLayout
- MotionLayout是ConstraintLayout的子類,所以它是一種布局類型,但是它能夠?yàn)椴季謱傩蕴砑觿?dòng)畫效果,是開發(fā)者實(shí)現(xiàn)動(dòng)畫效果的另一個(gè)新的選擇。
- 它可以直接通過觸摸屏幕來(lái)控制動(dòng)畫的運(yùn)行進(jìn)度。也就是說(shuō)MotionLayout會(huì)管理你的觸摸事件通過跟蹤手指的速度,并將其與系統(tǒng)中的視圖速度相匹配。從而可以自然地在兩者之間通過觸摸滑動(dòng)平穩(wěn)過渡。并且在動(dòng)畫里面加入了關(guān)鍵幀的概念,使得其自動(dòng)生成動(dòng)畫在運(yùn)行時(shí)某一階段會(huì)運(yùn)行到關(guān)鍵幀的狀態(tài)。
- 它具有ConstraintLayout的所有屬性。MotionLayout用來(lái)處理兩個(gè)ConstraintSet之間的切換,并在根據(jù)兩個(gè)ConstraintSet的CustomAttribute參數(shù)來(lái)自動(dòng)生成切換動(dòng)畫。
- MotionLayout支持在XML中完全描述一個(gè)復(fù)雜的動(dòng)畫,而不需要通過Java代碼來(lái)實(shí)現(xiàn)。
Window和WindowManager
類結(jié)構(gòu)圖:

SurfaceView相關(guān)
我們知道View是通過刷新來(lái)重繪視圖,系統(tǒng)通過發(fā)出VSSYNC信號(hào)來(lái)進(jìn)行屏幕的重繪,刷新的時(shí)間間隔是16ms,如果我們可以在16ms以內(nèi)將繪制工作完成,則沒有任何問題,如果我們繪制過程邏輯很復(fù)雜,并且我們的界面更新還非常頻繁,這時(shí)候就會(huì)造成界面的卡頓,影響用戶體驗(yàn),為此Android提供了SurfaceView來(lái)解決這一問題。如:繪制手寫簽名
View和SurfaceView的區(qū)別:
- 1 . View適用于主動(dòng)更新的情況,而SurfaceView則適用于被動(dòng)更新的情況,比如頻繁刷新界面。
- 2 . View在主線程中對(duì)頁(yè)面進(jìn)行刷新,而SurfaceView則開啟一個(gè)子線程來(lái)對(duì)頁(yè)面進(jìn)行刷新。
- 3 . View在繪圖時(shí)沒有實(shí)現(xiàn)雙緩沖機(jī)制,SurfaceView在底層機(jī)制中就實(shí)現(xiàn)了雙緩沖機(jī)制。
Android中的重要術(shù)語(yǔ)解釋
- 1.ActivityManagerServices,簡(jiǎn)稱AMS,服務(wù)端對(duì)象,負(fù)責(zé)系統(tǒng)中所有Activity的生命周期
- 2.ActivityThread,App的真正入口。當(dāng)開啟App之后,會(huì)調(diào)用main()開始運(yùn)行,開啟消息循環(huán)隊(duì)列,這就是傳說(shuō)中的UI線程或者叫主線程。與ActivityManagerServices配合,一起完成Activity的管理工作
- 3.ApplicationThread,用來(lái)實(shí)現(xiàn)ActivityManagerService與ActivityThread之間的交互。在ActivityManagerService需要管理相關(guān)Application中的Activity的生命周期時(shí),通過ApplicationThread的代理對(duì)象與ActivityThread通訊。
- 4.ApplicationThreadProxy,是ApplicationThread在服務(wù)器端的代理,負(fù)責(zé)和客戶端的ApplicationThread通訊。AMS就是通過該代理與ActivityThread進(jìn)行通信的。
- 5.Instrumentation,每一個(gè)應(yīng)用程序只有一個(gè)Instrumentation對(duì)象,每個(gè)Activity內(nèi)都有一個(gè)對(duì)該對(duì)象的引用。Instrumentation可以理解為應(yīng)用進(jìn)程的管家,ActivityThread要?jiǎng)?chuàng)建或暫停某個(gè)Activity時(shí),都需要通過Instrumentation來(lái)進(jìn)行具體的操作。
- 6.ActivityStack,Activity在AMS的棧管理,用來(lái)記錄已經(jīng)啟動(dòng)的Activity的先后關(guān)系,狀態(tài)信息等。通過ActivityStack決定是否需要啟動(dòng)新的進(jìn)程。
- 7.ActivityRecord,ActivityStack的管理對(duì)象,每個(gè)Activity在AMS對(duì)應(yīng)一個(gè)ActivityRecord,來(lái)記錄Activity的狀態(tài)以及其他的管理信息。其實(shí)就是服務(wù)器端的Activity對(duì)象的映像。
- 8.TaskRecord,AMS抽象出來(lái)的一個(gè)“任務(wù)”的概念,是記錄ActivityRecord的棧,一個(gè)“Task”包含若干個(gè)ActivityRecord。AMS用TaskRecord確保Activity啟動(dòng)和退出的順序。如果你清楚Activity的4種launchMode,那么對(duì)這個(gè)概念應(yīng)該不陌生。
其他面試題
invalidate、postInvalidate和requestLayout區(qū)別
- invalidate在主線程中調(diào)用,postInvalidate在子線程中調(diào)用,通過ViewRootImpl中的ViewRootHandler進(jìn)行線程調(diào)度,切到主線程,最終調(diào)的也是view.invalidate()
- invalidate() -> parent.invalidateChild() -> 層層找到parent,parent.invalidateChildInParent(),直到ViewRootImpl
- ViewRootImpl.invalidateChildInParent() -> invalidateRectOnScreen() -> scheduleTraversals() -> doTraversal() -> performTraversals() -> 根據(jù)條件去執(zhí)行performMeasure() / performLayout / performDraw
- 也就是說(shuō)任意一個(gè)子View調(diào)用invalidate,最終都會(huì)到ViewRootImpl的performTraversals
- invalidate是在view需要重繪的時(shí)候調(diào)用,requestLayout方法調(diào)用后只會(huì)執(zhí)行當(dāng)前view的measure和layout,而draw不一定被執(zhí)行,只有當(dāng)view的位置發(fā)生改變才會(huì)執(zhí)行draw方法
Intent 可以傳遞哪些數(shù)據(jù)類型
- Serializable:Java提供的接口,用來(lái)對(duì)數(shù)據(jù)進(jìn)行序列化
- Charsequence:Java提供的接口,實(shí)現(xiàn)了這個(gè)接口的類有:CharBuffer/String/StringBuffer/StringBuilder這個(gè)四個(gè)類
- Parcelable:Android提供了一種新的類型:Parcel。本類被用作封裝數(shù)據(jù)的容器,封裝后的數(shù)據(jù)可以通過Intent或IPC傳遞。 除了基本類型以外,只有實(shí)現(xiàn)了Parcelable接口的類才能被放入Parcel中。
- Bundle
- 基礎(chǔ)數(shù)據(jù)類型和String/StringBuffer/StringBuilder
Bitmap和Drawable的區(qū)別是什么?
- 可以簡(jiǎn)單地理解為 Bitmap 儲(chǔ)存的是像素信息,Drawable 儲(chǔ)存的是 對(duì) Canvas 的一系列操作。而 BitmapDrawable 儲(chǔ)存的是「把 Bitmap 渲染到 Canvas 上」這個(gè)操作。
- Bitmap:存儲(chǔ)圖像每個(gè)像素?cái)?shù)據(jù)的容器,是final修飾的類型,不允許被繼承
- Drawable:一種圖像繪制工具,調(diào)用canvas進(jìn)行繪制,儲(chǔ)存的是對(duì)Canva的一系列操作
它們之間可以互轉(zhuǎn)嗎?理論上是不可以的,但是可以通過自身生成出對(duì)方的類型來(lái)達(dá)到互轉(zhuǎn)的效果。
Webview與Js交互?
Webview調(diào)用Js
需要在主線程中調(diào)用
- 基本格式:
webView.loadUrl("javascript:methodName(parameterValues)"); - 調(diào)用Js無(wú)參無(wú)返回值函數(shù):
String call = "javascript:readyToGo()";
webView.loadUrl(call);
- 調(diào)用Js有參無(wú)返回值函數(shù)
String call = "javascript:alertMessage(\"" + "content" + "\")";
webView.loadUrl(call);
- 調(diào)用Js有參有返回值函數(shù)
String call = "javascript:alertMessage(\"" + "content" + "\")";
webView.evaluateJavascript(call, new ValueCallback<String>() {
@Override
public void onReceiveValue(String s) {
Log.d("findCar",s);
}
});
Js通過WebView調(diào)用Java代碼
從API19開始,Android提供了@JavascriptInterface對(duì)象注解的方式來(lái)建立起Javascript對(duì)象和Android原生對(duì)象的綁定,提供給JavScript調(diào)用的函數(shù)必須帶有@JavascriptInterface。
實(shí)際上,就是3個(gè)步驟:
1.Java被Js調(diào)用的方法上加@JavascriptInterface注解;
2.WebView注冊(cè)JavaScriptInterface;
3.js調(diào)用,如window.android.show("JavaScript called~!");
Js調(diào)用Android Toast方法
- 1.編寫Java原生方法并用使用@JavascriptInterface注解
@JavascriptInterface
public boolean show(String s){
Toast.makeText(getApplication(), s, Toast.LENGTH_SHORT).show();
return true;
}
- 2.注冊(cè)JavaScriptInterface
webView.addJavascriptInterface(this, "android");
addJavascriptInterface的作用是把this所代表的類映射為JavaScript中的android對(duì)象。
- 3.編寫JavaScript代碼
function toastClick(){
var str=window.android.show("JavaScript called~!");
console.log(str);
}
通過H5打開App的某個(gè)頁(yè)面?
在manifest文件中最開始啟動(dòng)的activity中添加:
<activity android:name=".activitys.MainActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="host"
android:pathPrefix="/pathPrefix"
android:scheme="scheme" />
</intent-filter>
</activity>
//注意host,pathPrefix,scheme都是自己自定義的,只要與h5頁(yè)面調(diào)用的一致即可,如下所示
如果要跳轉(zhuǎn)到指定的頁(yè)面,在MainActivity的onCreate()中添加:
Intent intent = getIntent();
Uri uri = intent.getData();
if (uri != null) {
String routeId = uri.getQueryParameter("pid");
Intent intent0 = new Intent(MainActivity.this, ZhidingActivity.class);
startActivity(intent0);
}
uri.getQueryParameter("pid");獲取h5頁(yè)面?zhèn)鬟f的參數(shù),如果沒有的話可以忽略
注意一點(diǎn),微信上對(duì)于app的喚醒有攔截,在瀏覽器中才可以起作用