理解Android中的Bitmap

2022-01-31 Android程式設計師

各位讀者,早上好,今天的推送是來自於翔同學授權分享的文章( 原文地址:https://zhuanlan.zhihu.com/p/31450987 ),他曾在魅族任職、目前是在阿里巴巴工作,專注於Android圖像和性能優化方面的工作。本文是基於android-6.0.1_r80原始碼分析理解Android中的Bitmap,相信讀完之後,會讓你對Android中的Bitmap有更深的了解

通過下面三個章節基本可以掃清Bitmap盲區。文章沒有覆蓋到的一方面是Bitmap用法,這部分建議閱讀Glide庫原始碼。一些Color的概念,例如premultiplied / Dither,需要具備一定CG物理基礎,不管怎樣先讀下去。

Bitmap對象創建

Bitmap java層構造函數是通過native層jni call過來的,邏輯在Bitmap_creator方法中。

static jobject Bitmap_creator(JNIEnv* env, jobject, jintArray jColors,                              jint offset, jint stride, jint width, jint height,                              jint configHandle, jboolean isMutable) {    SkColorType colorType = GraphicsJNI::legacyBitmapConfigToColorType(configHandle);    if (NULL != jColors) {        size_t n = env->GetArrayLength(jColors);        if (n < SkAbs32(stride) * (size_t)height) {            doThrowAIOOBE(env);            return NULL;        }    }        if (colorType == kARGB_4444_SkColorType) {        colorType = kN32_SkColorType;    }    SkBitmap bitmap;    bitmap.setInfo(SkImageInfo::Make(width, height, colorType, kPremul_SkAlphaType));    Bitmap* nativeBitmap = GraphicsJNI::allocateJavaPixelRef(env, &bitmap, NULL);    if (!nativeBitmap) {        return NULL;    }    if (jColors != NULL) {        GraphicsJNI::SetPixels(env, jColors, offset, stride,                0, 0, width, height, bitmap);    }    return GraphicsJNI::createBitmap(env, nativeBitmap,            getPremulBitmapCreateFlags(isMutable));}

legacyBitmapConfigToColorType將Bitmap.Config.ARGB_8888轉成skia域的顏色類型kBGRA_8888_SkColorType,顏色類型定義在SkImageInfo.h中,kARGB_4444_SkColorType會強轉成kN32_SkColorType,它就是kBGRA_8888_SkColorType,不必糾結。

enum SkColorType {    kUnknown_SkColorType,    kAlpha_8_SkColorType,    kRGB_565_SkColorType,    kARGB_4444_SkColorType,    kRGBA_8888_SkColorType,    kBGRA_8888_SkColorType,    kIndex_8_SkColorType,    kGray_8_SkColorType,    kLastEnum_SkColorType = kGray_8_SkColorType,#if SK_PMCOLOR_BYTE_ORDER(B,G,R,A)    kN32_SkColorType = kBGRA_8888_SkColorType,#elif SK_PMCOLOR_BYTE_ORDER(R,G,B,A)    kN32_SkColorType = kRGBA_8888_SkColorType,#else    #error "SK_*32_SHFIT values must correspond to BGRA or RGBA byte order"#endif};

接著,根據寬、高、顏色類型等創建SkBitmap,注意kPremul_SkAlphaType描述是alpha採用premultiplied處理的方式,CG處理alpha存在premultiplied和unpremultiplied兩兩種方式。

public:    SkImageInfo()        : fWidth(0)        , fHeight(0)        , fColorType(kUnknown_SkColorType)        , fAlphaType(kUnknown_SkAlphaType)        , fProfileType(kLinear_SkColorProfileType)    {}    static SkImageInfo Make(int width, int height, SkColorType ct, SkAlphaType at,                            SkColorProfileType pt = kLinear_SkColorProfileType) {        return SkImageInfo(width, height, ct, at, pt);    }

Make創建SkImageInfo對象,fWidth的賦值是一個關鍵點,後面Java層通過getAllocationByteCount獲取Bitmap內存佔用中會用到它計算一行像素佔用空間。allocateJavaPixelRef是通過JNI調用VMRuntime實例的newNonMovableArray方法分配內存。

int register_android_graphics_Graphics(JNIEnv* env){    jmethodID m;    jclass c;    ...    gVMRuntime = env->NewGlobalRef(env->CallStaticObjectMethod(gVMRuntime_class, m));    gVMRuntime_newNonMovableArray = env->GetMethodID(gVMRuntime_class, "newNonMovableArray",                                                     "(Ljava/lang/Class;I)Ljava/lang/Object;");    ...}

env->CallObjectMethod(gVMRuntime, gVMRuntime_newNonMovableArray, gByte_class, size)拿到虛擬機分配Heap對象,env->CallLongMethod(gVMRuntime, gVMRuntime_addressOf, arrayObj)拿到分配對象的地址,調用native層構造函數new android::Bitmap(env, arrayObj, (void*) addr, info, rowBytes, ctable)

Bitmap::Bitmap(JNIEnv* env, jbyteArray storageObj, void* address,            const SkImageInfo& info, size_t rowBytes, SkColorTable* ctable)        : mPixelStorageType(PixelStorageType::Java) {    env->GetJavaVM(&mPixelStorage.java.jvm);    mPixelStorage.java.jweakRef = env->NewWeakGlobalRef(storageObj);    mPixelStorage.java.jstrongRef = nullptr;    mPixelRef.reset(new WrappedPixelRef(this, address, info, rowBytes, ctable));            mPixelRef->unref();}void Bitmap::getSkBitmap(SkBitmap* outBitmap) {    assertValid();    android::AutoMutex _lock(mLock);            outBitmap->setInfo(mPixelRef->info(), mPixelRef->rowBytes());    outBitmap->setPixelRef(refPixelRefLocked())->unref();    outBitmap->setHasHardwareMipMap(hasHardwareMipMap());}void Bitmap::pinPixelsLocked() {    switch (mPixelStorageType) {    case PixelStorageType::Invalid:        LOG_ALWAYS_FATAL("Cannot pin invalid pixels!");        break;    case PixelStorageType::External:    case PixelStorageType::Ashmem:                break;    case PixelStorageType::Java: {        JNIEnv* env = jniEnv();        if (!mPixelStorage.java.jstrongRef) {            mPixelStorage.java.jstrongRef = reinterpret_cast<jbyteArray>(                    env->NewGlobalRef(mPixelStorage.java.jweakRef));            if (!mPixelStorage.java.jstrongRef) {                LOG_ALWAYS_FATAL("Failed to acquire strong reference to pixels");            }        }        break;    }    }}std::unique_ptr<WrappedPixelRef> mPixelRef;PixelStorageType mPixelStorageType;union {    struct {        void* address;        void* context;        FreeFunc freeFunc;    } external;    struct {        void* address;        int fd;        size_t size;    } ashmem;    struct {        JavaVM* jvm;        jweak jweakRef;        jbyteArray jstrongRef;    } java;} mPixelStorage;

native層的Bitmap構造函數,mPixelStorage保存前面創建Heap對象的弱引用,mPixelRef指向WrappedPixelRef。outBitmap拿到mPixelRef強引用對象,這裡理解為拿到SkBitmap對象。Bitmap* nativeBitmap = GraphicsJNI::allocateJavaPixelRef完成Bitmap Heap分配,創建native層Bitmap,SkBitmap對象,最後自然是創建Java層Bitmap對象,把該包的包上。native層是通過JNI方法,在Java層創建一個數組對象的,這個數組是對應在Java層的Bitmap對象的buffer數組,所以pixels還是保存在Java堆。而在native層這裡它是通過weak指針來引用的,在需要的時候會轉換為strong指針,用完之後又去掉strong指針,這樣這個數組對象還是能夠被Java堆自動回收。裡面jstrongRef一開始是賦值為null的,但是在bitmap的getSkBitmap方法會使用weakRef給他賦值。

jobject GraphicsJNI::createBitmap(JNIEnv* env, android::Bitmap* bitmap,        int bitmapCreateFlags, jbyteArray ninePatchChunk, jobject ninePatchInsets,        int density) {    bool isMutable = bitmapCreateFlags & kBitmapCreateFlag_Mutable;    bool isPremultiplied = bitmapCreateFlags & kBitmapCreateFlag_Premultiplied;            assert_premultiplied(bitmap->info(), isPremultiplied);    jobject obj = env->NewObject(gBitmap_class, gBitmap_constructorMethodID,            reinterpret_cast<jlong>(bitmap), bitmap->javaByteArray(),            bitmap->width(), bitmap->height(), density, isMutable, isPremultiplied,            ninePatchChunk, ninePatchInsets);    hasException(env);    return obj;}

重點看下這裡env->NewObject(gBitmap_class, gBitmap_constructorMethodID,…,參數中有一處bitmap->javaByteArray(),指向的是Heap對象。所以,實際的像素內存只有一份,被不同對象持有,Java層的Bitmap,native層的Btimap。

這裡順帶說一下JNI生命周期。JNI Local Reference的生命期是在native method的執行期(從 Java 程序切換到 native code環境時開始創建,或者在native method執行時調用JNI function創建),在native method執行完畢切換回Java程序時,所有JNI Local Reference被刪除,生命期結束(調用JNI function可以提前結束其生命期)。

JNI編程中明顯的內存洩漏

Native Code 本身的內存洩漏 JNI 編程首先是一門具體的程式語言,或者 C 語言,或者 C++,或者彙編,或者其它 native 的程式語言。每門程式語言環境都實現了自身的內存管理機制。因此,JNI 程序開發者要遵循 native 語言本身的內存管理機制,避免造成內存洩漏。以 C 語言為例,當用 malloc() 在進程堆中動態分配內存時,JNI 程序在使用完後,應當調用 free() 將內存釋放。總之,所有在 native 語言編程中應當注意的內存洩漏規則,在 JNI 編程中依然適應。 Native 語言本身引入的內存洩漏會造成 native memory 的內存,嚴重情況下會造成 native memory 的 out of memory。

Global Reference 引入的內存洩漏 JNI 編程還要同時遵循 JNI 的規範標準,JVM 附加了 JNI 編程特有的內存管理機制。 JNI 中的 Local Reference 只在 native method 執行時存在,當 native method 執行完後自動失效。這種自動失效,使得對 Local Reference 的使用相對簡單,native method 執行完後,它們所引用的 Java 對象的 reference count 會相應減 1。不會造成 Java Heap 中 Java 對象的內存洩漏。 而 Global Reference 對 Java 對象的引用一直有效,因此它們引用的 Java 對象會一直存在 Java Heap 中。程式設計師在使用 Global Reference 時,需要仔細維護對 Global Reference 的使用。如果一定要使用 Global Reference,務必確保在不用的時候刪除。就像在 C 語言中,調用 malloc() 動態分配一塊內存之後,調用 free() 釋放一樣。否則,Global Reference 引用的 Java 對象將永遠停留在 Java Heap 中,造成 Java Heap 的內存洩漏。

更多JNI洩露,參考閱讀JNI編程中潛在的內存洩漏——對LocalReference的深入理解一文(https://www.ibm.com/developerworks/cn/java/j-lo-jnileak/index.html)

Bitmap對象釋放

基於前文JNI Local Reference和Global Reference洩露,可以看到nativeRecycle實際調用native層Bitmap的freePixels方法,DeleteWeakGlobalRef釋放Bitmap native層Gloabl引用。邏輯還是很簡單的。

void Bitmap::freePixels() {    AutoMutex _lock(mLock);    if (mPinnedRefCount == 0) {        doFreePixels();        mPixelStorageType = PixelStorageType::Invalid;    }}void Bitmap::doFreePixels() {    switch (mPixelStorageType) {    case PixelStorageType::Invalid:                break;    case PixelStorageType::External:        mPixelStorage.external.freeFunc(mPixelStorage.external.address,                mPixelStorage.external.context);        break;    case PixelStorageType::Ashmem:        munmap(mPixelStorage.ashmem.address, mPixelStorage.ashmem.size);        close(mPixelStorage.ashmem.fd);        break;    case PixelStorageType::Java:        JNIEnv* env = jniEnv();        LOG_ALWAYS_FATAL_IF(mPixelStorage.java.jstrongRef,                "Deleting a bitmap wrapper while there are outstanding strong "                "references! mPinnedRefCount = %d", mPinnedRefCount);        env->DeleteWeakGlobalRef(mPixelStorage.java.jweakRef);        break;    }    if (android::uirenderer::Caches::hasInstance()) {        android::uirenderer::Caches::getInstance().textureCache.releaseTexture(                mPixelRef->getStableID());    }}

需要注意兩點訊息,一是Java層主動call recycle()方法或者Bitmap析構函數都會調用freePixels,移除Global對象引用,這個對象是Heap上存一堆像素的空間。GC時釋放掉。二是,JNI不再持有Global Reference,並native函數執行後釋放掉,但Java層的Bitmap對象還在,只是它的mBuffer和mNativePtr是無效地址,沒有像素Heap的Bitmap也就幾乎不消耗內存了。至於Java層Bitmap對象什麼時候釋放,生命周期結束自然free掉了。

static void DeleteWeakGlobalRef(JNIEnv* env, jweak obj) {  JavaVMExt* vm = down_cast<JNIEnvExt*>(env)->vm;  Thread* self = down_cast<JNIEnvExt*>(env)->self;  vm->DeleteWeakGlobalRef(self, obj);}void JavaVMExt::DeleteWeakGlobalRef(Thread* self, jweak obj) {  if (obj == nullptr) {    return;  }  MutexLock mu(self, weak_globals_lock_);  if (!weak_globals_.Remove(IRT_FIRST_SEGMENT, obj)) {    LOG(WARNING) << "JNI WARNING: DeleteWeakGlobalRef(" << obj << ") "                 << "failed to find entry";  }}

通過BitmapFactory創建Bitmap

Bitmap工廠類提供了多種decodeXXX方法創建Bitmap對象,主要是兼容不同的數據源,包括byte數組、文件、FD、Resource對象、InputStream,最終去到native層方法, 如下:

private static native Bitmap nativeDecodeStream(InputStream is, byte[] storage, Rect padding, Options opts);private static native Bitmap nativeDecodeFileDescriptor(FileDescriptor fd, Rect padding, Options opts);private static native Bitmap nativeDecodeAsset(long nativeAsset, Rect padding, Options opts);private static native Bitmap nativeDecodeByteArray(byte[] data, int offset, int length, Options opts);private static native boolean nativeIsSeekable(FileDescriptor fd);

來看看nativeDecodeStream方法,該方法中先是創建了bufferedStream對象,接著doDecode返回Bitmap對象。SkStreamRewindable定義在skia庫中繼承SkStream,它聲明了兩個方法rewind和duplicate,寫過網絡庫的同學一看命名便知是byte操作,前者功能是將文件內部的指針重新指向一個流的開頭,後者是創建共享此緩衝區內容的新的字節緩衝區。

static jobject nativeDecodeStream(JNIEnv* env, jobject clazz, jobject is, jbyteArray storage,        jobject padding, jobject options) {    jobject bitmap = NULL;    SkAutoTDelete<SkStream> stream(CreateJavaInputStreamAdaptor(env, is, storage));    if (stream.get()) {        SkAutoTDelete<SkStreamRewindable> bufferedStream(                SkFrontBufferedStream::Create(stream.detach(), BYTES_TO_BUFFER));        SkASSERT(bufferedStream.get() != NULL);        bitmap = doDecode(env, bufferedStream, padding, options);    }    return bitmap;}

doDecode先是通過JNI拿到Java層Options對象裡面的屬性,outWidth、outHeight、inDensity、inTargetDensity這些。後兩者用來計算Bitmap縮放比例,計算公式scale = (float) targetDensity / density。

static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding, jobject options) {    int sampleSize = 1;    SkImageDecoder::Mode decodeMode = SkImageDecoder::kDecodePixels_Mode;    SkColorType prefColorType = kN32_SkColorType;    bool doDither = true;    bool isMutable = false;    float scale = 1.0f;    bool preferQualityOverSpeed = false;    bool requireUnpremultiplied = false;    jobject javaBitmap = NULL;    if (options != NULL) {        sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID);        if (optionsJustBounds(env, options)) {            decodeMode = SkImageDecoder::kDecodeBounds_Mode;        }                env->SetIntField(options, gOptions_widthFieldID, -1);        env->SetIntField(options, gOptions_heightFieldID, -1);        env->SetObjectField(options, gOptions_mimeFieldID, 0);        jobject jconfig = env->GetObjectField(options, gOptions_configFieldID);        prefColorType = GraphicsJNI::getNativeBitmapColorType(env, jconfig);        isMutable = env->GetBooleanField(options, gOptions_mutableFieldID);        doDither = env->GetBooleanField(options, gOptions_ditherFieldID);        preferQualityOverSpeed = env->GetBooleanField(options,                gOptions_preferQualityOverSpeedFieldID);        requireUnpremultiplied = !env->GetBooleanField(options, gOptions_premultipliedFieldID);        javaBitmap = env->GetObjectField(options, gOptions_bitmapFieldID);        if (env->GetBooleanField(options, gOptions_scaledFieldID)) {            const int density = env->GetIntField(options, gOptions_densityFieldID);            const int targetDensity = env->GetIntField(options, gOptions_targetDensityFieldID);            const int screenDensity = env->GetIntField(options, gOptions_screenDensityFieldID);            if (density != 0 && targetDensity != 0 && density != screenDensity) {                scale = (float) targetDensity / density;            }        }    ...}

這些參數是提供給圖片解碼器SkImageDecoder。圖片資源無非是壓縮格式,SkImageDecoder工廠類根據輸入流同步拿到具體壓縮格式並創建相應解碼器。GetFormatName返回支持的圖片格式。 SkImageDecoder實例將Options參數設置下去。如此解壓出來的是根據實際尺寸裁剪後的圖片。

static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding, jobject options) {    ...    SkImageDecoder* decoder = SkImageDecoder::Factory(stream);    if (decoder == NULL) {        return nullObjectReturn("SkImageDecoder::Factory returned null");    }    decoder->setSampleSize(sampleSize);    decoder->setDitherImage(doDither);    decoder->setPreferQualityOverSpeed(preferQualityOverSpeed);    decoder->setRequireUnpremultipliedColors(requireUnpremultiplied)    ...}SkImageDecoder* SkImageDecoder::Factory(SkStreamRewindable* stream) {    return image_decoder_from_stream(stream);}SkImageDecoder* image_decoder_from_stream(SkStreamRewindable* stream) {    SkImageDecoder* codec = NULL;    const SkImageDecoder_DecodeReg* curr = SkImageDecoder_DecodeReg::Head();    while (curr) {        codec = curr->factory()(stream);                        bool rewindSuceeded = stream->rewind();                        if (!rewindSuceeded) {            SkDEBUGF(("Unable to rewind the image stream."));            SkDELETE(codec);            return NULL;        }        if (codec) {            return codec;        }        curr = curr->next();    }    return NULL;}const char* SkImageDecoder::GetFormatName(Format format) {    switch (format) {        case kUnknown_Format:            return "Unknown Format";        case kBMP_Format:            return "BMP";        case kGIF_Format:            return "GIF";        case kICO_Format:            return "ICO";        case kPKM_Format:            return "PKM";        case kKTX_Format:            return "KTX";        case kASTC_Format:            return "ASTC";        case kJPEG_Format:            return "JPEG";        case kPNG_Format:            return "PNG";        case kWBMP_Format:            return "WBMP";        case kWEBP_Format:            return "WEBP";        default:            SkDEBUGFAIL("Invalid format type!");    }    return "Unknown Format";}

解碼僅僅完成數據的讀取,圖片是經過渲染才能呈現在最終屏幕上,這個步驟在canvas.drawBitmap方法中完成。

static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding, jobject options) {    ...    SkBitmap outputBitmap;    if (willScale) {                                                const float sx = scaledWidth / float(decodingBitmap.width());        const float sy = scaledHeight / float(decodingBitmap.height());                SkColorType colorType = colorTypeForScaledOutput(decodingBitmap.colorType());                                outputBitmap.setInfo(SkImageInfo::Make(scaledWidth, scaledHeight,                colorType, decodingBitmap.alphaType()));        if (!outputBitmap.tryAllocPixels(outputAllocator, NULL)) {            return nullObjectReturn("allocation failed for scaled bitmap");        }                        if (outputAllocator != &javaAllocator) {            outputBitmap.eraseColor(0);        }        SkPaint paint;        paint.setFilterQuality(kLow_SkFilterQuality);        SkCanvas canvas(outputBitmap);        canvas.scale(sx, sy);        canvas.drawARGB(0x00, 0x00, 0x00, 0x00);        canvas.drawBitmap(decodingBitmap, 0.0f, 0.0f, &paint);    }    ...        return GraphicsJNI::createBitmap(env, javaAllocator.getStorageObjAndReset(),            bitmapCreateFlags, ninePatchChunk, ninePatchInsets, -1);}

最終渲染後的圖片數據包在了Bitmap對象中,這部分邏輯重回第一章節Bitmap對象創建。

到此,你是否對Android中的Bitmap有了更深的理解呢?喜歡本文可以用點讚、轉發等形式支持。

歡迎關注

相關焦點

  • Android Bitmap史上最詳細全解 | 原力計劃
    BitmapFactory 提供了多種創建 bitmap 的靜態方法://從資源文件中通過id加載bitmap//Resources res:資源文件,可以context.getResources()獲得//id:資源文件的id,如R.drawable.xxxpublic static Bitmap decodeResources(Resources res,int
  • Bitmap.recycle引發的血案
    然而……現在的SDK中對recycle方法是這樣注釋的,如圖所示:bitmap.png可以發現,系統建議你不要手動去調用,而是讓GC來進行處理不再使用的Bitmap。我們可以認為,即使在Android2.3之後的版本中去調用recycle,系統也是會強制回收內存的,只是系統不建議這樣做而已。
  • FFmpeg獲取視頻首幀轉封面圖Bitmap
    AVFormatContext結構體中的streams欄位包含了媒體文件中所以的流信息,包含視頻流,音頻流,字幕流等。AVCodecParameters結構體描述了被編碼後的流的屬性。創建Bitmapjobject createBitmap(JNIEnv *env,                     int width, int height) {    jclass bitmapCls = env->FindClass("android/graphics/Bitmap");
  • Android高斯模糊技術,實現毛玻璃效果
    終於實現出來了 ,效果圖如下:本示例是在ConvenientBanner這個開源庫demo上改的,自己寫一個浪費時間,直接拿來用,地址為:https://github.com/saiwu-bigkoo/Android-ConvenientBanner在找遍了所有高斯模糊的算法代碼後,發現android
  • 抖音上很火的字符畫 Android 實現
    抖音上炫代碼的不少,有些真的讓人嘆為觀止,作為一個androider,當我看到下面這段舞蹈的時候,終於忍不住了,想要通過android實現一樣的效果。這麼好玩的東西,為啥就沒有大佬做呢,原因可能有兩個,一是真的難,二是出力不討好,難以達到最終效果,一番嘗試後,技術問題都解決了,但並沒有達到電腦端美感,手機屏幕還是太小了。
  • Android應用中使用百度地圖API定位自己的位置
    android:id="@+id/bmapView"        android:layout_width="fill_parent"        android:layout_height="fill_parent"        android:clickable="true"        />  <Button  android
  • C# 合併BitMap圖像,生成超大bitmap
    使用c#,合併多個bitMap圖像當只需要兩個圖像合併的時候,可以簡單的使用gdi+,把兩個圖像畫到一個畫布上面實現合併bitm
  • 玩轉Android Bitmap
    Bitmap是Android系統中的圖像處理中最重要類之一。Bitmap可以獲取圖像文件信息,對圖像進行剪切、旋轉、縮放,壓縮等操作,並可以以指定格式保存圖像文件。2. 創建Bitmap對象既然不能直接通過構造方法創建Bitmap,那怎樣才能創建Bitmap對象。
  • 最新 Android 熱門開源項目公布
    使用起來極為便捷,只需在 build.gradle 中引入依賴:dependencies {debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.0-alpha-2'}LeakCanary 會自動檢測 debug build
  • Android系統的進程和線程
    manifest 文件中的每種組件元素(<activity>、 <service>、 <receiver> 和 <provider>)都支持 android:process 屬性,用於指定組件所屬運行的進程。
  • Bitmap粒子「爆炸」效果
    也就是一張jpg或png圖片就是很多顏色的合集,而這些合集信息都被封裝到了Bitmap類中你可以使用Bitmap獲取任意像素點,並修改它,對與某像素點而言,顏色信息是其主要的部分。(getResources(), R.mipmap.iv_200x200); mColArr = new int[bitmap.getWidth()][bitmap.getHeight()]; for (int i = 0; i < bitmap.getWidth(); i++) {     for (int j = 0; j < bitmap.getHeight
  • 徹底理解 Android Binder 通信架構
    從我個人的理解角度, 曾嘗試著在知乎回答同樣一個問題 為什麼Android要採用Binder作為IPC機制?.這是我第一次認認真真地在知乎上回答問題, 收到很多網友的點讚與回復, 讓我很受鼓舞, 也決心分享更多優先地文章回報讀者和粉絲, 為Android圈貢獻自己的微薄之力.
  • Android高斯模糊你所不知道的坑
    實際上高斯模糊效果本身是沒什麼好說的,只要你去網上搜索一下相信能找到不少相關的實現,其中對於android開發來說最主要的實現方法就是使用renderscript方法去實現,自己實現模糊效果的時候也是參考這些代碼來完成。
  • Android一個包含表格的圖標庫
    2.水平多柱狀圖2.1 xml布局 <wellijohn.org.varchart.hor_bar_with_line_chart.ChartLine        android:id="@+id/chartline"        android:layout_width="wrap_content"        android:layout_height
  • Android中SettingsProvider源碼解析
    android:sharedUserId="android.uid.system">    <application android:allowClearUserData="false"                 android:label="@string/app_label"                 android
  • 了解一下,Android 10中的ART虛擬機(2)
    並且,沒有掌握隨著Java語言本身的發展,隨著整體技術的發展,JVM本身所需要做的改進。我相信代碼的作者在改進ART虛擬機時腦子是有一條路線的,絕對不是一拍腦袋就想出來的。我之前很單純的把上面這個問題歸結於我對語言本身不了解。這才有了前面幾期公眾號中我試圖以quickjs和javascript語言為研究課題在這方面有所突破。但這條路可能是失敗了。
  • Android Shader 實戰 各種炫酷效果的基石
    兇殘的程式設計師的博客地址:http://blog.csdn.net/qian520ao自定義view中畫筆Paint有設置著色器方法paint.setShader(),我們來看一下這個Shader的子類效果圖
  • Android中adb的使用
    一提到adb,搞Android的肯定不會陌生,那adb到底是什麼,adb全稱為Android Debug Bridge,即Android調試橋,那adb到底是幹什麼的:通過adb我們可以在Eclipse中方面通過DDMS來調試Android程序,說白了就是debug工具。