1. JNI和NDK
1.談?wù)勀銓?duì)JNI和NDK的理解
答:JNI(Java Native Interface
)是Java提供的一種機(jī)制,用于實(shí)現(xiàn)Java與其他編程語(yǔ)言(如C、C++)之間的交互。它允許Java代碼調(diào)用本地代碼(Native Code)并與其進(jìn)行數(shù)據(jù)交換。
NDK(Native Development Kit
)是Android提供的一個(gè)工具集,用于在Android應(yīng)用中使用本地代碼。它包含了一系列的工具和庫(kù),可以將C、C++代碼編譯成與特定平臺(tái)相關(guān)的本地庫(kù)文件(例如.so文件),然后通過(guò)JNI將這些本地庫(kù)文件嵌入到Android應(yīng)用中。
JNI和NDK的關(guān)系是,JNI提供了Java與本地代碼交互的機(jī)制,而NDK則提供了將本地代碼集成到Android應(yīng)用中的工具和庫(kù)。通過(guò)JNI和NDK,開(kāi)發(fā)者可以在Android應(yīng)用中使用C、C++等本地語(yǔ)言編寫(xiě)高性能、底層的功能模塊,比如圖像處理、音頻處理、加密算法等。
使用JNI和NDK可以帶來(lái)以下優(yōu)勢(shì):
1.性能優(yōu)化:某些計(jì)算密集型任務(wù),使用本地代碼可以提高執(zhí)行效率。
2.跨平臺(tái)開(kāi)發(fā):通過(guò)使用本地代碼,可以在不同平臺(tái)上共享代碼和庫(kù)。
3.重用現(xiàn)有代碼:如果已經(jīng)有了C、C++等語(yǔ)言的現(xiàn)有代碼,可以通過(guò)JNI和NDK將其集成到Android應(yīng)用中,避免重復(fù)開(kāi)發(fā)。
需要注意的是,在使用JNI和NDK時(shí),需要謹(jǐn)慎處理內(nèi)存管理、類(lèi)型轉(zhuǎn)換和異常處理等問(wèn)題,以確保代碼的正確性和穩(wěn)定性。
2.簡(jiǎn)要的JNI調(diào)用過(guò)程:
1、編寫(xiě)本地代碼:首先,需要編寫(xiě)包含所需功能的本地代碼,通常是用C或C++編寫(xiě)。這些本地代碼將被編譯成共享庫(kù)(.so文件)。
2、創(chuàng)建JNI接口:在Java代碼中,需要聲明與本地代碼對(duì)應(yīng)的JNI接口。這些接口方法將與本地代碼中的函數(shù)進(jìn)行綁定,以便在Java中調(diào)用。
3、生成頭文件:使用Java Development Kit(JDK)
中的工具javah,生成包含JNI接口方法定義的頭文件。
4、實(shí)現(xiàn)JNI接口:在本地代碼中,需要實(shí)現(xiàn)JNI接口中聲明的方法。這些方法將提供與Java代碼之間的橋梁,使得Java可以調(diào)用本地代碼。
5、編譯本地代碼:使用C/C++編譯器將本地代碼編譯成共享庫(kù)(.so文件)。
6、將共享庫(kù)加載到應(yīng)用程序:在Android應(yīng)用程序中,可以使用System.loadLibrary()
方法加載共享庫(kù)。
7、調(diào)用JNI方法:在Java代碼中,通過(guò)調(diào)用JNI接口中聲明的方法來(lái)調(diào)用本地代碼。可以使用native關(guān)鍵字標(biāo)記這些方法。
8、運(yùn)行應(yīng)用程序:在應(yīng)用程序運(yùn)行時(shí),當(dāng)Java代碼調(diào)用JNI方法時(shí),將觸發(fā)與本地代碼的交互,完成相應(yīng)的功能操作。
需要注意的是,JNI調(diào)用涉及到本地代碼的編寫(xiě)和編譯,因此需要對(duì)C/C++有一定的了解。此外,在JNI調(diào)用過(guò)程中,需要注意內(nèi)存管理和數(shù)據(jù)類(lèi)型轉(zhuǎn)換等問(wèn)題,以確保安全性和正確性。
JNI調(diào)用過(guò)程包括編寫(xiě)本地代碼、創(chuàng)建JNI接口、生成頭文件、實(shí)現(xiàn)JNI接口、編譯本地代碼、加載共享庫(kù)、調(diào)用JNI方法等步驟,通過(guò)這些步驟可以實(shí)現(xiàn)Java代碼與本地代碼之間的交互。
2. 線程、同步、異步
1.Java創(chuàng)建線程的方式有幾種?start()方法和 run()方法的區(qū)別
答:在Java中,有兩種主要的方式可以創(chuàng)建線程:
1.繼承Thread類(lèi):通過(guò)繼承Thread
類(lèi)并重寫(xiě)其run()
方法來(lái)創(chuàng)建線程。首先,創(chuàng)建一個(gè)繼承自Thread的子類(lèi),并在子類(lèi)中實(shí)現(xiàn)run()
方法來(lái)定義線程的具體邏輯。然后,通過(guò)創(chuàng)建子類(lèi)的實(shí)例對(duì)象,并調(diào)用其start()
方法來(lái)啟動(dòng)線程。
2.實(shí)現(xiàn)Runnable接口:通過(guò)實(shí)現(xiàn)Runnable
接口來(lái)創(chuàng)建線程。首先,創(chuàng)建一個(gè)實(shí)現(xiàn)了Runnable接口的類(lèi),并在該類(lèi)中實(shí)現(xiàn)run()
方法。然后,創(chuàng)建Thread類(lèi)的實(shí)例對(duì)象,將實(shí)現(xiàn)了Runnable接口的類(lèi)的實(shí)例作為參數(shù)傳遞給Thread的構(gòu)造函數(shù)。最后,調(diào)用Thread對(duì)象的start()
方法來(lái)啟動(dòng)線程。
區(qū)別:
- 使用繼承Thread類(lèi)的方式,線程的邏輯直接寫(xiě)在子類(lèi)中的run()方法中,但這樣會(huì)限制了子類(lèi)的繼承關(guān)系。
- 使用實(shí)現(xiàn)Runnable接口的方式,線程的邏輯在實(shí)現(xiàn)了Runnable接口的類(lèi)中定義,可以更靈活地共享代碼和資源,并且避免了單繼承的限制。
關(guān)于start()方法和run()方法的區(qū)別:
- start()方法用于啟動(dòng)線程并異步執(zhí)行線程的邏輯。調(diào)用start()方法后,系統(tǒng)會(huì)為線程分配資源并調(diào)用線程的run()方法。
- run()方法是線程的入口點(diǎn),可以將線程的具體邏輯在run()方法中實(shí)現(xiàn)。但直接調(diào)用run()方法并不會(huì)啟動(dòng)新的線程,而是在當(dāng)前線程中同步執(zhí)行run()方法的代碼塊。
總結(jié)來(lái)說(shuō),Java創(chuàng)建線程的方式有繼承Thread類(lèi)和實(shí)現(xiàn)Runnable接口。start()
方法用于啟動(dòng)線程并異步執(zhí)行線程的邏輯,而run()
方法是線程的入口點(diǎn),在調(diào)用start()方法后由系統(tǒng)自動(dòng)調(diào)用。
2.Handler 機(jī)制和原理
Handler
主要用于異步消息的處理:當(dāng)發(fā)出一個(gè)消息之后,首先進(jìn)入一個(gè)消息隊(duì)列,發(fā)送消息的函數(shù)即刻返回,而另外一個(gè)部分在消息隊(duì)列中逐一將消息取出,然后對(duì)消息進(jìn)行處理,也就是發(fā)送消息和接收消息不是同步的處理。
基礎(chǔ)概念
- UI線程
主線程ActivityThread
,主線程也是Ui線程,應(yīng)用啟動(dòng)的時(shí)候會(huì)啟動(dòng)一個(gè)ui線程 - Handler
負(fù)責(zé)發(fā)送消息和處理消息。 - Looper
負(fù)責(zé)消息循環(huán),循環(huán)取出MessageQueue
里面的Message
,并交給相應(yīng)的Handler
進(jìn)行處理。 - MessageQueue
消息隊(duì)列,用來(lái)存放通過(guò)Hangdler
發(fā)送的消息,按照先進(jìn)先出的順序取出消息,內(nèi)部使用的是單鏈表結(jié)構(gòu)(優(yōu)先級(jí)鏈表) - Message
Handler發(fā)送和處理的消息個(gè)體,有MessageQueue
管理,攜帶Handler信息和具體消息
抽象概念具體化:
Handler:快遞員(收件(發(fā)送消息)和派件(處理消息))(收發(fā)都是統(tǒng)一快遞員,只屬于同一家快遞公司)
Message:快遞包裹(包裹中攜帶物品,和快遞員身份消息)
MessageQueue:快遞分揀中心(將快遞按照時(shí)間優(yōu)先級(jí)整理好包裹)
Looper:快遞公司(不停地發(fā)送消息)
- 首先在UI線程我們創(chuàng)建了一個(gè)Handler實(shí)例對(duì)象,無(wú)論是匿 名內(nèi)部類(lèi)還是自定義類(lèi)生成的Handler實(shí)例對(duì)象,我們都需 要對(duì)
handleMessage
方法進(jìn)行重寫(xiě), - 在
handleMessage
方法中我們可以通過(guò)參數(shù)msg來(lái)寫(xiě)接受消息過(guò)后UIi線程的邏輯處理,接著我們創(chuàng)建子線程 - 在子線程中需要更新U的時(shí)候,新建一個(gè)
Message
對(duì)象,并且將消息的數(shù)據(jù)記錄在這個(gè)消息對(duì)象Message
的內(nèi)部,比如arg1,arg2,obj等,然后通過(guò)前面的Handler實(shí)例對(duì)象調(diào)用sendMessge
方法把這個(gè)Message實(shí)例對(duì)象發(fā)送出去,之后這個(gè)消息會(huì)被存放于MessageQueue
中等待被處理, - 此時(shí)
MessageQueue
的管 家Looper正在不停的把MessageQueue
存在的消息取出 來(lái),通過(guò)回調(diào)dispatchMessage
方法將消息傳遞給Handler的handleMessage
方法,最終前面提到的消息會(huì)被Looper 從MessageQueue
中取出來(lái)傳遞handleMessage
方法。
handler的使用方法
1.post(runnable) Runnable
則是直接給出處理的方法)在未來(lái)的某個(gè)時(shí)間進(jìn)行相應(yīng)處理源碼底層還是調(diào)用了sendmassage(massage)
方法
2.sendmassage(massage)
放置信息,可以傳遞一些參數(shù) ,可以定時(shí)處理更新UI
handler引起的內(nèi)存泄露及解決辦法
handler發(fā)送的消息在當(dāng)前handler的消息隊(duì)列中,如果此時(shí)activity finish掉了,那么消息隊(duì)列的消息依舊會(huì)由handler進(jìn)行處理,若此時(shí)handler聲明為內(nèi)部類(lèi)(非靜態(tài)內(nèi)部類(lèi)),我們知道內(nèi)部類(lèi)天然持有外部類(lèi)的實(shí)例引用,這樣在GC垃圾回收機(jī)制進(jìn)行回收時(shí)發(fā)現(xiàn)這個(gè)Activity居然還有其他引用存在,因而就不會(huì)去回收這個(gè)Activity,進(jìn)而導(dǎo)致activity泄露。
解決方案:
1.把handler
設(shè)置成靜態(tài)內(nèi)部類(lèi),因?yàn)殪o態(tài)內(nèi)部類(lèi)不持有外部類(lèi)的引用,所以使用靜態(tài)的handler不會(huì)導(dǎo)致activity的泄露
2.onDestroy
生命周期中調(diào)用 handler.removeCallbacks()
進(jìn)行釋放
3.handler
內(nèi)部類(lèi)持有外部activity
的弱引用
3.為什么在子線程中創(chuàng)建Handler會(huì)拋異常?
不能在還沒(méi)有調(diào)用 Looper.prepare()
方法的線程中創(chuàng)建Handler
.因?yàn)閽伋霎惓5牡胤?,?code>mLooper 對(duì)象為null
的時(shí)候會(huì)拋出異常。說(shuō)明這里的Looper.myLooper0
;的返回值是null
。 只有調(diào)用了Looper.prepare()
方法,才會(huì)構(gòu)造一個(gè)Looper對(duì)象并在 ThreadLocal
存儲(chǔ)當(dāng)前線程的Looper對(duì)象
這樣在調(diào)用 Looper.myLooper()
時(shí),獲取的結(jié)果就不會(huì)為null
4.Android中的ANR的解決方法
答:ANR(Application Not Responding
)指的是Android應(yīng)用在主線程上執(zhí)行耗時(shí)操作時(shí)出現(xiàn)的無(wú)響應(yīng)現(xiàn)象,這可能導(dǎo)致應(yīng)用無(wú)法響應(yīng)用戶輸入,給用戶帶來(lái)不良體驗(yàn)。下面是幾種常見(jiàn)的解決ANR問(wèn)題的方法:
1.將耗時(shí)操作放在子線程中:將耗時(shí)的操作(如網(wǎng)絡(luò)請(qǐng)求、數(shù)據(jù)庫(kù)查詢等)放在子線程中執(zhí)行,避免阻塞主線程??梢允褂?code>Thread、AsyncTask
、Handler
等方式創(chuàng)建子線程,確保主線程保持響應(yīng)。
2.使用異步操作:對(duì)于一些需要等待結(jié)果的操作,例如網(wǎng)絡(luò)請(qǐng)求或數(shù)據(jù)庫(kù)查詢,使用異步方式執(zhí)行,如使用AsyncTask
、Handler
、RxJava
等框架來(lái)處理。
3.使用多線程技術(shù):合理使用多線程技術(shù),將一些繁重的計(jì)算或IO操作放在后臺(tái)線程中執(zhí)行,以減輕主線程的負(fù)擔(dān)。
4.避免在主線程進(jìn)行耗時(shí)的IO操作:例如文件讀寫(xiě)、數(shù)據(jù)庫(kù)操作等,這些操作可以使用異步方式或?qū)⑵浞旁谧泳€程中執(zhí)行。
5.使用合適的數(shù)據(jù)結(jié)構(gòu)和算法:優(yōu)化代碼邏輯,使用高效的數(shù)據(jù)結(jié)構(gòu)和算法,減少不必要的計(jì)算和迭代。
6.避免頻繁的UI更新:減少UI更新的次數(shù),盡量批量更新UI,避免頻繁調(diào)用UI更新方法。
7.使用定時(shí)器避免阻塞主線程:通過(guò)使用定時(shí)器(Timer)或者Handler的postDelayed()方法,將一些可能引起阻塞的操作延后執(zhí)行,避免在主線程中長(zhǎng)時(shí)間執(zhí)行。
8.使用線程池:使用線程池來(lái)管理線程,避免頻繁創(chuàng)建和銷(xiāo)毀線程的開(kāi)銷(xiāo)。
9.檢查和優(yōu)化代碼:定期檢查代碼,查找和消除潛在的性能問(wèn)題,如內(nèi)存泄漏、死鎖等。
10.使用性能分析工具:使用Android Studio提供的性能分析工具,如Profiler、Traceview等,來(lái)分析應(yīng)用的性能瓶頸,并優(yōu)化相應(yīng)的代碼。
綜上所述,通過(guò)合理的線程管理、使用異步操作、優(yōu)化代碼邏輯等方法,可以有效解決Android應(yīng)用中的ANR問(wèn)題,提升應(yīng)用的響應(yīng)性能
5.intentservice有什么優(yōu)點(diǎn)?
答:IntentService是Android中的一個(gè)特殊Service,它主要用于在后臺(tái)執(zhí)行異步任務(wù),處理Intent請(qǐng)求,并在任務(wù)完成后自動(dòng)停止。
以下是IntentService的幾個(gè)優(yōu)點(diǎn):
- 簡(jiǎn)化了異步任務(wù)的處理:IntentService封裝了異步任務(wù)的處理邏輯,使得開(kāi)發(fā)者可以更加專(zhuān)注于實(shí)現(xiàn)具體的任務(wù)邏輯,而無(wú)需關(guān)注線程管理和任務(wù)調(diào)度等細(xì)節(jié)。它通過(guò)創(chuàng)建一個(gè)單獨(dú)的工作線程來(lái)處理任務(wù),避免了在主線程中執(zhí)行耗時(shí)操作導(dǎo)致的ANR(Application Not Responding,在主線程上執(zhí)行耗時(shí)操作時(shí)出現(xiàn)的無(wú)響應(yīng)現(xiàn)象)問(wèn)題。
- 自動(dòng)停止:IntentService在任務(wù)執(zhí)行完成后會(huì)自動(dòng)停止,無(wú)需手動(dòng)調(diào)用stopSelf()方法來(lái)停止Service。這樣可以避免Service長(zhǎng)時(shí)間運(yùn)行而消耗系統(tǒng)資源。
- 順序執(zhí)行:IntentService會(huì)按照任務(wù)的順序依次處理Intent請(qǐng)求,確保每個(gè)Intent請(qǐng)求都能被正確處理。每次處理一個(gè)Intent請(qǐng)求時(shí),它會(huì)將其他Intent請(qǐng)求放入隊(duì)列中等待處理,保證了任務(wù)的順序性。
- 線程安全:IntentService內(nèi)部使用單個(gè)工作線程來(lái)處理任務(wù),因此避免了多線程并發(fā)訪問(wèn)的問(wèn)題。這樣可以簡(jiǎn)化任務(wù)的編寫(xiě),并減少并發(fā)導(dǎo)致的競(jìng)態(tài)條件和同步問(wèn)題。
-
可以與其他組件進(jìn)行通信:IntentService可以通過(guò)廣播、回調(diào)或發(fā)送消息等方式與其他組件進(jìn)行通信,便于任務(wù)的狀態(tài)更新、結(jié)果的傳遞等。
需要注意的是,IntentService適用于執(zhí)行一系列相對(duì)獨(dú)立的任務(wù),每個(gè)任務(wù)都是通過(guò)Intent進(jìn)行觸發(fā)和處理的。如果需要執(zhí)行長(zhǎng)時(shí)間運(yùn)行的任務(wù)或與UI交互的任務(wù),可能需要考慮其他方式,如使用HandlerThread或AsyncTask等。
6.okhttp異步請(qǐng)求流程
- 構(gòu)建
OkhttpClient
對(duì)象
OkhttpClient:相當(dāng)于配置中心,所有的請(qǐng)求都會(huì)共享這些配置,OkhttpClient
中定義了網(wǎng)絡(luò)協(xié)議、DNS
、請(qǐng)求時(shí)間等等。創(chuàng)建對(duì)象的方式有兩種,一種是通過(guò)直接new
對(duì)象的方式,另一種是通過(guò)Builder
模式設(shè)置參數(shù)來(lái)進(jìn)行創(chuàng)建。 - 構(gòu)建
Request
對(duì)象
Request:網(wǎng)絡(luò)請(qǐng)求信息的封裝類(lèi),內(nèi)置url、head
、get/post
請(qǐng)求等。Request
對(duì)象的構(gòu)建只能通過(guò)builder
模式來(lái)構(gòu)建,具體的構(gòu)建過(guò)程同OkhttpClient是一樣的,都是使用了Builder構(gòu)建模式。 - 創(chuàng)建
Call
對(duì)象
Call:網(wǎng)絡(luò)請(qǐng)求的執(zhí)行者,Call用來(lái)描述一個(gè)可被執(zhí)行、中斷的請(qǐng)求,client.newCall(request)
方法就是指創(chuàng)建一個(gè)新的將要被執(zhí)行的請(qǐng)求,每一個(gè)Request最終將會(huì)被封裝成一個(gè)Realcall對(duì)象。Realcall是Call接口唯一的實(shí)現(xiàn)類(lèi),AsyncCall是Realcall
中的內(nèi)部類(lèi),你也可以把RealCall
理解為同步請(qǐng)求操作,而AsyncCall
則是異步請(qǐng)求操作。 - 發(fā)送異步請(qǐng)求
在準(zhǔn)備工作(OkhttpClient
、Request
、Call
)都完成之后,接下來(lái)我們就要調(diào)用call.enqueue()
正式的開(kāi)始進(jìn)行網(wǎng)絡(luò)請(qǐng)求了。前面我們提到,RealCall
是Call唯一的 實(shí)現(xiàn)類(lèi),所以我們來(lái)查看RealCall
中的enqueue()
。在enqueue
方法中首先會(huì)判斷當(dāng)前的call是否已經(jīng)執(zhí)行過(guò)一次,如果已經(jīng)執(zhí)行過(guò)的話,就會(huì)拋出一個(gè)異常,如果沒(méi)有執(zhí)行的話會(huì)給executed
變量進(jìn)行賦值,表示已經(jīng)執(zhí)行過(guò),從這里也可以看出call只能執(zhí)行一次。接著我們看最后一行,一共做了兩件事,先是封裝了一個(gè)AsyncCall對(duì)象,然后通過(guò)client.dispatcher().enqueue()
方法開(kāi)始實(shí)際的異步請(qǐng)求。進(jìn)入AsyncCall中查看代碼??吹紸syncCall類(lèi)繼承自NamedRunnable,緊接著我們?cè)龠M(jìn)入到NamedRunnable中可以看到它實(shí)現(xiàn)了Runnable接口,所以最終確定AsyncCall
就是一個(gè)Runnable
,看完封裝AsyncCall對(duì)象之后,我們?cè)賮?lái)看一下client.dispatcher().enqueue()
,先是通過(guò)client.dispatcher()
獲取到dispatcher對(duì)象,然后調(diào)用Dispatcher中的enqueue方法看完封裝AsyncCall
對(duì)象之后,我們?cè)賮?lái)看一下client.dispatcher().enqueue()
,先是通過(guò)client.dispatcher()獲取到dispatcher
對(duì)象,然后調(diào)用Dispatcher中的enqueue
方法 - Dispatcher分發(fā)器
Dispatcher:Dispatcher
是一個(gè)任務(wù)分發(fā)器,用于管理其對(duì)應(yīng)OkhttpClient
的所有請(qǐng)求。它的主要功能如下:
發(fā)起/取消網(wǎng)絡(luò)請(qǐng)求API:execute
、enqueue
、cancel。
線程池管理異步任務(wù)。
記錄同步任務(wù)、異步任務(wù)及等待執(zhí)行的異步任務(wù)(內(nèi)部維護(hù)了三個(gè)隊(duì)列)。 -
Interceptors
攔截器 - 獲取
Response
響應(yīng) - 調(diào)用
finished
通過(guò)getResponseWithInterceptorChain()
拿到了服務(wù)器返回的響應(yīng)Response
,然后進(jìn)行請(qǐng)求成功或者請(qǐng)求失敗的回調(diào),到這里為止,一次完整的網(wǎng)絡(luò)請(qǐng)求請(qǐng)求已經(jīng)結(jié)束了。代碼中有一個(gè)finally
,那代表著我們的請(qǐng)求不管成功與否,都會(huì)進(jìn)入到這個(gè)finally當(dāng)中
7.Okhttp的同步請(qǐng)求
- 創(chuàng)建
OkhttpClient
對(duì)象 - 請(qǐng)求報(bào)文創(chuàng)建,包含常用的請(qǐng)求信息,如
url
、get/post
方法,設(shè)置請(qǐng)求頭等 - 創(chuàng)建
Call
對(duì)象 - 同步請(qǐng)求,發(fā)送請(qǐng)求后,就會(huì)進(jìn)入阻塞狀態(tài),直到收到響應(yīng)
8. 如何避免 OOM 異常
當(dāng)程序需要申請(qǐng)一段“大”內(nèi)存,但是虛擬機(jī)沒(méi)有辦法及時(shí)的給到,即使做了GC操作以后
這就會(huì)拋出 OutOfMemoryException
也就是OOM
如何避免OOM
減少內(nèi)存對(duì)象的占用
-
ArrayMap/SparseArray
代替hashmap
- 避免在android里面使用Enum
- 減少
bitmap
的內(nèi)存占用inSampleSize
:縮放比例,在把圖片載入內(nèi)存之前,我們需要先計(jì)算出一個(gè)合適的縮放比例,避免不必要的大圖載入。decode format
:解碼格式,選擇ARGB_8888/RBG_565/ARGB_4444/ALPHA_8,存在很大差異。 - 減少資源圖片的大小,過(guò)大的圖片可以考慮分段加載
9. Android 線程間通信有哪幾種方式
1)共享變量(內(nèi)存)
2)管道
3)handle機(jī)制
runOnUiThread(Runnable)
view.post(Runnable)
3. UI
1.談?wù)勀銓?duì) Bitmap 的理解,什么時(shí)候應(yīng)該手動(dòng)調(diào)用 bitmaprecycle():
答:Bitmap是Android中用于表示圖像的類(lèi),它可以加載、創(chuàng)建和操作位圖圖像。它是像素的二維數(shù)組,每個(gè)像素用于表示圖像的顏色和透明度。Bitmap類(lèi)提供了各種方法來(lái)操作圖像,包括縮放、裁剪、旋轉(zhuǎn)、像素操作等。
手動(dòng)調(diào)用bitmap.recycle()是用于釋放Bitmap占用的內(nèi)存資源。當(dāng)不再需要一個(gè)Bitmap
對(duì)象時(shí),可以調(diào)用recycle()
方法來(lái)顯式釋放內(nèi)存,以便及時(shí)回收內(nèi)存資源。這在以下情況下特別重要:
-
內(nèi)存敏感性:當(dāng)應(yīng)用程序使用大量的位圖資源時(shí),尤其是較大的位圖,及時(shí)釋放不再使用的位圖可以減少內(nèi)存占用,避免
OutOfMemoryError
等內(nèi)存相關(guān)問(wèn)題。 -
頻繁創(chuàng)建位圖:如果應(yīng)用程序頻繁創(chuàng)建位圖對(duì)象,但又沒(méi)有及時(shí)釋放舊的位圖對(duì)象,會(huì)導(dǎo)致內(nèi)存占用不斷增加,可能會(huì)造成內(nèi)存泄漏。因此,在創(chuàng)建新的位圖對(duì)象之前,應(yīng)該確保舊的位圖對(duì)象已經(jīng)被回收。
需要注意的是,調(diào)用recycle()方法后,Bitmap對(duì)象將變?yōu)闊o(wú)效狀態(tài),不能再對(duì)其進(jìn)行任何操作。因此,在調(diào)用recycle()方法之后,應(yīng)該避免對(duì)該Bitmap對(duì)象進(jìn)行任何讀取或?qū)懭氩僮鳌?/li>
另外,從Android 3.0(API級(jí)別11)開(kāi)始,Bitmap的內(nèi)存會(huì)自動(dòng)進(jìn)行垃圾回收,不再需要手動(dòng)調(diào)用recycle()方法。因此,在較新的Android版本上,手動(dòng)調(diào)用recycle()方法的必要性可能會(huì)降低。但對(duì)于舊版本的Android系統(tǒng),特別是內(nèi)存敏感的環(huán)境下,仍然建議及時(shí)調(diào)用recycle()方法來(lái)釋放Bitmap對(duì)象占用的內(nèi)存。
2. Android布局的五個(gè)常見(jiàn)類(lèi)型:
1、LinearLayout(線性布局):LinearLayout是最簡(jiǎn)單和常見(jiàn)的布局類(lèi)型之一。它按照水平或垂直方向排列子視圖??梢允褂?code>android:orientation屬性設(shè)置為horizontal
(水平)或vertical
(垂直)。
2、RelativeLayout(相對(duì)布局):RelativeLayout允許子視圖相對(duì)于父視圖或其他子視圖進(jìn)行定位??梢允褂酶鞣N規(guī)則(如alignParentTop
、alignParentLeft
、above
、below
等)來(lái)定義子視圖之間的相對(duì)位置。
3、ConstraintLayout(約束布局):ConstraintLayout
是一個(gè)靈活且功能強(qiáng)大的布局類(lèi)型。它使用約束條件來(lái)定義子視圖之間的關(guān)系??梢酝ㄟ^(guò)拖拽、連接線、屬性設(shè)置等方式將子視圖與父視圖或其他子視圖進(jìn)行關(guān)聯(lián)。
4、FrameLayout(幀布局):FrameLayout
是一種簡(jiǎn)單的布局,用于在屏幕上疊放多個(gè)子視圖。默認(rèn)情況下,子視圖會(huì)疊放在左上角,可以使用android:layout_gravity
屬性來(lái)調(diào)整子視圖的位置。
5、GridLayout(網(wǎng)格布局):GridLayout
將子視圖組織成網(wǎng)格狀的結(jié)構(gòu)。它可以指定行數(shù)和列數(shù),并可以控制子視圖在網(wǎng)格中的位置??梢允褂?code>android:layout_row和android:layout_column
屬性來(lái)指定子視圖所在的行和列。
3. Android 中的動(dòng)畫(huà)有哪幾類(lèi),它們的特點(diǎn)和區(qū)別是什么
- 視圖動(dòng)畫(huà),或者說(shuō)補(bǔ)間動(dòng)畫(huà)。只是視覺(jué)上的一個(gè)效果,實(shí)際view屬性沒(méi)有變化,性能好,但是支持方式少。
- 屬性動(dòng)畫(huà),通過(guò)變化屬性來(lái)達(dá)到動(dòng)畫(huà)的效果,性能略差,支持點(diǎn)擊等事件。android 3.0
- 幀動(dòng)畫(huà),通過(guò)drawable一幀幀畫(huà)出來(lái)。
- Gif動(dòng)畫(huà),原理同上,canvas畫(huà)出來(lái)。
4. 組件
1. Android四大組件是指Activity、Service、BroadcastReceiver和ContentProvider。
1、Activity(活動(dòng)):Activity是Android應(yīng)用程序的用戶界面的基本構(gòu)建塊。它負(fù)責(zé)處理用戶與應(yīng)用程序的交互,并負(fù)責(zé)顯示用戶界面。在我的項(xiàng)目中,通常使用Activity來(lái)展示各個(gè)界面,如歡迎頁(yè)面、登錄頁(yè)面、主頁(yè)面等。我會(huì)在Activity中處理用戶的輸入、展示數(shù)據(jù)、與其他組件進(jìn)行通信等操作。
2、Service(服務(wù)):Service是一種在后臺(tái)運(yùn)行的組件,它不與用戶交互,用于執(zhí)行長(zhǎng)時(shí)間運(yùn)行的任務(wù)或處理一些不需要用戶界面的操作。在我的項(xiàng)目中,我會(huì)使用Service來(lái)處理一些耗時(shí)操作,如網(wǎng)絡(luò)請(qǐng)求、音樂(lè)播放等。Service可以與Activity進(jìn)行通信,通過(guò)Intent或綁定方式進(jìn)行交互。
3、BroadcastReceiver(廣播接收器):BroadcastReceiver
用于接收和響應(yīng)系統(tǒng)或其他應(yīng)用程序發(fā)送的廣播消息。它允許應(yīng)用程序在后臺(tái)監(jiān)測(cè)并響應(yīng)各種系統(tǒng)事件或自定義事件。在我的項(xiàng)目中,我會(huì)使用BroadcastReceiver
來(lái)接收系統(tǒng)廣播或自定義廣播,如網(wǎng)絡(luò)狀態(tài)變化、電量變化等。通過(guò)注冊(cè)廣播接收器,并實(shí)現(xiàn)相應(yīng)的邏輯,我可以在特定事件發(fā)生時(shí)執(zhí)行相應(yīng)的操作。
4、ContentProvider(內(nèi)容提供器):ContentProvider
用于管理應(yīng)用程序中的共享數(shù)據(jù),并提供對(duì)外訪問(wèn)數(shù)據(jù)的接口。通過(guò)ContentProvider
,應(yīng)用程序可以與其他應(yīng)用程序共享數(shù)據(jù),并實(shí)現(xiàn)數(shù)據(jù)的增刪改查操作。在我的項(xiàng)目中,我會(huì)使用ContentProvider
來(lái)管理應(yīng)用程序中的數(shù)據(jù)庫(kù)或文件數(shù)據(jù),提供對(duì)外的數(shù)據(jù)訪問(wèn)接口。
在我的項(xiàng)目中,通常會(huì)根據(jù)業(yè)務(wù)需求結(jié)合使用這些組件。例如,一個(gè)典型的流程可能是在Activity
中展示用戶界面,并通過(guò)Service
進(jìn)行后臺(tái)任務(wù)處理,通過(guò)BroadcastReceiver
接收相關(guān)事件的通知并觸發(fā)相應(yīng)的操作。同時(shí),通過(guò)ContentProvider
來(lái)管理數(shù)據(jù)的訪問(wèn)和共享。每個(gè)組件都有其特定的作用和用法,合理使用這些組件可以提高應(yīng)用程序的靈活性和擴(kuò)展性。
5. 四大組件詳解
1. activity
(1) 一個(gè)Activity通常就是一個(gè)單獨(dú)的屏幕 (窗口)
(2) Activity之間通過(guò)Intent
、putString(key,value)
進(jìn)行通信。利用Bundle
進(jìn)行傳值
(3) android應(yīng)用中每一個(gè)Activity都必須要在AndroidManifest.xml
配置文件中聲明,否則系統(tǒng)將不識(shí)別也不執(zhí)行該Activity。
Activity向Fragment通信
1、利用Bundle+getArguments()
在Activity中實(shí)例化Fragment并使用setArguments
綁定Bundle對(duì)象,在Fragment中拿到Bundle實(shí)例
2、強(qiáng)轉(zhuǎn)型為Avtivity,調(diào)用對(duì)應(yīng)Activity的方法
Activity與Fragment之間通信,同屬于一個(gè)Activity的Fragment之間的通信。主要的方式有
- 方法一、通過(guò)
setArguments()
方法,在 Activity 中實(shí)例化Fragment對(duì)象,通過(guò)Fragment.setArguments(Bundle)
傳遞信息,在 Fragment 中通過(guò)getArguments()
方法獲得 Bundle 對(duì)象 - 方法二、通過(guò) Fragment 的
onAttach(Context context)
方法,在 Activity 中定義getData()
的公共方法,返回希望傳送的數(shù)據(jù)的類(lèi)型,在 Fragment 的onAttach(Context context)
方法中通過(guò) ((MainActivity) context) 類(lèi)型轉(zhuǎn)換,獲得 MainActivity 的上下文對(duì)象,再通過(guò)上面定義的共有方法獲得數(shù)據(jù)。
Fragment 向 Activity 傳遞數(shù)據(jù)
- 以定義接口的形式,在fragment中定義接口,然后Activity中實(shí)現(xiàn)接口,從而實(shí)現(xiàn)數(shù)據(jù)的傳遞
- 在 Fragment 中定義一個(gè)接口
MessageSend
,并定義方法sendMessage
- 定義
MessageSend
對(duì)象,并在 Fragment 的onAttach()
方法中初始化接口(當(dāng)然也可以在onCreate 或onCreateView
等方法中初始化接口,只要在傳送數(shù)據(jù)之前初始化就可以),在 Fragment 中設(shè)置需要傳送的數(shù)據(jù),在 Activity 中實(shí)現(xiàn)回調(diào)接口,即可獲取數(shù)據(jù)
Activity正常情況下生命周期
onCreate:在activity被創(chuàng)建時(shí)調(diào)用
onStart:Activity將被啟動(dòng),此時(shí)Activity已可見(jiàn),但還在后臺(tái)(無(wú)法與用戶交互)
onResume:在獲取用戶焦點(diǎn)與用戶互動(dòng)時(shí)調(diào)用
onPause:在activity被其他activity覆蓋時(shí)或者鎖屏?xí)r調(diào)用
onStop:在activity對(duì)用戶不可見(jiàn)時(shí)調(diào)用
onDestroy:在Activity銷(xiāo)毀時(shí)調(diào)用,回收工作和最終資源釋放
onReStart:在activity停止?fàn)顟B(tài)重新開(kāi)啟時(shí)調(diào)用
Activity第一次打開(kāi):onCreate->onStart->onResume
用戶返回桌面或打開(kāi)新Activity,當(dāng)前Activity:onPause->onStop,特殊情況當(dāng)新Activity采用透明模式,當(dāng)前Activity不會(huì)調(diào)用onStop
用戶退出當(dāng)前Activity:onPause->onStop->onDestroy
Activity異常情況下生命周期
android橫豎屏切換的時(shí)候Activity的生命周期
答:當(dāng)Android設(shè)備的屏幕從橫屏切換為豎屏,或者從豎屏切換為橫屏?xí)r,Activity的生命周期會(huì)經(jīng)歷以下過(guò)程:
1.onPause(): 在屏幕旋轉(zhuǎn)之前,系統(tǒng)會(huì)調(diào)用當(dāng)前Activity的onPause()
方法。這表示Activity正在失去焦點(diǎn),并即將進(jìn)入停止?fàn)顟B(tài)。
2.onSaveInstanceState(): 在屏幕旋轉(zhuǎn)之前,系統(tǒng)會(huì)調(diào)用當(dāng)前Activity的onSaveInstanceState()
方法,用于保存Activity的狀態(tài)信息。你可以在此方法中保存必要的數(shù)據(jù),以便在Activity重新創(chuàng)建后進(jìn)行恢復(fù)。
如果你不希望在橫豎屏切換時(shí)保存數(shù)據(jù),可以在 Activity 的清單文件中對(duì)應(yīng)的 標(biāo)簽中添加android:configChanges
屬性,并指定要忽略的配置變化。例如,可以添加 orientation 和 screenSize 以忽略橫豎屏切換
3.onStop(): 當(dāng)屏幕旋轉(zhuǎn)完成并且新的布局已經(jīng)加載完畢后,系統(tǒng)會(huì)調(diào)用當(dāng)前Activity的onStop()
方法。此時(shí),Activity已經(jīng)完全不可見(jiàn)。
4.onDestroy(): 如果屏幕旋轉(zhuǎn)導(dǎo)致當(dāng)前Activity被銷(xiāo)毀并重新創(chuàng)建,則系統(tǒng)會(huì)調(diào)用當(dāng)前Activity的onDestroy()
方法。你可以在此方法中釋放資源和執(zhí)行清理操作。
5.onCreate(): 在屏幕旋轉(zhuǎn)導(dǎo)致Activity重新創(chuàng)建時(shí),系統(tǒng)會(huì)調(diào)用當(dāng)前Activity的onCreate()
方法。你可以在此方法中重新初始化UI和恢復(fù)之前保存的數(shù)據(jù)。
6.onStart(): 在屏幕旋轉(zhuǎn)之后,系統(tǒng)會(huì)調(diào)用當(dāng)前Activity的onStart()
方法。此時(shí),Activity已經(jīng)可見(jiàn),但還沒(méi)有獲得焦點(diǎn)。
7.onResume(): 最后,系統(tǒng)會(huì)調(diào)用當(dāng)前Activity的onResume()
方法。在此方法中,你可以恢復(fù)之前的操作,并開(kāi)始響應(yīng)用戶的交互。
當(dāng)屏幕旋轉(zhuǎn)時(shí),Activity會(huì)被銷(xiāo)毀并重新創(chuàng)建,因此需要適當(dāng)?shù)靥幚頂?shù)據(jù)保存和恢復(fù)的邏輯,以保證用戶體驗(yàn)的連續(xù)性。onSaveInstanceState()
方法保存和恢復(fù)Activity的狀態(tài)信息,在onCreate()
和onRestoreInstanceState()
方法中進(jìn)行處理。
想讓某些配置在發(fā)生改變的時(shí)候不重啟Activity,需要為Activity添加android:configChanges屬性
后臺(tái)應(yīng)用被系統(tǒng)殺死 onDestroy()
具有返回值的啟動(dòng) onActivityResult -> OnRestart -> OnResume
重復(fù)啟動(dòng) onPause -> onNewIntent -> onResume
退出當(dāng)前 Activity 時(shí)–>onPause()–>onStop()–>onDestroy()
activity前后臺(tái)切換場(chǎng)景 生命周期
點(diǎn)擊 home 鍵回到桌面–>onPause()–>onStop()
再次回到原 Activity–>onRestart()–>onStart()–>onResume()
一個(gè)activity切換到另一個(gè)activity怎么傳遞數(shù)據(jù)和生命周期
使用Intent傳遞消息
從ActJumpActivity跳轉(zhuǎn)到ActNextActivity,調(diào)用方法的順序?yàn)椋荷弦粋€(gè)頁(yè)面onPause→下一個(gè)頁(yè)面 onCreate→onStart→onResume→上一個(gè)頁(yè)面onStop。
一個(gè)軟件切換到另一個(gè)軟件 怎么傳遞數(shù)據(jù)和生命周期
一個(gè)軟件切換到另一個(gè)軟件可以通過(guò)以下兩種方式傳遞數(shù)據(jù)1:
使用Intent與BroadCastReceiver沿著在應(yīng)用之間傳遞數(shù)據(jù)
1)應(yīng)用內(nèi)的動(dòng)態(tài)廣播接收器特定事件調(diào)用sendBroadcast(Intent)上的一個(gè)活動(dòng)
不要像上面提到的那樣調(diào)用Activity。使用某個(gè)操作在appTwo中注冊(cè)廣播接收器,并使用appTwo中提到的相同操作從appOne中發(fā)送Broadacst Intent。因此,當(dāng)您觸發(fā)sendbradcast時(shí)(意圖)來(lái)自appOne,監(jiān)聽(tīng)該Intent的appTwo將被觸發(fā),此時(shí)您將調(diào)用appTwo的Activity(您的主活動(dòng))在appTwo的onReceive方法中檢查您的活動(dòng)是否處于活動(dòng)狀態(tài)(如果活動(dòng)處于活動(dòng)狀態(tài),則從appTwo發(fā)送Broadcast(Intent)以刷新appTwo中的消息(如果未調(diào)用活動(dòng)))
當(dāng)一個(gè)安卓軟件切換到另一個(gè)軟件時(shí),兩個(gè)軟件的生命周期會(huì)分別經(jīng)歷以下過(guò)程
-
第一個(gè)軟件:
第一次啟動(dòng):onCreate() -> onStart() -> onResume()。
打開(kāi)新的軟件或回到桌面:onPause() -> onStop()。
再次回到第一個(gè)軟件:onRestart() -> onStart() -> onResume()。
按back鍵回退:onPause() -> onStop() -> onDestroy()。 -
第二個(gè)軟件:
第一次啟動(dòng):onCreate() -> onStart() -> onResume()。
切換到第一個(gè)軟件或回到桌面:onPause() -> onStop()。
再次回到第二個(gè)軟件:onRestart() -> onStart() -> onResume()。
按back鍵回退:onPause() -> onStop() -> onDestroy()。
當(dāng)內(nèi)存不足將導(dǎo)致低優(yōu)先級(jí)的Activity被殺死
優(yōu)先級(jí)分為三級(jí)
- 前臺(tái)Activity-與用戶正在交互
- 可見(jiàn)但非前臺(tái)Activity-比如說(shuō)彈出一個(gè)對(duì)話框,導(dǎo)致Activity可見(jiàn)但與用戶不可交互
-
后臺(tái)Activity-已經(jīng)被暫停的Activity,比如說(shuō)執(zhí)行了onStop,優(yōu)先級(jí)最低
當(dāng)內(nèi)存不足,會(huì)以此優(yōu)先級(jí)以此殺死Activity所在進(jìn)程,無(wú)四大組件運(yùn)行的進(jìn)程,很容易被殺死
Activity四大啟動(dòng)模式
Standard:標(biāo)準(zhǔn)模式(系統(tǒng)默認(rèn)模式),當(dāng)啟動(dòng)一個(gè)Activity,就會(huì)創(chuàng)建一個(gè)實(shí)例,無(wú)論實(shí)例是否已存在。Standard的Activity默認(rèn)進(jìn)入啟動(dòng)它的Activity所屬的工作棧中(若ActivityA啟動(dòng)了ActivityB(標(biāo)準(zhǔn)模式),ActivityB就會(huì)進(jìn)入ActivityA的工作棧)
SingleTop:棧頂復(fù)用模式,當(dāng)此Activity位于棧頂時(shí),將不會(huì)創(chuàng)建新實(shí)例,同時(shí)回調(diào)它的onNewIntent方法,通過(guò)此參數(shù)可以取出當(dāng)前請(qǐng)求的信息。但若此Activity不位于棧頂,將會(huì)重新創(chuàng)建
SingleTask:棧內(nèi)復(fù)用模式,單實(shí)例模式,若有Activity所需任務(wù)棧,則看此Activity是否存在于棧中,當(dāng)此Activity存在,則調(diào)用此Activity位于棧頂,若此Activity未存在于棧中,則創(chuàng)建此Activity實(shí)例壓入棧中(默認(rèn)有clearTop效果,將位于它之上的Activity全部出棧)
SingleIntance:?jiǎn)为?dú)實(shí)例模式,加強(qiáng)版SingleTask模式,此種模式的Activity只能單獨(dú)存在于一個(gè)工作棧,且不許后續(xù)創(chuàng)建新Activity
Activity標(biāo)志位FLAGS
- FLAG_ACTIVITY_NEW_TASK
此標(biāo)志位作用是為Activity指定SingleTask(棧內(nèi)復(fù)用模式)啟動(dòng)模式,其效果和在XML中指定相同 - FLAG_ACTIVITY_SINGLE_TOP
此標(biāo)志位作用是為Activity指定SingleTop(棧頂復(fù)用模式)啟動(dòng)模式,其效果和在XML中指定相同 - FLAG_ACTIVITY_CLEAR_TOP
具有此標(biāo)志位的Activity,啟動(dòng)時(shí)位于它棧頂?shù)腁ctivity都要出棧,通常與SingleTask啟動(dòng)模式聯(lián)用。標(biāo)志位與SingleTask(棧內(nèi)復(fù)用模式)聯(lián)用時(shí),會(huì)把它連同它之上的Activity出棧,并創(chuàng)建新Activity位于棧頂(SingleTask啟動(dòng)模式默認(rèn)有此標(biāo)志位效果) - FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
具有此標(biāo)志位的Activity不會(huì)出現(xiàn)在歷史Activity列表中,例如 點(diǎn)擊進(jìn)入順序:A->B->C,B具有此標(biāo)志位,當(dāng)從C返回時(shí)直接返回A,而不經(jīng)過(guò)B
2 .service
(1)service用于在后臺(tái)完成用戶指定的操作,無(wú)可視化界面展示。需要在應(yīng)用程序配置文件中聲明全部的service,使用<service> </service>
標(biāo)簽Service通常位于后臺(tái)運(yùn)行,它一般不需要與用戶交互,因此Service組件沒(méi)有圖形用戶界面。Service組件需要繼承Service基類(lèi)。Service組件通常用于為其他組件提供后臺(tái)服務(wù)或監(jiān)控其他組件的運(yùn)行狀態(tài)。
service生命周期
onCreate:若Service沒(méi)被創(chuàng)建過(guò),將調(diào)用startService()執(zhí)行onCreate,若已創(chuàng)建Service,多次調(diào)用startService
不會(huì)執(zhí)行onCreate
onStartComand:服務(wù)啟動(dòng)時(shí)調(diào)用,完成一些數(shù)據(jù)加載工作
onBind:服務(wù)被綁定時(shí)調(diào)用
onUnBind:服務(wù)和組件解綁時(shí)調(diào)用
onDestroy:服務(wù)停止時(shí)調(diào)用
兩種啟動(dòng)方式
startService:應(yīng)用組件通過(guò)startService()
啟動(dòng)服務(wù),服務(wù)處于started狀態(tài),生命周期與組件無(wú)關(guān),若無(wú)組件調(diào)用stopService
或服務(wù)自身調(diào)用stopSelf
,則會(huì)在后臺(tái)一直調(diào)用
bindService:應(yīng)用組件和服務(wù)同生共死,服務(wù)處于Bound狀態(tài),組件可調(diào)用unbindService()
方法與服務(wù)解綁,回調(diào)順序?yàn)閛nUnbind->onDestroy
當(dāng)所有組件與服務(wù)解綁后,服務(wù)會(huì)自動(dòng)被殺死
保證Service不被殺死
提高Service優(yōu)先級(jí)
系統(tǒng)監(jiān)聽(tīng)Service狀態(tài)
開(kāi)啟耗時(shí)操作
不能在主線程中進(jìn)行耗時(shí)操作(會(huì)導(dǎo)致ANR
2. 線程、同步、異步:Android中的ANR的解決方法),開(kāi)啟子線程進(jìn)行耗時(shí)操作
使用線程和Handler方式
IntentService
對(duì)比父類(lèi)Service,其回調(diào)函數(shù)onHandlerIntent
中可以進(jìn)行耗時(shí)操作,不必再開(kāi)線程。當(dāng)多次調(diào)用onHandlerIntent
時(shí)多個(gè)耗時(shí)任務(wù)會(huì)按順序依次執(zhí)行(因?yàn)閮?nèi)置Handler關(guān)聯(lián)了任務(wù)隊(duì)列)但僅用Service進(jìn)行耗時(shí)操作就很難管理
ActivityManagerService
從字面意義上看,活動(dòng)管理服務(wù),管理Android四大組件的啟動(dòng)、運(yùn)行管理調(diào)度等
startService()與bindService() 區(qū)別:
-
started service
(啟動(dòng)服務(wù))是由其他組件調(diào)用startService
方法啟動(dòng)的,這導(dǎo)致服務(wù)的onStartCommand()
方法被調(diào)用。當(dāng)服務(wù)是started狀態(tài)時(shí),其生命周期與啟動(dòng)它的組件無(wú)關(guān)并且可以在后臺(tái)無(wú)限期運(yùn)行,即使啟動(dòng)服務(wù)的組件已經(jīng)被銷(xiāo)毀。因此,服務(wù)需要在完成任務(wù)后 調(diào)用stopSelf()
方法停止,或者由其他組件調(diào)用stopService()
方法停止 - 使用
bindService()
方法啟用服務(wù),調(diào)用者與服務(wù)綁定在了一起,調(diào)用者一旦退出,服務(wù)也就終止,大有“不求同時(shí)生,必須同時(shí)死”的特點(diǎn)。
3 .content provider
以封裝的方式提供統(tǒng)一訪問(wèn)接口供不同應(yīng)用進(jìn)程訪問(wèn)同一共享數(shù)據(jù)
三個(gè)重要函數(shù):
ContentProdiver:以封裝形式提供統(tǒng)一訪問(wèn)接口
ContentResolver:根據(jù)不同的URI對(duì)ContentProdiver
進(jìn)行不同操作
ContentObserver:觀察ContentProdiver
數(shù)據(jù)變化并傳遞變化
- android平臺(tái)提供了
Content Provider
使一個(gè)應(yīng)用程序的指定數(shù)據(jù)集提供給其他應(yīng)用程序。其他應(yīng)用可以通過(guò)ContentResolver
類(lèi)從該內(nèi)容提供者中獲取或存入數(shù)據(jù)。 - 只有需要在多個(gè)應(yīng)用程序間共享數(shù)據(jù)是才需要內(nèi)容提供者。例如,通訊錄數(shù)據(jù)被多個(gè)應(yīng)用 程序使用,且必須存儲(chǔ)在一個(gè)內(nèi)容提供者中。它的好處是統(tǒng)一數(shù)據(jù)訪問(wèn)方式
- ContentProvider實(shí)現(xiàn)數(shù)據(jù)共享。
ContentProvider
用于保存和獲取數(shù)據(jù),并使其對(duì) 所有應(yīng)用程序可見(jiàn)。這是不同應(yīng)用程序間共享數(shù)據(jù)的唯一方式,因?yàn)閍ndroid沒(méi)有提供所有應(yīng)用共同訪問(wèn)的公共存儲(chǔ)區(qū) - 開(kāi)發(fā)人員不會(huì)直接使用
ContentProvider
類(lèi)的對(duì)象,大多數(shù)是通過(guò)ContentResolver
對(duì)象實(shí)現(xiàn)對(duì)ContentProvider
的操作。 -
ContentProvider
使用URI
來(lái)唯一標(biāo)識(shí)其數(shù)據(jù)集,這里的URI以content://作為前綴,表示該數(shù)據(jù)由ContentProvider
來(lái)管理。
4 .broadcast receiver
在應(yīng)用程序之間傳輸信息的機(jī)制,能夠?qū)V播進(jìn)行過(guò)濾并做出響應(yīng)
(1)你的應(yīng)用可以使用它對(duì)外部事件進(jìn)行過(guò)濾,只對(duì)感興趣的外部事件(如當(dāng)電話呼入時(shí),或者 數(shù)據(jù)網(wǎng)絡(luò)可用時(shí))進(jìn)行接收并做出響應(yīng)。廣播接收器沒(méi)有用戶界面。然而,它們可以啟動(dòng)一個(gè) activity或 serice來(lái)響應(yīng)它們收到的信息,或者用NotificationManager
來(lái)通知用戶。通知可以用很多種方式來(lái)吸引用戶的注意力,例如閃動(dòng)背燈、震動(dòng)、播放聲音等。一般來(lái)說(shuō)是在狀 態(tài)欄上放一個(gè)持久的圖標(biāo),用戶可以打開(kāi)它并獲取消息。
廣播形式:
本地廣播:只在應(yīng)用內(nèi)部傳播,安全但只能用動(dòng)態(tài)注冊(cè)
有序廣播:順序接收廣播并處理,同一時(shí)刻只有一個(gè)廣播能接收同一條數(shù)據(jù),優(yōu)先級(jí)越高的廣播優(yōu)先接收
普通廣播:intent的廣播,傳給所有廣播接收者,順序隨機(jī)
粘性廣播:等待對(duì)應(yīng)的廣播接收者注冊(cè)后,則結(jié)束滯留狀態(tài)與之匹配
兩種注冊(cè)方式:
靜態(tài)注冊(cè):AndroidManifest
文件中注冊(cè),只要設(shè)備開(kāi)啟,廣播則打開(kāi)
動(dòng)態(tài)注冊(cè):使用Context.registerRecevie
()注冊(cè),當(dāng)注冊(cè)的Activity關(guān)閉后,廣播即失效
如果后臺(tái)的activity由于某種原因被系統(tǒng)回收了,如何在被系統(tǒng)回收之前保存當(dāng)前狀態(tài)?
- Activity狀態(tài)的常用方法:onSaveInstanceState() 和onRestoreInstanceState() 方法:
在Activity被銷(xiāo)毀之前,系統(tǒng)會(huì)調(diào)用onSaveInstanceState()
方法。我們可以在該方法中保存需要恢復(fù)的數(shù)據(jù)到 Bundle 對(duì)象中。
在Activity重新創(chuàng)建后,系統(tǒng)會(huì)調(diào)用onRestoreInstanceState()
方法,并將之前保存的 Bundle 對(duì)象作為參數(shù)傳遞給該方法。我們可以從 Bundle 中恢復(fù)之前保存的數(shù)據(jù),并進(jìn)行相應(yīng)的處理。 -
使用 SharedPreferences:
在 Activity 的onDestroy()
方法中,將需要保存的狀態(tài)數(shù)據(jù)存儲(chǔ)到 SharedPreferences 中。
在 Activity 的onCreate()
方法中,從SharedPreferences
中讀取數(shù)據(jù),并進(jìn)行恢復(fù)。 -
使用數(shù)據(jù)庫(kù)或文件存儲(chǔ):
在 Activity 的onDestroy()
方法中,將需要保存的狀態(tài)數(shù)據(jù)存儲(chǔ)到數(shù)據(jù)庫(kù)或文件中。
在 Activity 的onCreate()
方法中,從數(shù)據(jù)庫(kù)或文件中讀取數(shù)據(jù),并進(jìn)行恢復(fù)。
需要注意的是,以上方法并不能保證在所有情況下都能完全恢復(fù) Activity 的狀態(tài)。在某些極端情況下,如系統(tǒng)內(nèi)存嚴(yán)重不足或用戶主動(dòng)關(guān)閉應(yīng)用等情況下,Activity 可能無(wú)法被恢復(fù)到之前的狀態(tài)。因此,除了保存和恢復(fù) Activity 狀態(tài)外,還應(yīng)該設(shè)計(jì)合理的應(yīng)用程序結(jié)構(gòu)和邏輯,以確保用戶體驗(yàn)的連貫性。
LaunchMode 的應(yīng)用場(chǎng)景
LaunchMode 有四種,分別為 Standard,SingleTop,SingleTask 和 SingleInstance
,每種模式的實(shí)現(xiàn)原理一樓都做了較詳細(xì)說(shuō)明,下面說(shuō)一下具體使用場(chǎng)景:
- Standard:
- Standard 模式是
系統(tǒng)默認(rèn)的啟動(dòng)模式
,一般我們 app中大部分頁(yè)面都是由該模式的頁(yè)面構(gòu)成的,比較常見(jiàn)的場(chǎng)景是:社交應(yīng)用中,點(diǎn)擊查看用戶A信息->查看用戶A粉絲->在粉絲中挑選查看用戶B信息->查看用戶A粉絲…這種情況下一般我們需要保留用戶操作 Activity 棧的頁(yè)面所有執(zhí)行順序。
- Standard 模式是
- SingleTop:
- SingleTop 模式一般常見(jiàn)于社交應(yīng)用中的通知欄行為功能,例如:App 用戶收到幾條好友請(qǐng)求的推送消息,需要用戶點(diǎn)擊推送通知進(jìn)入到請(qǐng)求者個(gè)人信息頁(yè),將信息頁(yè)設(shè)置為 SingleTop 模式就可以增強(qiáng)復(fù)用性。
- SingleTask:
- SingleTask 模式一般用作應(yīng)用的首頁(yè),例如瀏覽器主頁(yè),用戶可能從多個(gè)應(yīng)用啟動(dòng)瀏覽器,但主界面僅僅啟動(dòng)一次,其余情況都會(huì)走onNewIntent,并且會(huì)清空主界面上面的其他頁(yè)面。
- SingleInstance:
- SingleInstance 模式常應(yīng)用于獨(dú)立棧操作的應(yīng)用,如鬧鐘的提醒頁(yè)面,當(dāng)你在A應(yīng)用中看視頻時(shí),鬧鐘響了,你點(diǎn)擊鬧鐘提醒通知后進(jìn)入提醒詳情頁(yè)面,然后點(diǎn)擊返回就再次回到A的視頻頁(yè)面,這樣就不會(huì)過(guò)多干擾到用戶先前的操作了。
如何獲取綁定后的Service的方法
onBind
回調(diào)方法將返回給客戶端一個(gè)Binder接口實(shí)例,Binder允許客戶端回調(diào)服務(wù)的方法,比如得到Service運(yùn)行的狀態(tài)或其他操作。我們需要Binder對(duì)象返回具體的Service對(duì)象才能操作,所以說(shuō)具體的Service對(duì)象必須首先實(shí)現(xiàn)Binder對(duì)象
6. 機(jī)制
1.view的事件分發(fā)機(jī)制
一個(gè)點(diǎn)擊事件產(chǎn)生后,它的事件傳遞順序遵循:Activity->Window->DecorView->ViewGroup->View
View的事件分發(fā)機(jī)制主要由事件分發(fā)->事件攔截->事件處理三步來(lái)進(jìn)行邏輯控制
事件分發(fā):dispatchTouchEvent
用來(lái)進(jìn)行事件的分發(fā),如果事件能夠傳遞給當(dāng)前View,則該方法一定會(huì)被調(diào)用。返回結(jié)果受當(dāng)前View的onTouchEvent
和下級(jí)的dispatchTouchEvent
的影響,表示是否消耗當(dāng)前事件。
return:
ture:當(dāng)前View消耗所有事件
false:停止分發(fā),交由上層控件的onTouchEvent
方法進(jìn)行消費(fèi),如果本層控件是Activity,則事件將被系統(tǒng)消費(fèi),處理
事件攔截:onInterceptTouchEvent
需注意的是在Activity,ViewGroup,View中只有ViewGroup有這個(gè)方法。故一旦有點(diǎn)擊事件傳遞給View,則View
的onTouchEvent
方法就會(huì)被調(diào)用。
在dispatchTouch Event
內(nèi)部使用,用來(lái)判斷是否攔截事件。如果當(dāng)前View攔截了某個(gè)事件,那么該事件序列的其它方法也由當(dāng)前View處理,故該方法不會(huì)被再次調(diào)用,因?yàn)橐呀?jīng)無(wú)須詢問(wèn)它是否要攔截該事件。
return:
ture:對(duì)事件攔截,交給本層的onTouchEvent
進(jìn)行處理
false:不攔截,分發(fā)到子View,由子View的dispatchTouchEvent
進(jìn)行處理super.onInterceptTouchEvent(ev)
:默認(rèn)不攔截
事件處理:onTouchEvent
在dispatchTouchEvent
中調(diào)用,用來(lái)處理點(diǎn)擊事件,返回結(jié)果表示是否消耗當(dāng)前事件,如果不消耗,則在同一事件序列中,當(dāng)前View無(wú)法再接受到剩下的事件,并且事件將重新交給它的父元素處理,即父元素的onTouchEvent
會(huì)被調(diào)用。
return:
true:表示onTouchEvent
處理后消耗了當(dāng)前事件
false:不響應(yīng)事件,不斷的傳遞給上層的onTouchEvent
方法處理,直到某個(gè)View的onTouchEvent
返回true,則認(rèn)為該事件被消費(fèi),如果到最頂層View還是返回false,則該事件不消費(fèi),將交由Activity的onTouchEvent
處理。super.onTouchEvent(ev)
:默認(rèn)消耗當(dāng)前事件,與返回true一致。
-
Activity對(duì)點(diǎn)擊事件的分發(fā)過(guò)程
點(diǎn)擊事件用MotionEvent
來(lái)表示,當(dāng)一個(gè)點(diǎn)擊事件發(fā)生后,首先會(huì)傳遞給當(dāng)前的Activity,,由Activity的dispatchTouchEvent
方法來(lái)進(jìn)行事件派發(fā),具體是由Actiivty內(nèi)部的window來(lái)完成的。window會(huì)將事件傳遞給decorView
,decorView
一般為當(dāng)前界面的底層容器,通過(guò)Activity.getWindow.getDecorView()
可以獲得,Activity的點(diǎn)擊事件由當(dāng)前activity的附屬window進(jìn)行分發(fā),如果返回ture,則整個(gè)事件的循環(huán)結(jié)束了。如果返回false,則事件沒(méi)有人處理,當(dāng)所有View的onTouchEvent
方法都返回false時(shí),那么Activity的onTouchEvent
方法會(huì)被調(diào)用。 -
window點(diǎn)擊事件的分發(fā)過(guò)程
PhoneWindow
(Window是個(gè)抽象類(lèi),PhoneWindow
是Window的實(shí)現(xiàn)類(lèi))將事件直接傳遞給了DecorView.DecorView
是PhoneWindow
中一個(gè)內(nèi)部類(lèi),在PhoneWindow
中有這樣一句。getWindow().getDecorView()
返回的是一個(gè)DecorView
。我們可以通過(guò)一個(gè)setContenView
設(shè)置的View是DecorView
的子view,由于DecorView
是繼承FramLayout
而且是View的父元素,所以點(diǎn)擊事件一定會(huì)傳遞給view
,這個(gè)view就是頂級(jí)view,DecorView
是根view。 -
頂級(jí)View對(duì)點(diǎn)擊事件的分發(fā)過(guò)程
頂級(jí)view一般是ViewGroup
,當(dāng)點(diǎn)擊事件傳遞到頂級(jí)的ViewGroup
時(shí),ViewGroup
會(huì)調(diào)用自己的dispatchTouchEvent
方法,一層層分發(fā)下去,最后傳遞給view的onTouchEvent
方法。
具體傳遞順序:對(duì)于一個(gè)根ViewGroup
來(lái)講,首先會(huì)調(diào)用dispatchTouchEvent
,如果此時(shí)它的onInterceptTouchEvent
方法返回ture,表示攔截當(dāng)前的事件,說(shuō)明本次事件交給當(dāng)前的ViewGroup
處理,調(diào)用它的onTouchEvent
方法。如果返回false,表示不攔截此次事件,繼續(xù)分發(fā)給它的子元素,接著它的子元素的dispatchTouchEvent
接著會(huì)被調(diào)用,如此反復(fù)直到事件的結(jié)束。
7. 性能、內(nèi)存
1.GC是什么,為什么要有GC
答:GC (Garbage Collection
) 是一種自動(dòng)內(nèi)存管理機(jī)制,它是一種用于自動(dòng)檢測(cè)和回收不再使用的內(nèi)存的機(jī)制。在編程語(yǔ)言中,特別是在像Java、C#等高級(jí)語(yǔ)言中,GC負(fù)責(zé)自動(dòng)管理內(nèi)存的分配和釋放,以減少開(kāi)發(fā)人員手動(dòng)管理內(nèi)存的負(fù)擔(dān)。
GC的主要目的是解決內(nèi)存泄漏和內(nèi)存碎片化的問(wèn)題。內(nèi)存泄漏指的是程序中分配的內(nèi)存沒(méi)有被正確釋放,導(dǎo)致內(nèi)存占用不斷增加,最終導(dǎo)致系統(tǒng)性能下降甚至崩潰。而內(nèi)存碎片化是指內(nèi)存中存在大量無(wú)法利用的碎片空間,這些碎片空間雖然總和很大,但無(wú)法滿足大塊內(nèi)存的分配請(qǐng)求。
GC的工作原理是通過(guò)周期性地檢測(cè)和標(biāo)記不再使用的對(duì)象,并將其回收釋放。它會(huì)自動(dòng)追蹤對(duì)象之間的引用關(guān)系,當(dāng)一個(gè)對(duì)象不再被其他對(duì)象引用時(shí),就認(rèn)為該對(duì)象可以被回收。GC會(huì)從根對(duì)象(如全局變量、活動(dòng)線程等)開(kāi)始遍歷整個(gè)對(duì)象圖,標(biāo)記所有可達(dá)的對(duì)象,然后清理掉未標(biāo)記的對(duì)象。
GC的存在有以下幾個(gè)原因:
- 簡(jiǎn)化內(nèi)存管理:GC可以自動(dòng)處理內(nèi)存的分配和釋放,減輕了開(kāi)發(fā)人員的負(fù)擔(dān)。開(kāi)發(fā)者無(wú)需手動(dòng)跟蹤和釋放對(duì)象,不用擔(dān)心內(nèi)存泄漏和野指針等問(wèn)題。
- 避免內(nèi)存泄漏:GC可以檢測(cè)不再使用的對(duì)象,并及時(shí)回收釋放內(nèi)存,避免了內(nèi)存泄漏的問(wèn)題。
- 解決內(nèi)存碎片化:GC可以對(duì)內(nèi)存進(jìn)行整理和合并,減少內(nèi)存碎片的產(chǎn)生,提高內(nèi)存的利用率。
-
提升性能和穩(wěn)定性:GC可以在程序運(yùn)行時(shí)動(dòng)態(tài)地回收垃圾對(duì)象,釋放內(nèi)存資源,減少了內(nèi)存占用和頻繁的內(nèi)存分配/釋放操作,從而提升了程序的性能和穩(wěn)定性。
總之,GC的存在使得開(kāi)發(fā)者更專(zhuān)注于業(yè)務(wù)邏輯的實(shí)現(xiàn),減少了手動(dòng)內(nèi)存管理的復(fù)雜性,提高了代碼的可維護(hù)性和可靠性。
2、Android性能優(yōu)化
- 啟動(dòng)優(yōu)化:
application
中不要做大量耗時(shí)操作,如果必須的話建議做異步耗時(shí)操作 - 布局優(yōu)化: 使用合理的控件選擇,少嵌套。 (合理使用
include,merge,viewStub
等使用) - apk優(yōu)化(資源文件優(yōu)化,代碼優(yōu)化,lint檢查,png合理使用shape替代圖片,webp等)
- 性能優(yōu)化,網(wǎng)絡(luò)優(yōu)化,電量?jī)?yōu)化
- 避免輪詢,盡量使用推送
- 應(yīng)用處于后臺(tái)時(shí),禁用某些數(shù)據(jù)傳輸限制訪問(wèn)頻率,失敗后不要無(wú)限重連選用合適的定位服務(wù)(GPS定位,網(wǎng)絡(luò)定位,被動(dòng)定 位)
- 使用緩存
-
startActivityForResult
替代發(fā)送廣播內(nèi)存
- 優(yōu)化
- 循環(huán)盡量不使用局部變量
- 避免在
onDraw
中創(chuàng)建對(duì)象,onDraw會(huì)被頻繁調(diào)用,容易造成內(nèi)存抖動(dòng)。循環(huán)中創(chuàng)建大的對(duì)象,也是如此。 - 不用的對(duì)象及時(shí)釋放
- 數(shù)據(jù)庫(kù)的
cursor
及時(shí)關(guān)閉 -
adapter
使用緩存 - 注冊(cè)廣播后,在生命周期結(jié)束時(shí)反注冊(cè)
- 及時(shí)關(guān)閉流操作
- 圖片盡量使用軟引用,較大的圖片可以通過(guò)
bitmapFactory
縮放后再使用,并及時(shí)recycler
。另外加載巨圖時(shí)不要 使用setlmageBitmap
或setlmageResourse
或BitmapFactory.decodeResource
,這些方法拿到的都是bitmap
的對(duì)象,占用內(nèi)存較大??梢杂?code>BitmapFactory.decodeStream方法配合BitmapFactory.Options
進(jìn)行縮放 - 避免static成員變量引用資源耗費(fèi)過(guò)多實(shí)例。
- 避免靜態(tài)內(nèi)部類(lèi)的引用
3、什么情況下會(huì)導(dǎo)致內(nèi)存泄漏問(wèn)題
- 資源對(duì)象沒(méi)關(guān)閉造成的內(nèi)存泄漏(如:
Cursor
、File
等) -
Listview
的Adapter
中沒(méi)有使用緩存的Convertview -
Bitmap
對(duì)象不在使用時(shí)調(diào)用recycle
()釋放內(nèi)存 - 集合中對(duì)象沒(méi)清理造成的內(nèi)存泄漏(特別是static 修飾集合)
- 接收器、監(jiān)聽(tīng)器注冊(cè)沒(méi)取消造成的內(nèi)存泄漏
-
Activity
的context
造成的泄漏,可以使用ApplicationContext
-
Handler
造成的內(nèi)存泄漏問(wèn)題 (一般由于 Handler 生命周期比其外部類(lèi)的生命周期長(zhǎng)引起的)
8. 數(shù)據(jù)庫(kù)
1.SQLite 數(shù)據(jù)庫(kù)升級(jí)——新增字段處理
SQLite 的 ALTER TABLE 命令不通過(guò)執(zhí)行一個(gè)完整的轉(zhuǎn)儲(chǔ)和數(shù)據(jù)的重載來(lái)修改已有的表。您可以使用 ALTER TABLE 語(yǔ)句重命名表,使用 ALTER TABLE 語(yǔ)句還可以在已有的表中添加額外的列。
9. 權(quán)限
1、 Android 程序運(yùn)行時(shí)權(quán)限與文件系統(tǒng)權(quán)限的區(qū)別
文件的系統(tǒng)權(quán)限是由linux系統(tǒng)規(guī)定的,只讀,讀寫(xiě)等。
運(yùn)行時(shí)權(quán)限,是對(duì)于某個(gè)系統(tǒng)上的app的訪問(wèn)權(quán)限,允許,拒絕,詢問(wèn)。該功能可以防止非法的程序訪問(wèn)敏感的信息
10. 權(quán)Android 系統(tǒng) SDK相關(guān)
1、Android系統(tǒng)的架構(gòu)組成
android系統(tǒng)分為四部分,從高到低分別是文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-617821.html
- Android應(yīng)用層
Android會(huì)同一系列核心應(yīng)用程序包一起發(fā)布,該應(yīng)用程序 包包括email客戶端,SMS短消息程序,日歷,地圖,瀏覽 器,聯(lián)系人管理程序等。所有的應(yīng)用程序都是使用JAVA語(yǔ) 言編寫(xiě)的。 - Android應(yīng)用框架層
開(kāi)發(fā)人員也可以完全訪問(wèn)核心應(yīng)用程序所使用的API框架該應(yīng)用程序的架構(gòu)設(shè)計(jì)簡(jiǎn)化了組件的重用;任何一個(gè)應(yīng)用程序都可以發(fā)布它的功能塊并且任何其它的應(yīng)用程序都可以使用其所發(fā)布的功能塊(不過(guò)得遵循框架的安全性限制)。同樣,該應(yīng)用程序重用機(jī)制也使用戶可以方便的替換程序組件。 - Android系統(tǒng)運(yùn)行層
Android 包含一些C/C++庫(kù),這些庫(kù)能被Android系統(tǒng)中不同的組件使用。它們通過(guò) Android 應(yīng)用程序框架為開(kāi)發(fā)者提供服務(wù)。 - Linux內(nèi)核層
Android 的核心系統(tǒng)服務(wù)依賴于 Linux 2.6 內(nèi)核,如安全性,內(nèi)存管理,進(jìn)程管理,網(wǎng)絡(luò)協(xié)議棧和驅(qū)動(dòng)模型Linux 內(nèi)核也同時(shí)作為硬件和軟件棧之間的抽象層
3. Serializable和Parcelable的區(qū)別?
Android中序列化有兩種方式: Serializable
以及Parcelable
。其中Serializable是Java自帶的
,而Parcelable是安卓專(zhuān)有的
。Seralizable相對(duì)Parcelable而言,好處就是非常簡(jiǎn)單,只 需對(duì)需要序列化的類(lèi)class執(zhí)行就可以,不需要手動(dòng)去處理 序列化和反序列化的過(guò)程,所以常常用于網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù)處理Activity之間傳遞值的使用。Parcelable是android特有的序列化API,它的出現(xiàn)是為了解決Serializable在序列化的過(guò)程中消耗資源嚴(yán)重的問(wèn)題,但是因?yàn)楸旧硎褂眯枰謩?dòng)處理序列化和反序列化過(guò)程,會(huì)與具體的代碼綁定,使用較為繁瑣,一般只獲取內(nèi)存數(shù)據(jù)的時(shí)候使用。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-617821.html
到了這里,關(guān)于安卓面試問(wèn)題記錄的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!