2018年Android面試題含答案--適合中高級(下)

1、Activity生命週期?

onCreate() -> onStart() -> onResume() -> onPause() -> onStop() -> onDetroy()

2、Service生命週期?

service 啓動方式有兩種,一種是通過startService()方式進行啓動,另一種是通過bindService()方式進行啓動。不同的啓動方式他們的生命週期是不一樣.

通過startService()這種方式啓動的service,生命週期是這樣:調用startService() --> onCreate()--> onStartConmon()--> onDestroy()。這種方式啓動的話,需要注意一下幾個問題,第一:當我們通過startService被調用以後,多次在調用startService(),onCreate()方法也只會被調用一次,而onStartConmon()會被多次調用當我們調用stopService()的時候,onDestroy()就會被調用,從而銷燬服務。第二:當我們通過startService啓動時候,通過intent傳值,在onStartConmon()方法中獲取值的時候,一定要先判斷intent是否爲null。

通過bindService()方式進行綁定,這種方式綁定service,生命週期走法:bindService-->onCreate()-->onBind()-->unBind()-->onDestroy()  bingservice 這種方式進行啓動service好處是更加便利activity中操作service,比如加入service中有幾個方法,a,b ,如果要在activity中調用,在需要在activity獲取ServiceConnection對象,通過ServiceConnection來獲取service中內部類的類對象,然後通過這個類對象就可以調用類中的方法,當然這個類需要繼承Binder對象

3、Activity的啓動過程(不要回答生命週期)

app啓動的過程有兩種情況,第一種是從桌面launcher上點擊相應的應用圖標,第二種是在activity中通過調用startActivity來啓動一個新的activity。

我們創建一個新的項目,默認的根activity都是MainActivity,而所有的activity都是保存在堆棧中的,我們啓動一個新的activity就會放在上一個activity上面,而我們從桌面點擊應用圖標的時候,由於launcher本身也是一個應用,當我們點擊圖標的時候,系統就會調用startActivitySately(),一般情況下,我們所啓動的activity的相關信息都會保存在intent中,比如action,category等等。我們在安裝這個應用的時候,系統也會啓動一個PackaManagerService的管理服務,這個管理服務會對AndroidManifest.xml文件進行解析,從而得到應用程序中的相關信息,比如service,activity,Broadcast等等,然後獲得相關組件的信息。當我們點擊應用圖標的時候,就會調用startActivitySately()方法,而這個方法內部則是調用startActivty(),而startActivity()方法最終還是會調用startActivityForResult()這個方法。而在startActivityForResult()這個方法。因爲startActivityForResult()方法是有返回結果的,所以系統就直接給一個-1,就表示不需要結果返回了。而startActivityForResult()這個方法實際是通過Instrumentation類中的execStartActivity()方法來啓動activity,Instrumentation這個類主要作用就是監控程序和系統之間的交互。而在這個execStartActivity()方法中會獲取ActivityManagerService的代理對象,通過這個代理對象進行啓動activity。啓動會就會調用一個checkStartActivityResult()方法,如果說沒有在配置清單中配置有這個組件,就會在這個方法中拋出異常了。當然最後是調用的是Application.scheduleLaunchActivity()進行啓動activity,而這個方法中通過獲取得到一個ActivityClientRecord對象,而這個ActivityClientRecord通過handler來進行消息的發送,系統內部會將每一個activity組件使用ActivityClientRecord對象來進行描述,而ActivityClientRecord對象中保存有一個LoaderApk對象,通過這個對象調用handleLaunchActivity來啓動activity組件,而頁面的生命週期方法也就是在這個方法中進行調用。

4、Broadcast註冊方式與區別 

此處延伸:什麼情況下用動態註冊

Broadcast廣播,註冊方式主要有兩種.

第一種是靜態註冊,也可成爲常駐型廣播,這種廣播需要在Androidmanifest.xml中進行註冊,這中方式註冊的廣播,不受頁面生命週期的影響,即使退出了頁面,也可以收到廣播這種廣播一般用於想開機自啓動啊等等,由於這種註冊的方式的廣播是常駐型廣播,所以會佔用CPU的資源。

第二種是動態註冊,而動態註冊的話,是在代碼中註冊的,這種註冊方式也叫非常駐型廣播,收到生命週期的影響,退出頁面後,就不會收到廣播,我們通常運用在更新UI方面。這種註冊方式優先級較高。最後需要解綁,否會會內存泄露

廣播是分爲有序廣播和無序廣播。

5、HttpClient與HttpUrlConnection的區別 

此處延伸:Volley裏用的哪種請求方式(2.3前HttpClient,2.3後HttpUrlConnection)

首先HttpClient和HttpUrlConnection 這兩種方式都支持Https協議,都是以流的形式進行上傳或者下載數據,也可以說是以流的形式進行數據的傳輸,還有ipv6,以及連接池等功能。HttpClient這個擁有非常多的API,所以如果想要進行擴展的話,並且不破壞它的兼容性的話,很難進行擴展,也就是這個原因,Google在Android6.0的時候,直接就棄用了這個HttpClient.

而HttpUrlConnection相對來說就是比較輕量級了,API比較少,容易擴展,並且能夠滿足Android大部分的數據傳輸。比較經典的一個框架volley,在2.3版本以前都是使用HttpClient,在2.3以後就使用了HttpUrlConnection。

6、java虛擬機和Dalvik虛擬機的區別 

Java虛擬機:

1、java虛擬機基於棧。 基於棧的機器必須使用指令來載入和操作棧上數據,所需指令更多更多。

2、java虛擬機運行的是java字節碼。(java類會被編譯成一個或多個字節碼.class文件)

