阿里面試
自我介紹
首先要介紹自己的專案經驗和個人的擅長點,因為面試官主要考察你的表達能力和語言精簡能力,
簡歷的撰寫其實這里可能需要注意幾點;
1.標題的直接按照姓名-幾年作業經驗-應聘崗位格式來填寫,例如 黃銘——四年作業經驗——Android開發高級工程師,因為要知道,HR篩選簡歷是非常快的,你要在第一時間就讓他看到他想要看的資訊,HR一開始最關心的就是作業年限和崗位,你直接在標題欄用最大的字體寫出來會比很多人介紹了半天專案最后寫年限要好得多,
2.個人資訊寫最前面, 個人資訊欄其實不用寫很多東西,最主要的就是姓名、聯系方式、性別、如果學校是名校那么就寫上,如果本科學的是計算機專業也可以在這里寫上,如果都不是就最好放到最后去寫,因為最前面的東西永遠是最先看到的內容,也是你最有競爭力和賣弄的資本,
####1.使用
LeakCancary 2.0使用,只需要配置如下代碼,便可以進行使用,比LeakCanary1.0不知道高到哪里去了~
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.0-alpha-2'
####2.原始碼分析
閱讀原始碼后可以看到leakcancary-leaksentry模塊的Androidmanifest檔案,可以看到下面的內容:
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.squareup.leakcanary.leaksentry"
>
<application>
<provider
android:name="leakcanary.internal.LeakSentryInstaller"
android:authorities="${applicationId}.leak-sentry-installer"
android:exported="false"/>
</application>
</manifest>
然后我們可以看到LeakSentryInstaller這個類到底做了什么
internal class LeakSentryInstaller : ContentProvider() {
override fun onCreate(): Boolean {
CanaryLog.logger = DefaultCanaryLog()
val application = context!!.applicationContext as Application
//利用系統自動呼叫ContentProvider的onCreate來進行安裝
InternalLeakSentry.install(application)
return true
}
...
至于為什么系統會呼叫ContentProvider的onCreate方法,我們可以看看原始碼,在ActivityThread中的H中的handleMessage可以看到
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
case BIND_APPLICATION:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
AppBindData data = (AppBindData)msg.obj;
//關鍵方法
handleBindApplication(data);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
然后在handleBindApplication中可以看到
// don't bring up providers in restricted mode; they may depend on the
// app's custom Application class
if (!data.restrictedBackupMode) {
if (!ArrayUtils.isEmpty(data.providers)) {
//contentprovider初始化,里面會呼叫onCreate方法
installContentProviders(app, data.providers);
}
}
// Do this after providers, since instrumentation tests generally start their
// test thread at this point, and we don't want that racing.
try {
mInstrumentation.onCreate(data.instrumentationArgs);
}
catch (Exception e) {
throw new RuntimeException(
"Exception thrown in onCreate() of "
+ data.instrumentationName + ": " + e.toString(), e);
}
try {
//app的onCreate方法呼叫
mInstrumentation.callApplicationOnCreate(app);
} catch (Exception e) {
具體呼叫contentprovider的onCreate代碼邏輯如下
@UnsupportedAppUsage
private void installContentProviders(
Context context, List<ProviderInfo> providers) {
final ArrayList<ContentProviderHolder> results = new ArrayList<>();
for (ProviderInfo cpi : providers) {
···
//installProvider方法
ContentProviderHolder cph = installProvider(context, null, cpi,
false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/);
if (cph != null) {
cph.noReleaseNeeded = true;
results.add(cph);
}
}
//installProvider方法,然后一步步跟進
//1
//XXX Need to create the correct context for this provider.
localProvider.attachInfo(c, info);
//2
public void attachInfo(Context context, ProviderInfo info) {
attachInfo(context, info, false);
}
//3
private void attachInfo(Context context, ProviderInfo info, boolean testing) {
mNoPerms = testing;
mCallingPackage = new ThreadLocal<>();
if (mContext == null) {
···
ContentProvider.this.onCreate();
}
}
通過上面的分析,可以知道在我們引入依賴后,依賴包中的AndroidMainfest.xml檔案便會主動合并到主AndroidManifest.xml檔案中,然后在程式啟動程序中便會自動創建ContentProvider,然后進行InternalLeakSentry.install(application),接下來進行一些列的監控和dump操作等,
#####2.1 InternalLeakSentry.install(application)
下面來分析InternalLeakSentry.install(application)里面都做了一些什么,可以看到
fun install(application: Application) {
CanaryLog.d("Installing LeakSentry")
checkMainThread()
if (this::application.isInitialized) {
return
}
InternalLeakSentry.application = application
val configProvider = { LeakSentry.config }
// 1.監聽 Activity.onDestroy()
ActivityDestroyWatcher.install(
application, refWatcher, configProvider
)
// 2.監聽 Fragment.onDestroy()
FragmentDestroyWatcher.install(
application, refWatcher, configProvider
)
// 3.監聽完成后進行一些初始化作業
listener.onLeakSentryInstalled(application)
}
從命名上可以看到在Activity和Fragment進行destory的時候進行watch
- ActivityDestroyWatcher
internal class ActivityDestroyWatcher private constructor(
private val refWatcher: RefWatcher,
private val configProvider: () -> Config
) {
private val lifecycleCallbacks = object : ActivityLifecycleCallbacksAdapter() {
override fun onActivityDestroyed(activity: Activity) {
if (configProvider().watchActivities) {
// 監聽到 onDestroy() 之后,通過 refWatcher 監測 Activity
refWatcher.watch(activity)
}
}
}
companion object {
fun install(
application: Application,
refWatcher: RefWatcher,
configProvider: () -> Config
) {
val activityDestroyWatcher =
ActivityDestroyWatcher(refWatcher, configProvider)
application.registerActivityLifecycleCallbacks(activityDestroyWatcher.lifecycleCallbacks)
}
}
}
- FragmentDestroyWatcher
internal interface FragmentDestroyWatcher {
fun watchFragments(activity: Activity)
companion object {
private const val SUPPORT_FRAGMENT_CLASS_NAME = "androidx.fragment.app.Fragment"
fun install(
application: Application,
refWatcher: RefWatcher,
configProvider: () -> LeakSentry.Config
) {
val fragmentDestroyWatchers = mutableListOf<FragmentDestroyWatcher>()
//大于等于android O
if (SDK_INT >= O) {
fragmentDestroyWatchers.add(
AndroidOFragmentDestroyWatcher(refWatcher, configProvider)
)
}
if (classAvailable(
SUPPORT_FRAGMENT_CLASS_NAME
)
) {
// androidx 使用 SupportFragmentDestroyWatcher
fragmentDestroyWatchers.add(
SupportFragmentDestroyWatcher(refWatcher, configProvider)
)
}
if (fragmentDestroyWatchers.size == 0) {
return
}
application.registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacksAdapter() {
override fun onActivityCreated(
activity: Activity,
savedInstanceState: Bundle?
) {
for (watcher in fragmentDestroyWatchers) {
watcher.watchFragments(activity)
}
}
})
}
private fun classAvailable(className: String): Boolean {
return try {
Class.forName(className)
true
} catch (e: ClassNotFoundException) {
false
}
}
}
}
Android O 及以后,androidx 都具備對 Fragment 生命周期的監聽功能,為什么不監聽Android O之前的呢???(待解決)
在版本為1.5.4之前是不支持Fragment記憶體泄漏監聽的,后面版本才加了進來,
- listener.onLeakSentryInstalled(application)
該listener的最終實作類是leakcanary-android-core中的InternalLeakCanary類
override fun onLeakSentryInstalled(application: Application) {
this.application = application
val heapDumper = AndroidHeapDumper(application, leakDirectoryProvider)
//用于發現可能的記憶體泄漏之后手動呼叫 GC 確認是否真的為記憶體泄露
val gcTrigger = GcTrigger.Default
val configProvider = { LeakCanary.config }
val handlerThread = HandlerThread(HeapDumpTrigger.LEAK_CANARY_THREAD_NAME)
handlerThread.start()
val backgroundHandler = Handler(handlerThread.looper)
//用于確認記憶體泄漏之后進行 heap dump 作業,
heapDumpTrigger = HeapDumpTrigger(
application, backgroundHandler, LeakSentry.refWatcher, gcTrigger, heapDumper, configProvider
)
application.registerVisibilityListener { applicationVisible ->
this.applicationVisible = applicationVisible
heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible)
}
addDynamicShortcut(application)
}
這里有個關于GC回收的知識點,我們可以看看優秀的第三方框架都是怎么寫的
interface GcTrigger {
fun runGc()
object Default : GcTrigger {
override fun runGc() {
// Code taken from AOSP FinalizationTest:
// https://android.googlesource.com/platform/libcore/+/master/support/src/test/java/libcore/
// java/lang/ref/FinalizationTester.java
// System.gc() does not garbage collect every time. Runtime.gc() is
// more likely to perform a gc.
Runtime.getRuntime()
.gc()
enqueueReferences()
System.runFinalization()
}
private fun enqueueReferences() {
// Hack. We don't have a programmatic way to wait for the reference queue daemon to move
// references to the appropriate queues.
try {
Thread.sleep(100)
} catch (e: InterruptedException) {
throw AssertionError()
}
}
}
}
可以看到,它使用了Runtime.getRuntime().gc()而不是System.gc(),進入System.gc原始碼一看
public static void gc() {
boolean shouldRunGC;
synchronized (LOCK) {
shouldRunGC = justRanFinalization;
if (shouldRunGC) {
justRanFinalization = false;
} else {
runGC = true;
}
}
if (shouldRunGC) {
Runtime.getRuntime().gc();
}
}
可以看到System.gc原始碼的還是最終實作是Runtime.getRuntime().gc();但是需要一系列的判斷條件,我們手動呼叫System.runFinalization()可以使gc方法中的justRanFinalizationw為true,從而保證Runtime.getRuntime().gc()會被執行,
####3.如何判斷物件可能泄露:ReferenceQueue含義及作用
在Activity/Fragment銷毀后,會進行一系列的物件回收,我們把這些物件分別和參考佇列進行關聯,當某個物件被回收時,**(弱參考一旦變成弱可達(可達性演算法分析),參考就會加到參考佇列中,然后再進行回收)**我們物件的參考就會被加入到參考佇列中,根據該原理進行一系列的操作,最終判斷是否記憶體泄漏,
#####3.1 參考佇列
通常我們將其ReferenceQueue翻譯為參考佇列,換言之就是存放參考的佇列,保存的是Reference物件,其作用在于Reference物件所參考的物件被GC回收時,該Reference物件將會被加入參考佇列中(ReferenceQueue)的佇列末尾,
ReferenceQueue常用的方法:
public Reference poll():從佇列中取出一個元素,佇列為空則回傳null;
public Reference remove():從佇列中出對一個元素,若沒有則阻塞至有可出隊元素;
public Reference remove(long timeout):從佇列中出對一個元素,若沒有則阻塞至有可出對元素或阻塞至超過timeout毫秒;
-
強參考
-
軟參考
-
弱參考
-
虛參考(Phantom Reference)
虛引等同于沒有參考,這意味著在任何時候都可能被GC回收,設定虛參考的目的是為了被虛參考關聯的物件在被垃圾回收器回收時,能夠收到一個系統通知,(被用來跟蹤物件被GC回收的活動)虛參考和弱參考的區別在于:虛參考在使用時必須和參考佇列(ReferenceQueue)聯合使用,其在GC回收期間的活動如下:
ReferenceQueue queue=new ReferenceQueue();
PhantomReference pr=new PhantomReference(object,queue);
也即是GC在回收一個物件時,如果發現該物件具有虛參考,那么在回收之前會首先該物件的虛參考加入到與之關聯的參考佇列中,程式可以通過判斷參考佇列中是否已經加入虛參考來了解被參考的物件是否被GC回收,
#####3.2 GC Root物件
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-23zFcSFz-1623419386976)(/Users/jackie/Library/Application Support/typora-user-images/image-20200723182524190.png)]
#####3.3 記憶體是否泄漏
知道參考佇列的原理后,先大概描述一下如何判斷是否泄漏,首先創建三個佇列
/**
* References passed to [watch] that haven't made it to [retainedReferences] yet.
* watch() 方法傳進來的參考,尚未判定為泄露
*/
private val watchedReferences = mutableMapOf<String, KeyedWeakReference>()
/**
* References passed to [watch] that we have determined to be retained longer than they should
* have been.
* watch() 方法傳進來的參考,已經被判定為泄露
*/
private val retainedReferences = mutableMapOf<String, KeyedWeakReference>()
private val queue = ReferenceQueue<Any>() // 參考佇列,配合弱參考使用
//KeyedWeakReference,物件和參考佇列進行弱參考關聯,所以這個物件一定會被回收
class KeyedWeakReference(
referent: Any,
val key: String,
val name: String,
val watchUptimeMillis: Long,
referenceQueue: ReferenceQueue<Any>
) : WeakReference<Any>(
referent, referenceQueue
) {
@Volatile
var retainedUptimeMillis = -1L
companion object {
@Volatile
@JvmStatic var heapDumpUptimeMillis = 0L
}
}
如果一個obj物件,它和佇列queue進行弱參考關聯,在進行垃圾收集時,發現該物件具有弱參考,會把參考加入到參考佇列中,我們如果在該佇列中拿到參考,則說明該物件被回收了,如果拿不到,則說明該物件還有強/軟參考未釋放,那么就說明物件還未回收,發生記憶體泄漏了,然后dump記憶體快照,使用第三方庫進行參考鏈分析,
這里重點強調一點一個物件可能被多個參考持有,比如強參考,軟參考,弱參考,只要這個物件還有強參考/軟參考,與這個物件關聯的任意參考佇列就拿不到參考,參考佇列就相當于一個通知,多個參考佇列和一個物件關聯,物件被回收時,多個佇列都會受到通知,
#####3.4 watch()
@Synchronized fun watch(
watchedReference: Any,
referenceName: String
) {
if (!isEnabled()) {
return
}
//移除佇列中將要被 GC 的參考
removeWeaklyReachableReferences()
val key = UUID.randomUUID()
.toString()
val watchUptimeMillis = clock.uptimeMillis()
val reference = // 構建當前參考的弱參考物件,并關聯參考佇列 queue
KeyedWeakReference(watchedReference, key, referenceName, watchUptimeMillis, queue)
if (referenceName != "") {
CanaryLog.d(
"Watching instance of %s named %s with key %s", reference.className,
referenceName, key
)
} else {
CanaryLog.d(
"Watching instance of %s with key %s", reference.className, key
)
}
watchedReferences[key] = reference
checkRetainedExecutor.execute {
//如果參考未被移除,則可能存在記憶體泄漏
moveToRetained(key)
}
}
removeWeaklyReachableReferences()
private fun removeWeaklyReachableReferences() {
// WeakReferences are enqueued as soon as the object to which they point to becomes weakly
// reachable. This is before finalization or garbage collection has actually happened.
// 弱參考一旦變得弱可達,就會立即入隊,這將在 finalization 或者 GC 之前發生,
var ref: KeyedWeakReference?
do {
ref = queue.poll() as KeyedWeakReference? // 佇列 queue 中的物件都是會被 GC 的
if (ref != null) {
val removedRef = watchedReferences.remove(ref.key)
if (removedRef == null) {
retainedReferences.remove(ref.key)
}
// 移除 watchedReferences 佇列中的會被 GC 的 ref 物件,剩下的就是可能泄露的物件
}
} while (ref != null)
}
moveToRetained()
@Synchronized private fun moveToRetained(key: String) {
removeWeaklyReachableReferences() // 再次呼叫,防止遺漏
val retainedRef = watchedReferences.remove(key)
if (retainedRef != null) {
retainedReferences[key] = retainedRef
onReferenceRetained()
}
}
最后會回呼到InternalLeakCanary的onReferenceRetained()方法
override fun onReferenceRetained() {
if (this::heapDumpTrigger.isInitialized) {
heapDumpTrigger.onReferenceRetained()
}
}
//1.HeapDumpTrigger 的 onReferenceRetained()
fun onReferenceRetained() {
scheduleRetainedInstanceCheck("found new instance retained")
}
//2.scheduleRetainedInstanceCheck
private fun scheduleRetainedInstanceCheck(reason: String) {
backgroundHandler.post {
checkRetainedInstances(reason)
}
}
//3.checkRetainedInstances
private fun checkRetainedInstances(reason: String) {
CanaryLog.d("Checking retained instances because %s", reason)
val config = configProvider()
// A tick will be rescheduled when this is turned back on.
if (!config.dumpHeap) {
return
}
var retainedKeys = refWatcher.retainedKeys
//當前泄露實體個數小于 5 個,不進行 heap dump
if (checkRetainedCount(retainedKeys, config.retainedVisibleThreshold)) return
if (!config.dumpHeapWhenDebugging && DebuggerControl.isDebuggerAttached) {
showRetainedCountWithDebuggerAttached(retainedKeys.size)
scheduleRetainedInstanceCheck("debugger was attached", WAIT_FOR_DEBUG_MILLIS)
CanaryLog.d(
"Not checking for leaks while the debugger is attached, will retry in %d ms",
WAIT_FOR_DEBUG_MILLIS
)
return
}
// 可能存在被觀察的參考將要變得弱可達,但是還未入隊參考佇列,
// 這時候應該主動呼叫一次 GC,可能可以避免一次 heap dump
gcTrigger.runGc()
retainedKeys = refWatcher.retainedKeys
if (checkRetainedCount(retainedKeys, config.retainedVisibleThreshold)) return
HeapDumpMemoryStore.setRetainedKeysForHeapDump(retainedKeys)
CanaryLog.d("Found %d retained references, dumping the heap", retainedKeys.size)
HeapDumpMemoryStore.heapDumpUptimeMillis = SystemClock.uptimeMillis()
dismissNotification()
val heapDumpFile = heapDumper.dumpHeap()
if (heapDumpFile == null) {
CanaryLog.d("Failed to dump heap, will retry in %d ms", WAIT_AFTER_DUMP_FAILED_MILLIS)
scheduleRetainedInstanceCheck("failed to dump heap", WAIT_AFTER_DUMP_FAILED_MILLIS)
showRetainedCountWithHeapDumpFailed(retainedKeys.size)
return
}
refWatcher.removeRetainedKeys(retainedKeys)
HeapAnalyzerService.runAnalysis(application, heapDumpFile)
}
一些細節可以看看代碼注釋,checkRetainedCount滿足個數的話,就要發起head dump,具體的邏輯在AndroidHeapDumper.dumpHeap()中:
override fun dumpHeap(): File? {
val heapDumpFile = leakDirectoryProvider.newHeapDumpFile() ?: return null
···
return try {
//Dump出檔案
Debug.dumpHprofData(heapDumpFile.absolutePath)
heapDumpFile
} catch (e: Exception) {
CanaryLog.d(e, "Could not dump heap")
// Abort heap dump
null
} finally {
cancelToast(toast)
notificationManager.cancel(R.id.leak_canary_notification_dumping_heap)
}
}
最后啟動一個前臺服務 HeapAnalyzerService 來分析 heap dump 檔案,老版本中是使用 Square 自己的 haha 庫來決議的,這個庫已經廢棄了,Square 完全重寫了決議庫,主要邏輯都在 moudle leakcanary-analyzer 中,這部分我還沒有閱讀,就不在這里分析了,對于新的決議器,官網是這樣介紹的:
Uses 90% less memory and 6 times faster than the prior heap parser.
減少了 90% 的記憶體占用,而且比原來快了 6 倍,后面有時間單獨來分析一下這個決議庫,
后面的程序就不再贅述了,通過決議庫找到最短 GC Roots 參考路徑,然后展示給用戶,
4.手動寫記憶體泄漏檢測
下面是參考Zero的Demo寫的記憶體泄漏檢測的一個例子,思路和LeakCanary一樣
fun main() {
class MyKeyedWeakReference(
referent: Any,
val key: String,
val name: String,
referenceQueue: ReferenceQueue<Any>
) : WeakReference<Any>(
referent, referenceQueue
) {
val className: String = referent.javaClass.name
override fun toString(): String {
return "{key=$key,className=$className}"
}
}
//需要觀察的物件
val watchedReferences = mutableMapOf<String,MyKeyedWeakReference>()
//如果最后retainedReferences還存在參考,說明泄漏了
val retainedReferences = mutableMapOf<String,MyKeyedWeakReference>()
//當與之關聯的弱參考中的實體被回收,則會加入到queue
val gcQueue = ReferenceQueue<Any>()
fun sleep(mills: Long){
try {
Thread.sleep(mills)
}catch (e: Exception){
e.printStackTrace()
}
}
fun gc(){
println("執行gc...")
Runtime.getRuntime().gc()
sleep(100)
System.runFinalization()
}
fun removeWeaklyReachableReferences(){
println("removeWeaklyReachableReferences")
var ref: MyKeyedWeakReference?
do {
ref = gcQueue.poll() as MyKeyedWeakReference? //佇列queue中的物件都是會被GC的
println("ref=$ref,如果ref為null,說明物件還有強參考")
if (ref != null){ //說明被釋放了
println("ref=$ref, 物件被釋放了,key=${ref.key}")
val removedRef = watchedReferences.remove(ref.key)
println("removedRef=$removedRef, 如果removedRef為null,說明已經不在watchedReferences了,key=${ref.key}")
if (removedRef == null){
//不在watchedReferences則說明在retainedReferences
retainedReferences.remove(ref.key)
}
}
}while (ref != null)
}
@Synchronized
fun moveToRetained(key: String){
println("5.moveToRetained,key=$key")
removeWeaklyReachableReferences()
val retainedRef = watchedReferences.remove(key)
println("retainedRef =$retainedRef 如果還有值說明沒有被釋放")
if (retainedRef != null){ //添加到retainedReferences
retainedReferences[key] = retainedRef
}
}
fun watch(
obj: Any,
referenceName: String = ""){
println("2.watch...")
removeWeaklyReachableReferences()
val key = UUID.randomUUID().toString()
println("3.key=$key")
val reference = MyKeyedWeakReference(obj,key,referenceName,gcQueue)
println("4.reference=$reference")
//加入觀察串列
watchedReferences[key] = reference
//過段時間查看是否釋放
thread(start = true){
sleep(5000)
moveToRetained(key)
}
}
var obj : Any? = Object()
println("1.創建一個物件obj=$obj")
watch(obj!!,"")
sleep(2000)
obj = null
if (obj == null){
println("obj=$obj 釋放了")
}
gc()
sleep(5000)
println("watchedReferences=$watchedReferences")
println("retainedReferences=$retainedReferences")
println("執行完畢")
5. ContentProvider的優化
5.1 Content的初始化順序
? 通過ContentProvider來進行初始化確實能給使用者帶來便利,但是會影響啟動速度,如果有多個ContentProvider,如何控制這些ContentProvider初始化的順序呢,可以參考下面這篇文章https://sivanliu.github.io/2017/12/16/provider%E5%88%9D%E5%A7%8B%E5%8C%96/,如果一些第三方值只提供ContentProvider的初始化方式,我們又不想影響我們APP的啟動時間,該如何處理呢?
#####5.2
? 如果一些第三方庫只提供ContentProvider的初始化方式,我們又不想影響我們APP的啟動時間,該如何處理呢?我們可以使用AOP方式進行插樁,通過Gradle+Transform+ASM進行修改ContentProvider的onCreate方法,提前回傳,然后手動去呼叫初始化代碼,如果這些初始化代碼是私有的或者只限制包內使用的,也可以通過ASM去修改訪問權限,然后在我們想初始化的地方再進行初始化,這可能涉及到一個先后的問題,需要先修改完然后再在某個地方初始化,這里只是提供一個思路,如果一個庫初始化耗時很長,又在ContentProvider中進行初始化,ContentProvider中初始化的代碼又臭又長,又沒有提供其他初始化方法,這樣的垃圾庫你要它干嘛!
####6.總結
-
利用ContentProvider自動初始化,無需用戶手動初始化
-
GC回收,參考佇列
-
1.5.4之后支持fragment,支持androidx
-
當泄露參考到達 5 個時才會發起 heap dump
-
全新的 heap parser,減少 90% 記憶體占用,提升 6 倍速度
-
ContentProvider的優劣,以及優化方案
參考文章
https://sivanliu.github.io/2017/12/16/provider%E5%88%9D%E5%A7%8B%E5%8C%96/
https://juejin.im/post/5d1225546fb9a07ecd3d6b71
最后相關架構及資料領取方式:
點擊我的騰訊檔案免費領取獲取往期Android高級架構資料、原始碼、筆記、視頻,高級UI、性能優化、架構師課程、NDK、混合式開發(ReactNative+Weex)微信小程式、Flutter全方面的Android進階實踐技術,群內還有技術大牛一起討論交流解決問題,
然后再在某個地方初始化,這里只是提供一個思路,如果一個庫初始化耗時很長,又在ContentProvider中進行初始化,ContentProvider中初始化的代碼又臭又長,又沒有提供其他初始化方法,這樣的垃圾庫你要它干嘛!
####6.總結
-
利用ContentProvider自動初始化,無需用戶手動初始化
-
GC回收,參考佇列
-
1.5.4之后支持fragment,支持androidx
-
當泄露參考到達 5 個時才會發起 heap dump
-
全新的 heap parser,減少 90% 記憶體占用,提升 6 倍速度
-
ContentProvider的優劣,以及優化方案
參考文章
https://sivanliu.github.io/2017/12/16/provider%E5%88%9D%E5%A7%8B%E5%8C%96/
https://juejin.im/post/5d1225546fb9a07ecd3d6b71
最后相關架構及資料領取方式:
點擊我的騰訊檔案免費領取獲取往期Android高級架構資料、原始碼、筆記、視頻,高級UI、性能優化、架構師課程、NDK、混合式開發(ReactNative+Weex)微信小程式、Flutter全方面的Android進階實踐技術,群內還有技術大牛一起討論交流解決問題,
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/287171.html
標籤:其他
下一篇:Activity常見問題
