<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          谷歌官方改了兩次的知識(shí)點(diǎn),你一定要知道!

          共 7003字,需瀏覽 15分鐘

           ·

          2022-05-30 18:21

          ?安卓進(jìn)階漲薪訓(xùn)練營(yíng),讓一部分人先進(jìn)大廠

          大家好,我是皇叔,最近開了一個(gè)安卓進(jìn)階漲薪訓(xùn)練營(yíng),可以幫助大家突破技術(shù)&職場(chǎng)瓶頸,從而度過難關(guān),進(jìn)入心儀的公司。


          詳情見這篇文章:繼Android進(jìn)階三部曲之后,我的最強(qiáng)作來啦!



          今天面試遇到同學(xué)說做過內(nèi)存優(yōu)化,于是我一般都會(huì)問那 Bitmap 的像素內(nèi)存存在哪?大多數(shù)同學(xué)都回答在 java heap 里面,就比較尷尬,理論上你做內(nèi)存優(yōu)化,如果連圖片這個(gè)內(nèi)存大戶內(nèi)存存在哪都不清楚,實(shí)在不太能說得過去。



          Bitmap可以說是安卓里面最常見的內(nèi)存消耗大戶了,我們開發(fā)過程中遇到的oom問題很多都是由它引發(fā)的。谷歌官方也一直在迭代它的像素內(nèi)存管理策略。從 Android 2.3.3以前的分配在native上,到2.3-7.1之間的分配在java堆上,又到8.0之后的回到native上。幾度變遷,它的回收方法也在跟著變化。

          Android 2.3.3以前

          2.3.3以前Bitmap的像素內(nèi)存是分配在natvie上,而且不確定什么時(shí)候會(huì)被回收。根據(jù)官方文檔的說法我們需要手動(dòng)調(diào)用Bitmap.recycle()去回收:

          https://developer.android.com/topic/performance/graphics/manage-memory


          在 Android 2.3.3(API 級(jí)別 10)及更低版本上,位圖的后備像素?cái)?shù)據(jù)存儲(chǔ)在本地內(nèi)存中。它與存儲(chǔ)在 Dalvik 堆中的位圖本身是分開的。本地內(nèi)存中的像素?cái)?shù)據(jù)并不以可預(yù)測(cè)的方式釋放,可能會(huì)導(dǎo)致應(yīng)用短暫超出其內(nèi)存限制并崩潰。

          在 Android 2.3.3(API 級(jí)別 10)及更低版本上,建議使用?recycle()。如果您在應(yīng)用中顯示大量位圖數(shù)據(jù),則可能會(huì)遇到 OutOfMemoryError 錯(cuò)誤。利用?recycle()?方法,應(yīng)用可以盡快回收內(nèi)存。

          注意:只有當(dāng)您確定位圖已不再使用時(shí)才應(yīng)該使用?recycle()。如果您調(diào)用?recycle()?并在稍后嘗試?yán)L制位圖,則會(huì)收到錯(cuò)誤:"Canvas: trying to use a recycled bitmap"。

          2.Android 3.0~Android 7.1

          雖然3.0~7.1的版本Bitmp的像素內(nèi)存是分配在java堆上的,但是實(shí)際是在natvie層進(jìn)行decode的,而且會(huì)在native層創(chuàng)建一個(gè)c++的對(duì)象和java層的Bitmap對(duì)象進(jìn)行關(guān)聯(lián)。


          從BitmapFactory的源碼我們可以看到它一路調(diào)用到nativeDecodeStream這個(gè)native方法:


          //?BitmapFactory.java
          public?static?Bitmap?decodeFile(String?pathName,?Options?opts)?{
          ????...
          ????stream?=?new?FileInputStream(pathName);
          ????bm?=?decodeStream(stream,?null,?opts);
          ????...
          ????return?bm;
          }

          public?static?Bitmap?decodeStream(InputStream?is,?Rect?outPadding,?Options?opts)?{
          ????...
          ????bm?=?decodeStreamInternal(is,?outPadding,?opts);
          ????...
          ????return?bm;
          }

          private?static?Bitmap?decodeStreamInternal(InputStream?is,?Rect?outPadding,?Options?opts)?{
          ????...
          ????return?nativeDecodeStream(is,?tempStorage,?outPadding,?opts);
          }


          nativeDecodeStream實(shí)際上會(huì)通過jni創(chuàng)建java堆的內(nèi)存,然后讀取io流解碼圖片將像素?cái)?shù)據(jù)存到這個(gè)java堆內(nèi)存里面:


          //?BitmapFactory.cpp
          static?jobject?nativeDecodeStream(JNIEnv*?env,?jobject?clazz,?jobject?is,?jbyteArray?storage,
          ????????jobject?padding,?jobject?options)
          ?
          {
          ????...
          ????bitmap?=?doDecode(env,?bufferedStream,?padding,?options);
          ????...
          ????return?bitmap;
          }

          static?jobject?doDecode(JNIEnv*?env,?SkStreamRewindable*?stream,?jobject?padding,?jobject?options)?{
          ????...
          ????//?outputAllocator是像素內(nèi)存的分配器,會(huì)在java堆上創(chuàng)建內(nèi)存給像素?cái)?shù)據(jù),可以通過BitmapFactory.Options.inBitmap復(fù)用前一個(gè)bitmap像素內(nèi)存
          ????SkBitmap::Allocator*?outputAllocator?=?(javaBitmap?!=?NULL)??
          ????????????(SkBitmap::Allocator*)&recyclingAllocator?:?(SkBitmap::Allocator*)&javaAllocator;
          ????...
          ????//?將內(nèi)存分配器設(shè)置給解碼器
          ????decoder->setAllocator(outputAllocator);
          ????...
          ????//解碼
          ????if?(decoder->decode(stream,?&decodingBitmap,?prefColorType,?decodeMode)
          ????????????????!=?SkImageDecoder::kSuccess)?{
          ????????return?nullObjectReturn("decoder->decode?returned?false");
          ????}
          ????...
          ????return?GraphicsJNI::createBitmap(env,?javaAllocator.getStorageObjAndReset(),
          ????????????bitmapCreateFlags,?ninePatchChunk,?ninePatchInsets,?-1);
          }

          //?Graphics.cpp
          jobject?GraphicsJNI::createBitmap(JNIEnv*?env,?android::Bitmap*?bitmap,
          ????????int?bitmapCreateFlags,?jbyteArray?ninePatchChunk,?jobject?ninePatchInsets,
          ????????int?density)?{

          ????//?java層的Bitmap對(duì)象實(shí)際上是natvie層new出來的
          ????//?native層也會(huì)創(chuàng)建一個(gè)android::Bitmap對(duì)象與java層的Bitmap對(duì)象綁定
          ????//?bitmap->javaByteArray()代碼bitmap的像素?cái)?shù)據(jù)其實(shí)是存在java層的byte數(shù)組中
          ????jobject?obj?=?env->NewObject(gBitmap_class,?gBitmap_constructorMethodID,
          ????????????reinterpret_cast(bitmap),?bitmap->javaByteArray(),
          ????????????bitmap->width(),?bitmap->height(),?density,?isMutable,?isPremultiplied,
          ????????????ninePatchChunk,?ninePatchInsets);
          ????...
          ????return?obj;
          }


          我們可以看最后會(huì)調(diào)用javaAllocator.getStorageObjAndReset()創(chuàng)建一個(gè)android::Bitmap類型的native層Bitmap對(duì)象,然后通過jni調(diào)用java層的Bitmap構(gòu)造函數(shù)去創(chuàng)建java層的Bitmap對(duì)象,同時(shí)將native層的Bitmap對(duì)象保存到mNativePtr:


          //?Bitmap.java
          //?Convenience?for?JNI?access
          private?final?long?mNativePtr;

          /**
          ?*?Private?constructor?that?must?received?an?already?allocated?native?bitmap
          ?*?int?(pointer).
          ?*/

          //?called?from?JNI
          Bitmap(long?nativeBitmap,?byte[]?buffer,?int?width,?int?height,?int?density,
          ????????boolean?isMutable,?boolean?requestPremultiplied,
          ????????byte[]?ninePatchChunk,?NinePatch.InsetStruct?ninePatchInsets)?{
          ????...
          ????mNativePtr?=?nativeBitmap;
          ????...
          }


          從上面的源碼我們也能看出來,Bitmap的像素是存在java堆的,所以如果bitmap沒有人使用了,垃圾回收器就能自動(dòng)回收這塊的內(nèi)存,但是在native創(chuàng)建出來的nativeBitmap要怎么回收呢?從Bitmap的源碼我們可以看到在Bitmap構(gòu)造函數(shù)里面還會(huì)創(chuàng)建一個(gè)BitmapFinalizer去管理nativeBitmap:


          /**
          ?*?Private?constructor?that?must?received?an?already?allocated?native?bitmap
          ?*?int?(pointer).
          ?*/

          //?called?from?JNI
          Bitmap(long?nativeBitmap,?byte[]?buffer,?int?width,?int?height,?int?density,
          ????????boolean?isMutable,?boolean?requestPremultiplied,
          ????????byte[]?ninePatchChunk,?NinePatch.InsetStruct?ninePatchInsets)?{
          ????...
          ????mNativePtr?=?nativeBitmap;
          ????mFinalizer?=?new?BitmapFinalizer(nativeBitmap);
          ????...
          }


          BitmapFinalizer的原理十分簡(jiǎn)單。Bitmap對(duì)象被銷毀的時(shí)候BitmapFinalizer也會(huì)同步被銷毀,然后就可以在BitmapFinalizer.finalize()里面銷毀native層的nativeBitmap:


          private?static?class?BitmapFinalizer?{
          ????private?long?mNativeBitmap;
          ????...
          ????BitmapFinalizer(long?nativeBitmap)?{
          ????????mNativeBitmap?=?nativeBitmap;
          ????}
          ????...
          ????@Override
          ????public?void?finalize()?{
          ????????try?{
          ????????????super.finalize();
          ????????}?catch?(Throwable?t)?{
          ????????????//?Ignore
          ????????}?finally?{
          ????????????setNativeAllocationByteCount(0);
          ????????????nativeDestructor(mNativeBitmap);
          ????????????mNativeBitmap?=?0;
          ????????}
          ????}
          }

          3.Android 8.0之后

          8.0以后像素內(nèi)存又被放回了native上,所以依然需要在java層的Bitmap對(duì)象回收之后同步回收native的內(nèi)存。


          雖然BitmapFinalizer同樣可以實(shí)現(xiàn),但是Java的finalize方法實(shí)際上是不推薦使用的,所以谷歌也換了NativeAllocationRegistry去實(shí)現(xiàn):


          /**
          ?*?Private?constructor?that?must?received?an?already?allocated?native?bitmap
          ?*?int?(pointer).
          ?*/

          //?called?from?JNI
          Bitmap(long?nativeBitmap,?int?width,?int?height,?int?density,
          ????????boolean?isMutable,?boolean?requestPremultiplied,
          ????...
          ????mNativePtr?=?nativeBitmap;
          ????long?nativeSize?=?NATIVE_ALLOCATION_SIZE?+?getAllocationByteCount();
          ????NativeAllocationRegistry?registry?=?new?NativeAllocationRegistry(
          ????????Bitmap.class.getClassLoader(),?nativeGetNativeFinalizer(),?nativeSize);
          ????registry.registerNativeAllocation(this,?nativeBitmap);
          }


          NativeAllocationRegistry底層實(shí)際上使用了sun.misc.Cleaner,可以為對(duì)象注冊(cè)一個(gè)清理的Runnable。當(dāng)對(duì)象內(nèi)存被回收的時(shí)候jvm就會(huì)調(diào)用它。


          import?sun.misc.Cleaner;

          public?Runnable?registerNativeAllocation(Object?referent,?Allocator?allocator)?{
          ????...
          ????CleanerThunk?thunk?=?new?CleanerThunk();
          ????Cleaner?cleaner?=?Cleaner.create(referent,?thunk);
          ????..
          }

          private?class?CleanerThunk?implements?Runnable?{
          ????...
          ????public?void?run()?{
          ????????if?(nativePtr?!=?0)?{
          ????????????applyFreeFunction(freeFunction,?nativePtr);
          ????????}
          ????????registerNativeFree(size);
          ????}
          ????...
          }


          這個(gè)Cleaner的原理也很暴力,首先它是一個(gè)虛引用,registerNativeAllocation實(shí)際上創(chuàng)建了一個(gè)Bitmap的虛引用:


          //?Cleaner.java
          public?class?Cleaner?extends?PhantomReference?{
          ????...
          ????public?static?Cleaner?create(Object?ob,?Runnable?thunk)?{
          ????????...
          ????????return?add(new?Cleaner(ob,?thunk));
          ????}
          ????...
          ????private?Cleaner(Object?referent,?Runnable?thunk)?{
          ????????super(referent,?dummyQueue);
          ????????this.thunk?=?thunk;
          ????}
          ????...
          ????public?void?clean()?{
          ????????...
          ????????thunk.run();
          ????????...
          ????}
          ????...
          }


          虛引用的話我們都知道需要配合一個(gè)ReferenceQueue使用,當(dāng)對(duì)象的引用被回收的時(shí)候,jvm就會(huì)將這個(gè)虛引用丟到ReferenceQueue里面。而ReferenceQueue在插入的時(shí)候居然通過instanceof判斷了下是不是Cleaner:


          //?ReferenceQueue.java
          private?boolean?enqueueLocked(Reference?r)?{
          ????...
          ????if?(r?instanceof?Cleaner)?{
          ????????Cleaner?cl?=?(sun.misc.Cleaner)?r;
          ????????cl.clean();
          ????????...
          ????}
          ????...
          }


          也就是說Bitmap對(duì)象被回收,就會(huì)觸發(fā)Cleaner這個(gè)虛引用被丟入ReferenceQueue,而ReferenceQueue里面會(huì)判斷丟進(jìn)來的虛引用是不是Cleaner,如果是就調(diào)用Cleaner.clean()方法。而clean方法內(nèi)部就會(huì)再去執(zhí)行我們注冊(cè)的清理的Runnable。






          為了防止失聯(lián),歡迎關(guān)注我的小號(hào)

          ?? 微信改了推送機(jī)制,真愛請(qǐng)星標(biāo)本公號(hào)??

          瀏覽 118
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  成人三级片在线免费观看 | 香蕉中文网| 五月天成人影视 | 久热精品在线视频 | 久久精品一区二区三区在线 |