Dalvik虛擬機

  • 1、dalvik虛擬機是基於寄存器的

  • 2、Dalvik運行的是自定義的.dex字節碼格式。(java類被編譯成.class文件後,會通過一個dx工具將所有的.class文件轉換成一個.dex文件,然後dalvik虛擬機會從其中讀取指令和數據

  • 3、常量池已被修改爲只使用32位的索引,以 簡化解釋器。

  • 4、一個應用,一個虛擬機實例,一個進程(所有android應用的線程都是對應一個linux線程,都運行在自己的沙盒中,不同的應用在不同的進程中運行。每個android dalvik應用程序都被賦予了一個獨立的linux PID(app_*))

7、進程保活(不死進程)

  • 此處延伸:進程的優先級是什麼*

當前業界的Android進程保活手段主要分爲** 黑、白、灰 **三種,其大致的實現思路如下:

  • 黑色保活* :不同的app進程,用廣播相互喚醒(包括利用系統提供的廣播進行喚醒)

  • 白色保活* :啓動前臺Service

  • 灰色保活* :利用系統的漏洞啓動前臺Service

  • 黑色保活*

所謂黑色保活,就是利用不同的app進程使用廣播來進行相互喚醒。舉個3個比較常見的場景:

** 場景1** :開機,網絡切換、拍照、拍視頻時候,利用系統產生的廣播喚醒app

** 場景2** :接入第三方SDK也會喚醒相應的app進程,如微信sdk會喚醒微信,支付寶sdk會喚醒支付寶。由此發散開去,就會直接觸發了下面的 場景3

** 場景3** :假如你手機裏裝了支付寶、淘寶、天貓、UC等阿里系的app,那麼你打開任意一個阿里系的app後,有可能就順便把其他阿里系的app給喚醒了。(只是拿阿里打個比方,其實BAT系都差不多)

** 白色保活**

白色保活手段非常簡單,就是調用系統api啓動一個前臺的Service進程,這樣會在系統的通知欄生成一個Notification,用來讓用戶知道有這樣一個app在運行着,哪怕當前的app退到了後臺。如下方的LBE和QQ音樂這樣:

灰色保活

灰色保活,這種保活手段是應用範圍最廣泛。它是利用系統的漏洞來啓動一個前臺的Service進程,與普通的啓動方式區別在於,它不會在系統通知欄處出現一個Notification,看起來就如同運行着一個後臺Service進程一樣。這樣做帶來的好處就是,用戶無法察覺到你運行着一個前臺進程(因爲看不到Notification),但你的進程優先級又是高於普通後臺進程的。那麼如何利用系統的漏洞呢,大致的實現思路和代碼如下:

思路一:API < 18,啓動前臺Service時直接傳入new Notification();

思路二:API >= 18,同時啓動兩個id相同的前臺Service,然後再將後啓動的Service做stop處理

熟悉Android系統的童鞋都知道,系統出於體驗和性能上的考慮,app在退到後臺時系統並不會真正的kill掉這個進程,而是將其緩存起來。打開的應用越多,後臺緩存的進程也越多。在系統內存不足的情況下,系統開始依據自身的一套進程回收機制來判斷要kill掉哪些進程,以騰出內存來供給需要的app。這套殺進程回收內存的機制就叫 Low Memory Killer ,它是基於Linux內核的 OOM Killer(Out-Of-Memory killer)機制誕生。

  • 進程的重要性,劃分5級:-
  • 前臺進程 (Foreground process)

  • 可見進程 (Visible process)

  • 服務進程 (Service process)

  • 後臺進程 (Background process)

  • 空進程 (Empty process)

瞭解完 Low Memory Killer,再科普一下oom_adj。什麼是oom_adj?它是linux內核分配給每個系統進程的一個值,代表進程的優先級,進程回收機制就是根據這個優先級來決定是否進行回收。對於oom_adj的作用,你只需要記住以下幾點即可:

進程的oom_adj越大,表示此進程優先級越低,越容易被殺回收;越小,表示進程優先級越高,越不容易被殺回收

普通app進程的oom_adj>=0,系統進程的oom_adj纔可能<0

有些手機廠商把這些知名的app放入了自己的白名單中,保證了進程不死來提高用戶體驗(如微信、QQ、陌陌都在小米的白名單中)。如果從白名單中移除,他們終究還是和普通app一樣躲避不了被殺的命運,爲了儘量避免被殺,還是老老實實去做好優化工作吧。

所以,進程保活的根本方案終究還是回到了性能優化上,進程永生不死終究是個徹頭徹尾的僞命題!

8、講解一下Context 

Context是一個抽象基類。在翻譯爲上下文,也可以理解爲環境,是提供一些程序的運行環境基礎信息。Context下有兩個子類,ContextWrapper是上下文功能的封裝類,而ContextImpl則是上下文功能的實現類。而ContextWrapper又有三個直接的子類, ContextThemeWrapper、Service和Application。其中,ContextThemeWrapper是一個帶主題的封裝類,而它有一個直接子類就是Activity,所以Activity和Service以及Application的Context是不一樣的,只有Activity需要主題,Service不需要主題。Context一共有三種類型,分別是Application、Activity和Service。這三個類雖然分別各種承擔着不同的作用,但它們都屬於Context的一種,而它們具體Context的功能則是由ContextImpl類去實現的,因此在絕大多數場景下,Activity、Service和Application這三種類型的Context都是可以通用的。不過有幾種場景比較特殊,比如啓動Activity,還有彈出Dialog。出於安全原因的考慮,Android是不允許Activity或Dialog憑空出現的,一個Activity的啓動必須要建立在另一個Activity的基礎之上,也就是以此形成的返回棧。而Dialog則必須在一個Activity上面彈出(除非是System Alert類型的Dialog),因此在這種場景下,我們只能使用Activity類型的Context,否則將會出錯。

getApplicationContext()和getApplication()方法得到的對象都是同一個application對象,只是對象的類型不一樣。

Context數量 = Activity數量 + Service數量 + 1 (1爲Application)

9、理解Activity,View,Window三者關係

這個問題真的很不好回答。所以這裏先來個算是比較恰當的比喻來形容下它們的關係吧。Activity像一個工匠(控制單元),Window像窗戶(承載模型),View像窗花(顯示視圖)LayoutInflater像剪刀,Xml配置像窗花圖紙。

  • 1:Activity構造的時候會初始化一個Window,準確的說是PhoneWindow。

  • 2:這個PhoneWindow有一個“ViewRoot”,這個“ViewRoot”是一個View或者說ViewGroup,是最初始的根視圖。

  • 3:“ViewRoot”通過addView方法來一個個的添加View。比如TextView,Button等

  • 4:這些View的事件監聽,是由WindowManagerService來接受消息,並且回調Activity函數。比如onClickListener,onKeyDown等。

10、四種LaunchMode及其使用場景

此處延伸:棧(First In Last Out)與隊列(First In First Out)的區別

棧與隊列的區別:

1. 隊列先進先出,棧先進後出

2. 對插入和刪除操作的"限定"。 棧是限定只能在表的一端進行插入和刪除操作的線性表。 隊列是限定只能在表的一端進行插入和在另一端進行刪除操作的線性表。

3. 遍歷數據速度不同

standard 模式

這是默認模式,每次激活Activity時都會創建Activity實例,並放入任務棧中。使用場景:大多數Activity。

  • singleTop 模式

如果在任務的棧頂正好存在該Activity的實例,就重用該實例( 會調用實例的 onNewIntent() ),否則就會創建新的實例並放入棧頂,即使棧中已經存在該Activity的實例,只要不在棧頂,都會創建新的實例。使用場景如新聞類或者閱讀類App的內容頁面。

  • singleTask 模式

如果在棧中已經有該Activity的實例,就重用該實例(會調用實例的 onNewIntent() )。重用時,會讓該實例回到棧頂,因此在它上面的實例將會被移出棧。如果棧中不存在該實例,將會創建新的實例放入棧中。使用場景如瀏覽器的主界面。不管從多少個應用啓動瀏覽器,只會啓動主界面一次,其餘情況都會走onNewIntent,並且會清空主界面上面的其他頁面。

  • singleInstance 模式

在一個新棧中創建該Activity的實例,並讓多個應用共享該棧中的該Activity實例。一旦該模式的Activity實例已經存在於某個棧中,任何應用再激活該Activity時都會重用該棧中的實例( 會調用實例的 onNewIntent() )。其效果相當於多個應用共享一個應用,不管誰激活該 Activity 都會進入同一個應用中。使用場景如鬧鈴提醒,將鬧鈴提醒與鬧鈴設置分離。singleInstance不要用於中間頁面,如果用於中間頁面,跳轉會有問題,比如:A -> B (singleInstance) -> C,完全退出後,在此啓動,首先打開的是B。

11、View的繪製流程

自定義控件

1、組合控件。這種自定義控件不需要我們自己繪製,而是使用原生控件組合成的新控件。如標題欄。

2、繼承原有的控件。這種自定義控件在原生控件提供的方法外,可以自己添加一些方法。如製作圓角,圓形圖片。

3、完全自定義控件:這個View上所展現的內容全部都是我們自己繪製出來的。比如說製作水波紋進度條。

View的繪製流程:OnMeasure()——>OnLayout()——>OnDraw()

第一步:OnMeasure():測量視圖大小。從頂層父View到子View遞歸調用measure方法,measure方法又回調OnMeasure。

第二步:OnLayout():確定View位置,進行頁面佈局。從頂層父View向子View的遞歸調用view.layout方法的過程,即父View根據上一步measure子View所得到的佈局大小和佈局參數,將子View放在合適的位置上。

第三步:OnDraw():繪製視圖。ViewRoot創建一個Canvas對象,然後調用OnDraw()。六個步驟:①、繪製視圖的背景;②、保存畫布的圖層(Layer);③、繪製View的內容;④、繪製View子視圖,如果沒有就不用;

⑤、還原圖層(Layer);⑥、繪製滾動條。

12、View,ViewGroup事件分發

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事件。

13、保存Activity狀態

onSaveInstanceState(Bundle)會在activity轉入後臺狀態之前被調用,也就是onStop()方法之前,onPause方法之後被調用;

14、Android中的幾種動畫

幀動畫:指通過指定每一幀的圖片和播放時間,有序的進行播放而形成動畫效果,比如想聽的律動條。

補間動畫:指通過指定View的初始狀態、變化時間、方式,通過一系列的算法去進行圖形變換,從而形成動畫效果,主要有Alpha、Scale、Translate、Rotate四種效果。注意:只是在視圖層實現了動畫效果,並沒有真正改變View的屬性,比如滑動列表,改變標題欄的透明度。

屬性動畫:在Android3.0的時候才支持,通過不斷的改變View的屬性,不斷的重繪而形成動畫效果。相比於視圖動畫,View的屬性是真正改變了。比如view的旋轉,放大,縮小。

15、Android中跨進程通訊的幾種方式

Android 跨進程通信,像intent,contentProvider,廣播,service都可以跨進程通信。

intent:這種跨進程方式並不是訪問內存的形式,它需要傳遞一個uri,比如說打電話。

contentProvider:這種形式,是使用數據共享的形式進行數據共享。

service:遠程服務,aidl

廣播

16、AIDL理解

此處延伸:簡述Binder

AIDL: 每一個進程都有自己的Dalvik VM實例,都有自己的一塊獨立的內存,都在自己的內存上存儲自己的數據,執行着自己的操作,都在自己的那片狹小的空間裏過完自己的一生。而aidl就類似與兩個進程之間的橋樑,使得兩個進程之間可以進行數據的傳輸,跨進程通信有多種選擇,比如 BroadcastReceiver , Messenger 等,但是 BroadcastReceiver 佔用的系統資源比較多,如果是頻繁的跨進程通信的話顯然是不可取的;Messenger 進行跨進程通信時請求隊列是同步進行的,無法併發執行。

Binde機制簡單理解:

在Android系統的Binder機制中,是有Client,Service,ServiceManager,Binder驅動程序組成的,其中Client,service,Service Manager運行在用戶空間,Binder驅動程序是運行在內核空間的。而Binder就是把這4種組件粘合在一塊的粘合劑,其中核心的組件就是Binder驅動程序,Service Manager提供輔助管理的功能,而Client和Service正是在Binder驅動程序和Service Manager提供的基礎設施上實現C/S 之間的通信。其中Binder驅動程序提供設備文件/dev/binder與用戶控件進行交互,

Client、Service,Service Manager通過open和ioctl文件操作相應的方法與Binder驅動程序進行通信。而Client和Service之間的進程間通信是通過Binder驅動程序間接實現的。而Binder Manager是一個守護進程,用來管理Service,並向Client提供查詢Service接口的能力。

17、Handler的原理

Android中主線程是不能進行耗時操作的,子線程是不能進行更新UI的。所以就有了handler,它的作用就是實現線程之間的通信。

handler整個流程中,主要有四個對象,handler,Message,MessageQueue,Looper。當應用創建的時候,就會在主線程中創建handler對象,

我們通過要傳送的消息保存到Message中,handler通過調用sendMessage方法將Message發送到MessageQueue中,Looper對象就會不斷的調用loop()方法

不斷的從MessageQueue中取出Message交給handler進行處理。從而實現線程之間的通信。

18、Binder機制原理

在Android系統的Binder機制中,是有Client,Service,ServiceManager,Binder驅動程序組成的,其中Client,service,Service Manager運行在用戶空間,Binder驅動程序是運行在內核空間的。而Binder就是把這4種組件粘合在一塊的粘合劑,其中核心的組件就是Binder驅動程序,Service Manager提供輔助管理的功能,而Client和Service正是在Binder驅動程序和Service Manager提供的基礎設施上實現C/S 之間的通信。其中Binder驅動程序提供設備文件/dev/binder與用戶控件進行交互,Client、Service,Service Manager通過open和ioctl文件操作相應的方法與Binder驅動程序進行通信。而Client和Service之間的進程間通信是通過Binder驅動程序間接實現的。而Binder Manager是一個守護進程,用來管理Service,並向Client提供查詢Service接口的能力。

19、熱修復的原理

我們知道Java虛擬機 —— JVM 是加載類的class文件的,而Android虛擬機——Dalvik/ART VM 是加載類的dex文件,

而他們加載類的時候都需要ClassLoader,ClassLoader有一個子類BaseDexClassLoader,而BaseDexClassLoader下有一個

數組——DexPathList,是用來存放dex文件,當BaseDexClassLoader通過調用findClass方法時,實際上就是遍歷數組,

找到相應的dex文件,找到,則直接將它return。而熱修復的解決方法就是將新的dex添加到該集合中,並且是在舊的dex的前面,

所以就會優先被取出來並且return返回。

20、Android內存泄露及管理

  • (1)內存溢出(OOM)和內存泄露(對象無法被回收)的區別。 

  • (2)引起內存泄露的原因

(3) 內存泄露檢測工具 ------>LeakCanary

內存溢出 out of memory:是指程序在申請內存時,沒有足夠的內存空間供其使用,出現out of memory;比如申請了一個integer,但給它存了long才能存下的數,那就是內存溢出。內存溢出通俗的講就是內存不夠用。

內存泄露 memory leak:是指程序在申請內存後,無法釋放已申請的內存空間,一次內存泄露危害可以忽略,但內存泄露堆積後果很嚴重,無論多少內存,遲早會被佔光

內存泄露原因:

一、Handler 引起的內存泄漏。

解決:將Handler聲明爲靜態內部類,就不會持有外部類SecondActivity的引用,其生命週期就和外部類無關,

如果Handler裏面需要context的話,可以通過弱引用方式引用外部類

二、單例模式引起的內存泄漏。

解決:Context是ApplicationContext,由於ApplicationContext的生命週期是和app一致的,不會導致內存泄漏

三、非靜態內部類創建靜態實例引起的內存泄漏。

解決:把內部類修改爲靜態的就可以避免內存泄漏了

四、非靜態匿名內部類引起的內存泄漏。

解決:將匿名內部類設置爲靜態的。

五、註冊/反註冊未成對使用引起的內存泄漏。

註冊廣播接受器、EventBus等,記得解綁。

六、資源對象沒有關閉引起的內存泄漏。

在這些資源不使用的時候,記得調用相應的類似close()、destroy()、recycler()、release()等方法釋放。

七、集合對象沒有及時清理引起的內存泄漏。

通常會把一些對象裝入到集合中,當不使用的時候一定要記得及時清理集合,讓相關對象不再被引用。

21、Fragment與Fragment、Activity通信的方式

  • 1.直接在一個Fragment中調用另外一個Fragment中的方法

  • 2.使用接口回調

  • 3.使用廣播

  • 4.Fragment直接調用Activity中的public方法

22、Android UI適配

字體使用sp,使用dp,多使用match_parent,wrap_content,weight

圖片資源,不同圖片的的分辨率,放在相應的文件夾下可使用百分比代替。

23、app優化

app優化:(工具:Hierarchy Viewer 分析佈局  工具:TraceView 測試分析耗時的)

  • App啓動優化

  • 佈局優化

  • 響應優化

  • 內存優化

  • 電池使用優化

  • 網絡優化

App啓動優化(針對冷啓動)

App啓動的方式有三種:

冷啓動:App沒有啓動過或App進程被killed, 系統中不存在該App進程, 此時啓動App即爲冷啓動。

熱啓動:熱啓動意味着你的App進程只是處於後臺, 系統只是將其從後臺帶到前臺, 展示給用戶。

介於冷啓動和熱啓動之間, 一般來說在以下兩種情況下發生:

  • (1)用戶back退出了App, 然後又啓動. App進程可能還在運行, 但是activity需要重建。

  • (2)用戶退出App後, 系統可能由於內存原因將App殺死, 進程和activity都需要重啓, 但是可以在onCreate中將被動殺死鎖保存的狀態(saved instance state)恢復。

優化:

Application的onCreate(特別是第三方SDK初始化),首屏Activity的渲染都不要進行耗時操作,如果有,就可以放到子線程或者IntentService中

** 佈局優化**

儘量不要過於複雜的嵌套。可以使用<include>,<merge>,<ViewStub>

響應優化

Android系統每隔16ms會發出VSYNC信號重繪我們的界面(Activity)。

頁面卡頓的原因:

  • (1)過於複雜的佈局.

  • (2)UI線程的複雜運算

  • (3)頻繁的GC,導致頻繁GC有兩個原因:1、內存抖動, 即大量的對象被創建又在短時間內馬上被釋放.2、瞬間產生大量的對象會嚴重佔用內存區域。

內存優化:參考內存泄露和內存溢出部分

電池使用優化(使用工具:Batterystats & bugreport)

  • (1)優化網絡請求

  • (2)定位中使用GPS, 請記得及時關閉

網絡優化(網絡連接對用戶的影響:流量,電量,用戶等待)可在Android studio下方logcat旁邊那個工具Network Monitor檢測

  • API設計:App與Server之間的API設計要考慮網絡請求的頻次, 資源的狀態等. 以便App可以以較少的請求來完成業務需求和界面的展示.

  • Gzip壓縮:使用Gzip來壓縮request和response, 減少傳輸數據量, 從而減少流量消耗.

  • 圖片的Size:可以在獲取圖片時告知服務器需要的圖片的寬高, 以便服務器給出合適的圖片, 避免浪費.

  • 網絡緩存:適當的緩存, 既可以讓我們的應用看起來更快, 也能避免一些不必要的流量消耗.

24、圖片優化

  • (1)對圖片本身進行操作。儘量不要使用setImageBitmap、setImageResource、BitmapFactory.decodeResource來設置一張大圖,因爲這些方法在完成decode後,

最終都是通過java層的createBitmap來完成的,需要消耗更多內存.

  • (2)圖片進行縮放的比例,SDK中建議其值是2的指數值,值越大會導致圖片不清晰。

  • (3)不用的圖片記得調用圖片的recycle()方法

25、HybridApp WebView和JS交互

Android與JS通過WebView互相調用方法,實際上是:

Android去調用JS的代碼

  • 1. 通過WebView的loadUrl(),使用該方法比較簡潔,方便。但是效率比較低,獲取返回值比較困難。

  • 2. 通過WebView的evaluateJavascript(),該方法效率高,但是4.4以上的版本才支持,4.4以下版本不支持。所以建議兩者混合使用。

JS去調用Android的代碼

1. 通過WebView的addJavascriptInterface()進行對象映射 ,該方法使用簡單,僅將Android對象和JS對象映射即可,但是存在比較大的漏洞。

漏洞產生原因是:當JS拿到Android這個對象後,就可以調用這個Android對象中所有的方法,包括系統類(java.lang.Runtime 類),從而進行任意代碼執行。

解決方式:

  • (1)Google 在Android 4.2 版本中規定對被調用的函數以 @JavascriptInterface進行註解從而避免漏洞攻擊。

  • (2)在Android 4.2版本之前採用攔截prompt()進行漏洞修復。

2. 通過 WebViewClient 的shouldOverrideUrlLoading ()方法回調攔截 url 。這種方式的優點:不存在方式1的漏洞;缺點:JS獲取Android方法的返回值複雜。(ios主要用的是這個方式)

  • (1)Android通過 WebViewClient 的回調方法shouldOverrideUrlLoading ()攔截 url

  • (2)解析該 url 的協議

  • (3)如果檢測到是預先約定好的協議,就調用相應方法

3. 通過 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()方法回調攔截JS對話框alert()、confirm()、prompt() 消息

這種方式的優點:不存在方式1的漏洞;缺點:JS獲取Android方法的返回值複雜。

26、JAVA GC原理

垃圾收集算法的核心思想是:對虛擬機可用內存空間,即堆空間中的對象進行識別,如果對象正在被引用,那麼稱其爲存活對象

,反之,如果對象不再被引用,則爲垃圾對象,可以回收其佔據的空間,用於再分配。垃圾收集算法的選擇和垃圾收集系統參數的合理調節直接影響着系統性能。

27、ANR

ANR全名Application Not Responding, 也就是"應用無響應". 當操作在一段時間內系統無法處理時, 系統層面會彈出上圖那樣的ANR對話框.

產生原因

  • (1)5s內無法響應用戶輸入事件(例如鍵盤輸入, 觸摸屏幕等).

  • (2)BroadcastReceiver在10s內無法結束

  • (3)Service 20s內無法結束(低概率)

解決方式:

  • (1)不要在主線程中做耗時的操作,而應放在子線程中來實現。如onCreate()和onResume()裏儘可能少的去做創建操作。

  • (2)應用程序應該避免在BroadcastReceiver裏做耗時的操作或計算。

  • (3)避免在Intent Receiver裏啓動一個Activity,因爲它會創建一個新的畫面,並從當前用戶正在運行的程序上搶奪焦點。

  • (4)service是運行在主線程的,所以在service中做耗時操作,必須要放在子線程中。

28、設計模式

此處延伸:Double Check的寫法被要求寫出來。

單例模式:分爲惡漢式和懶漢式

餓漢式:

public class Singleton 

{ 

    private static Singleton instance = new Singleton(); 

    public static Singleton getInstance() 

    { 

        return instance ; 

    } 

}

懶漢式:

public class Singleton02 

{ 

    private static Singleton02 instance; 

    public static Singleton02 getInstance() 

    { 

        if (instance == null) 

        { 

            synchronized (Singleton02.class) 

            { 

                if (instance == null) 

                { 

                    instance = new Singleton02(); 

                } 

            } 

        } 

        return instance; 

    } 

}

29、RxJava

30、MVP,MVC,MVVM

此處延伸:手寫mvp例子,與mvc之間的區別,mvp的優勢

MVP模式,對應着Model--業務邏輯和實體模型,view--對應着activity,負責View的繪製以及與用戶交互,Presenter--負責View和Model之間的交互,MVP模式是在MVC模式的基礎上,將Model與View徹底分離使得項目的耦合性更低,在Mvc中項目中的activity對應着mvc中的C--Controllor,而項目中的邏輯處理都是在這個C中處理,同時View與Model之間的交互,也是也就是說,mvc中所有的邏輯交互和用戶交互,都是放在Controllor中,也就是activity中。View和model是可以直接通信的。而MVP模式則是分離的更加徹底,分工更加明確Model--業務邏輯和實體模型,view--負責與用戶交互,Presenter 負責完成View於Model間的交互,MVP和MVC最大的區別是MVC中是允許Model和View進行交互的,而MVP中很明顯,Model與View之間的交互由Presenter完成。還有一點就是Presenter與View之間的交互是通過接口的

31、手寫算法(選擇冒泡必須要會)

32、JNI 

  • (1)安裝和下載Cygwin,下載 Android NDK

  • (2)在ndk項目中JNI接口的設計

  • (3)使用C/C++實現本地方法

  • (4)JNI生成動態鏈接庫.so文件

  • (5)將動態鏈接庫複製到java工程,在java工程中調用,運行java工程即可

33、RecyclerView和ListView的區別

RecyclerView可以完成ListView,GridView的效果,還可以完成瀑布流的效果。同時還可以設置列表的滾動方向(垂直或者水平);

RecyclerView中view的複用不需要開發者自己寫代碼,系統已經幫封裝完成了。

RecyclerView可以進行局部刷新。

RecyclerView提供了API來實現item的動畫效果。

在性能上:

如果需要頻繁的刷新數據,需要添加動畫,則RecyclerView有較大的優勢。

如果只是作爲列表展示,則兩者區別並不是很大。

34、Universal-ImageLoader,Picasso,Fresco,Glide對比

Fresco 是 Facebook 推出的開源圖片緩存工具,主要特點包括:兩個內存緩存加上 Native 緩存構成了三級緩存,

優點

  • 1. 圖片存儲在安卓系統的匿名共享內存, 而不是虛擬機的堆內存中, 圖片的中間緩衝數據也存放在本地堆內存, 所以, 應用程序有更多的內存使用, 不會因爲圖片加載而導致oom, 同時也減少垃圾回收器頻繁調用回收 Bitmap 導致的界面卡頓, 性能更高。

  • 2. 漸進式加載 JPEG 圖片, 支持圖片從模糊到清晰加載。

  • 3. 圖片可以以任意的中心點顯示在 ImageView, 而不僅僅是圖片的中心。

  • 4. JPEG 圖片改變大小也是在 native 進行的, 不是在虛擬機的堆內存, 同樣減少 OOM。

  • 5. 很好的支持 GIF 圖片的顯示。

缺點:

  • 1. 框架較大, 影響 Apk 體積

  • 2. 使用較繁瑣

Universal-ImageLoader:(估計由於HttpClient被Google放棄,作者就放棄維護這個框架)

優點:

  • 1.支持下載進度監聽

  • 2.可以在 View 滾動中暫停圖片加載,通過 PauseOnScrollListener 接口可以在 View 滾動中暫停圖片加載。

  • 3.默認實現多種內存緩存算法 這幾個圖片緩存都可以配置緩存算法,不過 ImageLoader 默認實現了較多緩存算法,如 Size 最大先刪除、使用最少先刪除、最近最少使用、先進先刪除、時間最長先刪除等。

  • 4.支持本地緩存文件名規則定義

Picasso 優點

  • 1. 自帶統計監控功能。支持圖片緩存使用的監控,包括緩存命中率、已使用內存大小、節省的流量等。

  • 2.支持優先級處理。每次任務調度前會選擇優先級高的任務,比如 App 頁面中 Banner 的優先級高於 Icon 時就很適用。

  • 3.支持延遲到圖片尺寸計算完成加載

  • 4.支持飛行模式、併發線程數根據網絡類型而變。 手機切換到飛行模式或網絡類型變換時會自動調整線程池最大併發數,比如 wifi 最大併發爲 4,4g 爲 3,3g 爲 2。  這裏 Picasso 根據網絡類型來決定最大併發數,而不是 CPU 核數。

  • 5.“無”本地緩存。無”本地緩存,不是說沒有本地緩存,而是 Picasso 自己沒有實現,交給了 Square 的另外一個網絡庫 okhttp 去實現,這樣的好處是可以通過請求 Response Header 中的 Cache-Control 及 Expired 控制圖片的過期時間。

 Glide 優點

  • 1. 不僅僅可以進行圖片緩存還可以緩存媒體文件。Glide 不僅是一個圖片緩存,它支持 Gif、WebP、縮略圖。甚至是 Video,所以更該當做一個媒體緩存。

  • 2. 支持優先級處理。

  • 3. 與 Activity/Fragment 生命週期一致,支持 trimMemory。Glide 對每個 context 都保持一個 RequestManager,通過 FragmentTransaction 保持與 Activity/Fragment 生命週期一致,並且有對應的 trimMemory 接口實現可供調用。

  • 4. 支持 okhttp、Volley。Glide 默認通過 UrlConnection 獲取數據,可以配合 okhttp 或是 Volley 使用。實際 ImageLoader、Picasso 也都支持 okhttp、Volley。

  • 5. 內存友好。Glide 的內存緩存有個 active 的設計,從內存緩存中取數據時,不像一般的實現用 get,而是用 remove,再將這個緩存數據放到一個 value 爲軟引用的 activeResources map 中,並計數引用數,在圖片加載完成後進行判斷,如果引用計數爲空則回收掉。內存緩存更小圖片,Glide 以 url、view_width、view_height、屏幕的分辨率等做爲聯合 key,將處理後的圖片緩存在內存緩存中,而不是原始圖片以節省大小與 Activity/Fragment 生命週期一致,支持 trimMemory。圖片默認使用默認 RGB_565 而不是 ARGB_888,雖然清晰度差些,但圖片更小,也可配置到 ARGB_888。

  • 6.Glide 可以通過 signature 或不使用本地緩存支持 url 過期

42、Xutils, OKhttp, Volley, Retrofit對比

Xutils這個框架非常全面,可以進行網絡請求,可以進行圖片加載處理,可以數據儲存,還可以對view進行註解,使用這個框架非常方便,但是缺點也是非常明顯的,使用這個項目,會導致項目對這個框架依賴非常的嚴重,一旦這個框架出現問題,那麼對項目來說影響非常大的。、

OKhttp:Android開發中是可以直接使用現成的api進行網絡請求的。就是使用HttpClient,HttpUrlConnection進行操作。okhttp針對Java和Android程序,封裝的一個高性能的http請求庫,支持同步,異步,而且okhttp又封裝了線程池,封裝了數據轉換,封裝了參數的使用,錯誤處理等。API使用起來更加的方便。但是我們在項目中使用的時候仍然需要自己在做一層封裝,這樣才能使用的更加的順手。

**Volley:**Volley是Google官方出的一套小而巧的異步請求庫,該框架封裝的擴展性很強,支持HttpClient、HttpUrlConnection, 甚至支持OkHttp,而且Volley裏面也封裝了ImageLoader,所以如果你願意你甚至不需要使用圖片加載框架,不過這塊功能沒有一些專門的圖片加載框架強大,對於簡單的需求可以使用,稍複雜點的需求還是需要用到專門的圖片加載框架。Volley也有缺陷,比如不支持post大數據,所以不適合上傳文件。不過Volley設計的初衷本身也就是爲頻繁的、數據量小的網絡請求而生。

**Retrofit:**Retrofit是Square公司出品的默認基於OkHttp封裝的一套RESTful網絡請求框架,RESTful是目前流行的一套api設計的風格, 並不是標準。Retrofit的封裝可以說是很強大,裏面涉及到一堆的設計模式,可以通過註解直接配置請求,可以使用不同的http客戶端,雖然默認是用http ,可以使用不同Json Converter 來序列化數據,同時提供對RxJava的支持,使用Retrofit + OkHttp + RxJava + Dagger2 可以說是目前比較潮的一套框架,但是需要有比較高的門檻。

Volley VS OkHttp

Volley的優勢在於封裝的更好,而使用OkHttp你需要有足夠的能力再進行一次封裝。而OkHttp的優勢在於性能更高,因爲 OkHttp基於NIO和Okio ,所以性能上要比 Volley更快。IO 和 NIO這兩個都是Java中的概念,如果我從硬盤讀取數據,第一種方式就是程序一直等,數據讀完後才能繼續操作這種是最簡單的也叫阻塞式IO,還有一種是你讀你的,程序接着往下執行,等數據處理完你再來通知我,然後再處理回調。而第二種就是 NIO 的方式,非阻塞式, 所以NIO當然要比IO的性能要好了,而 Okio是 Square 公司基於IO和NIO基礎上做的一個更簡單、高效處理數據流的一個庫。理論上如果Volley和OkHttp對比的話,更傾向於使用 Volley,因爲Volley內部同樣支持使用OkHttp,這點OkHttp的性能優勢就沒了,  而且 Volley 本身封裝的也更易用,擴展性更好些。

OkHttp VS Retrofit

毫無疑問,Retrofit 默認是基於 OkHttp 而做的封裝,這點來說沒有可比性,肯定首選 Retrofit。

Volley VS Retrofit

這兩個庫都做了不錯的封裝,但Retrofit解耦的更徹底,尤其Retrofit2.0出來,Jake對之前1.0設計不合理的地方做了大量重構, 職責更細分,而且Retrofit默認使用OkHttp,性能上也要比Volley佔優勢,再有如果你的項目如果採用了RxJava ,那更該使用  Retrofit 。所以這兩個庫相比,Retrofit更有優勢,在能掌握兩個框架的前提下該優先使用 Retrofit。但是Retrofit門檻要比Volley稍高些,要理解他的原理,各種用法,想徹底搞明白還是需要花些功夫的,如果你對它一知半解,那還是建議在商業項目使用Volley吧。

Java

1、線程中sleep和wait的區別

  • (1)這兩個方法來自不同的類,sleep是來自Thread,wait是來自Object;

  • (2)sleep方法沒有釋放鎖,而wait方法釋放了鎖。

  • (3)wait,notify,notifyAll只能在同步控制方法或者同步控制塊裏面使用,而sleep可以在任何地方使用。

2、Thread中的start()和run()方法有什麼區別

start()方法是用來啓動新創建的線程,而start()內部調用了run()方法,這和直接調用run()方法是不一樣的,如果直接調用run()方法,

則和普通的方法沒有什麼區別。

3、關鍵字final和static是怎麼使用的。

final:
  • 1、final變量即爲常量,只能賦值一次。

  • 2、final方法不能被子類重寫。

  • 3、final類不能被繼承。

static:
  • 1、static變量:對於靜態變量在內存中只有一個拷貝(節省內存),JVM只爲靜態分配一次內存,

在加載類的過程中完成靜態變量的內存分配,可用類名直接訪問(方便),當然也可以通過對象來訪問(但是這是不推薦的)。

  • 2、static代碼塊

 static代碼塊是類加載時,初始化自動執行的。

  • 3、static方法

static方法可以直接通過類名調用,任何的實例也都可以調用,因此static方法中不能用this和super關鍵字,

不能直接訪問所屬類的實例變量和實例方法(就是不帶static的成員變量和成員成員方法),只能訪問所屬類的靜態成員變量和成員方法。

  • 4、String,StringBuffer,StringBuilder區別

  • 1、三者在執行速度上:StringBuilder > StringBuffer > String (由於String是常量,不可改變,拼接時會重新創建新的對象)。

  • 2、StringBuffer是線程安全的,StringBuilder是線程不安全的。(由於StringBuffer有緩衝區)

5、Java中重載和重寫的區別:

1、重載:一個類中可以有多個相同方法名的,但是參數類型和個數都不一樣。這是重載。

2、重寫:子類繼承父類,則子類可以通過實現父類中的方法,從而新的方法把父類舊的方法覆蓋。

6、Http https區別

此處延伸:https的實現原理

  • 1、https協議需要到ca申請證書,一般免費證書較少,因而需要一定費用。

  • 2、http是超文本傳輸協議,信息是明文傳輸,https則是具有安全性的ssl加密傳輸協議。

  • 3、http和https使用的是完全不同的連接方式,用的端口也不一樣,前者是80,後者是443。

  • 4、http的連接很簡單,是無狀態的;HTTPS協議是由SSL+HTTP協議構建的可進行加密傳輸、身份認證的網絡協議,比http協議安全。

https實現原理:

  • (1)客戶使用https的URL訪問Web服務器,要求與Web服務器建立SSL連接。

  • (2)Web服務器收到客戶端請求後,會將網站的證書信息(證書中包含公鑰)傳送一份給客戶端。

  • (3)客戶端的瀏覽器與Web服務器開始協商SSL連接的安全等級,也就是信息加密的等級。

  • (4)客戶端的瀏覽器根據雙方同意的安全等級,建立會話密鑰,然後利用網站的公鑰將會話密鑰加密,並傳送給網站。

  • (5)Web服務器利用自己的私鑰解密出會話密鑰。

  • (6)Web服務器利用會話密鑰加密與客戶端之間的通信。

7、Http位於TCP/IP模型中的第幾層?爲什麼說Http是可靠的數據傳輸協議?

tcp/ip的五層模型

從下到上:物理層->數據鏈路層->網絡層->傳輸層->應用層

其中tcp/ip位於模型中的網絡層,處於同一層的還有ICMP(網絡控制信息協議)。http位於模型中的應用層

由於tcp/ip是面向連接的可靠協議,而http是在傳輸層基於tcp/ip協議的,所以說http是可靠的數據傳輸協議。

8、HTTP鏈接的特點

HTTP連接最顯著的特點是客戶端發送的每次請求都需要服務器回送響應,在請求結束後,會主動釋放連接。

從建立連接到關閉連接的過程稱爲“一次連接”。

9、TCP和UDP的區別

tcp是面向連接的,由於tcp連接需要三次握手,所以能夠最低限度的降低風險,保證連接的可靠性。

udp 不是面向連接的,udp建立連接前不需要與對象建立連接,無論是發送還是接收,都沒有發送確認信號。所以說udp是不可靠的。

由於udp不需要進行確認連接,使得UDP的開銷更小,傳輸速率更高,所以實時行更好。

10、Socket建立網絡連接的步驟

建立Socket連接至少需要一對套接字,其中一個運行與客戶端--ClientSocket,一個運行於服務端--ServiceSocket

  • 1、服務器監聽:服務器端套接字並不定位具體的客戶端套接字,而是處於等待連接的狀態,實時監控網絡狀態,等待客戶端的連接請求。

  • 2、客戶端請求:指客戶端的套接字提出連接請求,要連接的目標是服務器端的套接字。注意:客戶端的套接字必須描述他要連接的服務器的套接字,

指出服務器套接字的地址和端口號,然後就像服務器端套接字提出連接請求。

  • 3、連接確認:當服務器端套接字監聽到客戶端套接字的連接請求時,就響應客戶端套接字的請求,建立一個新的線程,把服務器端套接字的描述

發給客戶端,一旦客戶端確認了此描述,雙方就正式建立連接。而服務端套接字則繼續處於監聽狀態,繼續接收其他客戶端套接字的連接請求。

11、Tcp/IP三次握手,四次揮手

【問題1】爲什麼連接的時候是三次握手,關閉的時候卻是四次握手?

答:因爲當Server端收到Client端的SYN連接請求報文後,可以直接發送SYN+ACK報文。其中ACK報文是用來應答的,SYN報文是用來同步的。但是關閉連接時,當Server端收到FIN報文時,很可能並不會立即關閉SOCKET,所以只能先回復一個ACK報文,告訴Client端,"你發的FIN報文我收到了"。只有等到我Server端所有的報文都發送完了,我才能發送FIN報文,因此不能一起發送。故需要四步握手。

【問題2】爲什麼TIME_WAIT狀態需要經過2MSL(最大報文段生存時間)才能返回到CLOSE狀態?

答:雖然按道理,四個報文都發送完畢,我們可以直接進入CLOSE狀態了,但是我們必須假象網絡是不可靠的,有可以最後一個ACK丟失。所以TIME_WAIT狀態就是用來重發可能丟失的ACK報文。在Client發送出最後的ACK回覆,但該ACK可能丟失。Server如果沒有收到ACK,將不斷重複發送FIN片段。所以Client不能立即關閉,它必須確認Server接收到了該ACK。Client會在發送出ACK之後進入到TIME_WAIT狀態。Client會設置一個計時器,等待2MSL的時間。如果在該時間內再次收到FIN,那麼Client會重發ACK並再次等待2MSL。所謂的2MSL是兩倍的MSL(Maximum Segment Lifetime)。MSL指一個片段在網絡中最大的存活時間,2MSL就是一個發送和一個回覆所需的最大時間。如果直到2MSL,Client都沒有再次收到FIN,那麼Client推斷ACK已經被成功接收,則結束TCP連接。

【問題3】爲什麼不能用兩次握手進行連接?

答:3次握手完成兩個重要的功能,既要雙方做好發送數據的準備工作(雙方都知道彼此已準備好),也要允許雙方就初始序列號進行協商,這個序列號在握手過程中被髮送和確認。

現在把三次握手改成僅需要兩次握手,死鎖是可能發生的。作爲例子,考慮計算機S和C之間的通信,假定C給S發送一個連接請求分組,S收到了這個分組,併發 送了確認應答分組。按照兩次握手的協定,S認爲連接已經成功地建立了,可以開始發送數據分組。可是,C在S的應答分組在傳輸中被丟失的情況下,將不知道S 是否已準備好,不知道S建立什麼樣的序列號,C甚至懷疑S是否收到自己的連接請求分組。在這種情況下,C認爲連接還未建立成功,將忽略S發來的任何數據分 組,只等待連接確認應答分組。而S在發出的分組超時後,重複發送同樣的分組。這樣就形成了死鎖。

【問題4】如果已經建立了連接,但是客戶端突然出現故障了怎麼辦?

TCP還設有一個保活計時器,顯然,客戶端如果出現故障,服務器不能一直等下去,白白浪費資源。服務器每收到一次客戶端的請求後都會重新復位這個計時器,時間通常是設置爲2小時,若兩小時還沒有收到客戶端的任何數據,服務器就會發送一個探測報文段,以後每隔75分鐘發送一次。若一連發送10個探測報文仍然沒反應,服務器就認爲客戶端出了故障,接着就關閉連接。

原文地址: https://www.cnblogs.com/huangjialin/p/8657696.html,轉載請註明原創

閱讀更多

近3年BAT面試真題整理合集

** 一招教你讀懂JVM和Dalvik之間的區別**

NDK項目實戰—高仿360手機助手之卸載監聽

kotlin學習筆記-異常好玩的list集合總結

相信自己,沒有做不到的,只有想不到的

在這裏獲得的不僅僅是技術!

image

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章