主頁 > 移動端開發 > Android跨行程傳大圖思考及實作——附上原理分析

Android跨行程傳大圖思考及實作——附上原理分析

2021-09-25 17:45:32 移動端開發

1.拋一個問題

這一天,法海想鍛煉小青的定力,由于Bitmap也是一個Parcelable型別的資料,法海想通過Intent小青傳個特別大的圖片

intent.putExtra("myBitmap",fhBitmap)

如果“法海”(Activity)使用Intent去傳遞一個大的Bitmap“小青”(Activity),如果你的圖片夠大,會出現類似下面這樣的錯誤,請繼續往下看:

Caused by: android.os.TransactionTooLargeException: data parcel size 8294952 bytes
        at android.os.BinderProxy.transactNative(Native Method)
        at android.os.BinderProxy.transact(BinderProxy.java:535)
        at android.app.IActivityTaskManager$Stub$Proxy.startActivity(IActivityTaskManager.java:3904)
        at android.app.Instrumentation.execStartActivity(Instrumentation.java:1738)

至于是什么樣的大圖,這個只有法海知道了(小青:好羞澀啊)🙈🙈🙈

所以TransactionTooLargeException這玩意爆出來的地方在哪呢?

2.問題定位分析

我們可以看到錯誤的日志資訊里面看到呼叫了BinderProxy.transactNative,這個transactNative是一個native方法

//android.os.BinderProxy
/**
 * Native implementation of transact() for proxies
*/
public native boolean transactNative(int code, Parcel data, Parcel reply,
            int flags) throws RemoteException;

Android Code Search,全域搜索一下:android_os_BinderProxy_transact

//frameworks/base/core/jni/android_util_Binder.cpp

static jboolean android_os_BinderProxy_transact(JNIEnv* env, jobject obj,
        jint code, jobject dataObj, jobject replyObj, jint flags) // throws RemoteException
{
    ......
    status_t err = target->transact(code, *data, reply, flags);
    ......
    if (err == NO_ERROR) { 
        //如果匹配成功直接攔截不往下面執行了
        return JNI_TRUE;
    } else if (err == UNKNOWN_TRANSACTION) {
        return JNI_FALSE;
    }
    signalExceptionForError(env, obj, err, true /*canThrowRemoteException*/, data->dataSize());
    return JNI_FALSE;
}

我們打開signalExceptionForError方法看看里面的內容

//frameworks/base/core/jni/android_util_Binder.cpp
//處理例外的方法
void signalExceptionForError(JNIEnv* env, jobject obj, status_t err,
        bool canThrowRemoteException, int parcelSize)
{
    switch (err) {
        //其他例外,大家可以自行閱讀了解;
        //如:沒有權限例外,檔案太大,錯誤的檔案描述符,等等;
        ........
        case FAILED_TRANSACTION: {
            const char* exceptionToThrow;
            char msg[128];
            //官方在FIXME中寫道:事務過大是FAILED_TRANSACTION最常見的原因
            //但它不是唯一的原因,Binder驅動可以回傳 BR_FAILED_REPLY
            //也有其他原因可能是:事務格式不正確,檔案描述符FD已經被關閉等等

            //parcelSize大于200K就會報錯,canThrowRemoteException傳遞進來的是true
            if (canThrowRemoteException && parcelSize > 200*1024) {
                // bona fide large payload
                exceptionToThrow = "android/os/TransactionTooLargeException";
                snprintf(msg, sizeof(msg)-1, "data parcel size %d bytes", parcelSize);
            } else {
                ..........
            }
            //使用指定的類和訊息內容拋出例外
            jniThrowException(env, exceptionToThrow, msg);
        } break;
        ........
    }
}

此時我們看到: parcelSize大于200K就會報錯,難道一定是200K以內?先別著急著下結論,繼續往下看👇👇

3.提出疑問

法海:我有個疑問,我看到檔案寫的1M大小啊;

許仙:別急,妹夫,來先看一下檔案的解釋,看一下使用說明:
官方TransactionTooLargeException的檔案中描述到:Binder 事務緩沖區有一個有限的固定大小,目前為 1MB,由行程所有正在進行的事務共享
可以看到寫的是:共享事務的緩沖區

如來佛祖:汝等別急,我們簡單測驗一下,Intent傳遞201*1024個位元組陣列,我們發現可以正常傳遞過去,Logcat僅僅輸出了一個Error提示的日志資訊,還是可以正常傳遞的

E/ActivityTaskManager: Transaction too large, intent: Intent { cmp=com.melody.test/.SecondActivity (has extras) }, extras size: 205848, icicle size: 0

我們再測驗一個值,intent傳遞800*1024個位元組陣列,我們發現會崩潰

android.os.TransactionTooLargeException: data parcel size 821976 bytes
        at android.os.BinderProxy.transactNative(Native Method)
        at android.os.BinderProxy.transact(BinderProxy.java:540)
        at android.app.IApplicationThread$Stub$Proxy.scheduleTransaction(IApplicationThread.java:2504)
        at android.app.servertransaction.ClientTransaction.schedule(ClientTransaction.java:136)

不要著急,我們繼續往下看分析

4.解答疑問

我們來看一下,下面兩行代碼

//frameworks/base/core/jni/android_util_Binder.cpp
//這個方法android_os_BinderProxy_transact里面的
IBinder* target = getBPNativeData(env, obj)->mObject.get();
status_t err = target->transact(code, *data, reply, flags);

從上面的分析和測驗結果,我們從target->transact這里來找err回傳值, 先根據頭檔案,搜索對應的cpp類,我們看一下這幾個cpp類:BpBinder.cppIPCThreadState.cppProcessState.cpp

//frameworks/native/libs/binder/ProcessState.cpp

// (1 * 1024 * 1024) - (4096 *2)
#define BINDER_VM_SIZE ((1 * 1024 * 1024) - sysconf(_SC_PAGE_SIZE) * 2)
#define DEFAULT_MAX_BINDER_THREADS 15

//下面兩個注釋
//參考自官方檔案:https://source.android.google.cn/devices/architecture/hidl/binder-ipc
#ifdef __ANDROID_VNDK__
//供應商/供應商行程之間的IPC,使用 AIDL 介面
const char* kDefaultDriver = "/dev/vndbinder";
#else
// "/dev/binder" 設備節點成為框架行程的專有節點
const char* kDefaultDriver = "/dev/binder";
#endif

//建構式:初始化一些變數,Binder最大執行緒數等
ProcessState::ProcessState(const char* driver)
      : mDriverName(String8(driver)),
        mDriverFD(-1),
        mVMStart(MAP_FAILED),
        ......
        mMaxThreads(DEFAULT_MAX_BINDER_THREADS),
        mStarvationStartTimeMs(0),
        mThreadPoolStarted(false),
        mThreadPoolSeq(1),
        mCallRestriction(CallRestriction::NONE) {
    ......
    //打開驅動
    base::Result<int> opened = open_driver(driver);
    if (opened.ok()) {
        //映射(1M-8k)的mmap空間
        mVMStart = mmap(nullptr, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE,
                        opened.value(), 0);
        ......
    }
    ......
}

點擊查看sysconf.cpp
getauxval(AT_PAGESZ) = 4096,可以得出Binder記憶體限制BINDER_VM_SIZE = 1M-8kb

這里為什么不是1M,而是1M-8K?
最開始的時候,官方寫的是1M,后來他們內部自己優化了;
來看這里👉👉官方提交的ProcessState.cpp提交的log日志:允許內核更有效地利用其虛擬地址空間

我們知道:微信的MMKV美團的Logan的日志組件,都是基于mmap來實作的;

binder驅動的注冊邏輯在Binder.c中,我們看一下binder_mmap方法

//kernel/msm/drivers/android/binder.c
static int binder_mmap(struct file *filp, struct vm_area_struct *vma)
{
	int ret;
	struct binder_proc *proc = filp->private_data;
	const char *failure_string;
	if (proc->tsk != current->group_leader)
		return -EINVAL;
        //這里可以看到:映射空間最多4M
	if ((vma->vm_end - vma->vm_start) > SZ_4M)
		vma->vm_end = vma->vm_start + SZ_4M;
	......
        //初始化指定的空間vma用于分配系結緩沖區
	ret = binder_alloc_mmap_handler(&proc->alloc, vma);
        ......
}

這里能看到映射空間最多4M,我們再來看一下binder_alloc_mmap_handler這個方法,點擊查看binder_alloc.c

//kernel/msm/drivers/android/binder_alloc.c
//由binder_mmap()呼叫來初始化指定的空間vma用于分配系結緩沖區
int binder_alloc_mmap_handler(struct binder_alloc *alloc,
			      struct vm_area_struct *vma)
{
     ......
     //buffer_size最大4M
     alloc->buffer_size = vma->vm_end - vma->vm_start;
     ......
     //異步事務的空閑緩沖區大小最大2M
     alloc->free_async_space = alloc->buffer_size / 2;
     ......
}

從上面的分析得出結論:
1.Binder驅動給每個行程最多分配4M的buffer空間大小;
2.異步事務的空閑緩沖區空間大小最多為2M
3.Binder內核記憶體上限為1M-8k;
4.異步事務緩沖區空間大小等于buffer_size/2,具體值取決于buffer_size;


同步、異步是定義在AIDL檔案中的,我們看上面測驗的兩個例子,其中有一個傳了800*1024個位元組陣列崩潰如下:

android.os.TransactionTooLargeException: data parcel size 821976 bytes
        at android.os.BinderProxy.transactNative(Native Method)
        at android.os.BinderProxy.transact(BinderProxy.java:540)
        at android.app.IApplicationThread$Stub$Proxy.scheduleTransaction(IApplicationThread.java:2504)

點擊查看IApplicationThread.aidl 查看AIDL里面的內容,我們看到scheduleTransaction是一個異步的方法;
因為oneway修飾在interface之前,會讓interface內所有的方法都隱式地帶上oneway;

由于oneway異步呼叫,我們這個時候修改一下,傳遞(1M-8k)/2大小之內的資料測驗一下

// ((1024 * 1024 - 8 * 1024)/2)-1

 E/ActivityTaskManager: Transaction too large, intent: Intent { cmp=com.melody.test/.SecondActivity (has extras) }, extras size: 520236, icicle size: 0

Exception when starting activity com.melody.test/.SecondActivity
    android.os.TransactionTooLargeException: data parcel size 522968 bytes
        at android.os.BinderProxy.transactNative(Native Method)
        at android.os.BinderProxy.transact(BinderProxy.java:540)
        at android.app.IApplicationThread$Stub$Proxy.scheduleTransaction(IApplicationThread.java:2504)

可以看到還是會報錯,說明異步事務的可用空間不夠,仔細看一下為什么不夠,細心的同學可能發現了:
警告的日志列印extras size: 520236
崩潰的日志列印data parcel size: 522968
大小相差2732 約等于 2.7k

如果這個時候我們用Intent傳遞一個ByteArray,比之前的大小減去3k
ByteArray((1024*1024 - (8 * 1024))/2 - 3 * 1024)

startActivity(Intent(this,SecondActivity::class.java).apply {
            putExtra("KEY",ByteArray((1024*1024 - (8 * 1024))/2 - 3 * 1024))
})

這個時候發現(1M-8k)/2 -3k,可以成功傳遞資料,說明有其他資料占用了這部分空間,
我們上面寫了,不要忘記:共享事務的緩沖區這里減去3k僅測驗用的,我們繼續往下分析;

找一下:異步事務的空閑緩沖區空間大小比較的地方,打開binder_alloc.c,找到binder_alloc_new_buf方法

//kernel/msm/drivers/android/binder_alloc.c
//分配一個新緩沖區
struct binder_buffer *binder_alloc_new_buf(struct binder_alloc *alloc,
					   size_t data_size,
					   size_t offsets_size,
					   size_t extra_buffers_size,
					   int is_async,
					   int pid)
{
	......
	buffer = binder_alloc_new_buf_locked(alloc, data_size, offsets_size,extra_buffers_size, is_async, pid);
        .......
}

我們來看一下binder_alloc_new_buf_locked方法

//kernel/msm/drivers/android/binder_alloc.c
static struct binder_buffer *binder_alloc_new_buf_locked(	
	struct binder_alloc *alloc,
	size_t data_size,
	size_t offsets_size,
	size_t extra_buffers_size,
	int is_async,
	int pid)
{
    ......
    //如果是異步事務,檢查所需的大小是否在異步事務的空閑緩沖區區間內
    if (is_async &&
	alloc->free_async_space < size + sizeof(struct binder_buffer)) {
            return ERR_PTR(-ENOSPC);
	}
}

分析了這么多,不論是同步還是異步,都是共享事務的緩沖區,如果有大量資料需要通過Activity的Intent傳遞,資料大小最好維持在200k以內
上面測驗的時候,超出200k資料傳遞的時候,LogCat已經給我們列印提示“Transaction too large”了,但是只要沒有超出異步事務空閑的緩沖區大小,就不會崩潰
如果Intent傳遞大量的資料完全可以使用別的方式方法;

5.Intent設定Bitmap發生了什么?

5.1-Intent.writeToParcel

Intent資料寫入到parcel中,在writeToParcel方法里面,Intent把Bundle寫入到Parcel中

//android.content.Intent

public void writeToParcel(Parcel out, int flags) {
    ......
    //把Bundle寫入到Parcel中
    out.writeBundle(mExtras);
}

打開out.writeBundle方法

//android.os.Parcel#writeBundle
public final void writeBundle(@Nullable Bundle val) {
     if (val == null) {
         writeInt(-1);
         return;
     }
     //執行Bundle自身的writeToParcel方法
     val.writeToParcel(this, 0);
}

5.2-Bundle.writeToParcel

//android.os.Bundle

public void writeToParcel(Parcel parcel, int flags) {
     final boolean oldAllowFds = parcel.pushAllowFds((mFlags & FLAG_ALLOW_FDS) != 0);
     try {
        //這里官方注釋已經寫的很詳細了:
        //將Bundle內容寫入Parcel,通常是為了讓它通過IBinder連接傳遞
        super.writeToParcelInner(parcel, flags);
     } finally {
        //把mAllowFds值設定回來
        parcel.restoreAllowFds(oldAllowFds);
     }
}

點擊查看Parcel.cpp,我們看一下里面的pushAllowFds方法

//frameworks/native/libs/binder/Parcel.cpp
bool Parcel::pushAllowFds(bool allowFds)
{
    const bool origValue = mAllowFds;
    if (!allowFds) {
        mAllowFds = false;
    }
    return origValue;
}

如果Bundle設定了不允許帶描述符,當呼叫pushAllowFds之后Parcel中的內容也不帶描述符;
在文章開頭,我們舉的例子中:通過Intent去傳遞一個Bitmap,在執行到Instrumentation#execStartActivity的時候,我們發現Intent有個prepareToLeaveProcess方法,在此方法里面呼叫了Bundle#setAllowFds(false)

//android.app.Instrumentation
public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {
        try {
            ......
            intent.prepareToLeaveProcess(who);
            ......
        } catch (RemoteException e) {
            throw new RuntimeException("Failure from system", e);
        }
        return null;
    }

5.3-Parcel.writeArrayMapInternal

剛剛上面Bundle.writeToParcel方法里面super.writeToParcelInner觸發下面方法

//android.os.BaseBundle
void writeToParcelInner(Parcel parcel, int flags) {
       ......
       parcel.writeArrayMapInternal(map);
       ......
}

我們看一下writeArrayMapInternal方法

void writeArrayMapInternal(@Nullable ArrayMap<String, Object> val) {
        ......
        for (int i=0; i<N; i++) {
            writeString(val.keyAt(i));
            //根據不同資料型別呼叫不同的write方法
            writeValue(val.valueAt(i));
        }
    }

5.4-writeValue

文章一開頭我們使用的是intent.putExtra("bmp",法海bitmap)

//android.os.Parcel
public final void writeValue(@Nullable Object v) {
    ......
    if (v instanceof Parcelable) {
        writeInt(VAL_PARCELABLE);
        writeParcelable((Parcelable) v, 0);
    } 
    ......
}
public final void writeParcelable(@Nullable Parcelable p, int parcelableFlags) {
    ......
    writeParcelableCreator(p);
    p.writeToParcel(this, parcelableFlags);
}

因為傳入的是Bitmap,我們看Bitmap.writeToParcel

5.5-Bitmap.writeToParcel

//android.graphics.Bitmap
public void writeToParcel(Parcel p, int flags) {
    noteHardwareBitmapSlowCall();
    //打開Bitmap.cpp找對應的native方法
    if (!nativeWriteToParcel(mNativePtr, mDensity, p)) {
        throw new RuntimeException("native writeToParcel failed");
    }
}

點擊打開Bitmap.cpp,查看Bitmap_writeToParcel方法

//frameworks/base/libs/hwui/jni/Bitmap.cpp

static jboolean Bitmap_writeToParcel(JNIEnv* env, jobject,
                                     jlong bitmapHandle, jint density, jobject parcel) {
    ......
    //獲得Native層的物件
    android::Parcel* p = parcelForJavaObject(env, parcel);
    SkBitmap bitmap;
    auto bitmapWrapper = reinterpret_cast<BitmapWrapper*>(bitmapHandle);
    //獲取SkBitmap
    bitmapWrapper->getSkBitmap(&bitmap);
    //寫入parcel
    p->writeInt32(!bitmap.isImmutable());
    ......
    p->writeInt32(bitmap.width());
    p->writeInt32(bitmap.height());
    p->writeInt32(bitmap.rowBytes());
    p->writeInt32(density);

    // Transfer the underlying ashmem region if we have one and it's immutable.
    android::status_t status;
    int fd = bitmapWrapper->bitmap().getAshmemFd();
    if (fd >= 0 && bitmap.isImmutable() && p->allowFds()) {
        //AshmemFd大于等于0 && bitmap不可變 && parcel允許帶Fd
        //符合上述條件,將fd寫入到parcel中
        status = p->writeDupImmutableBlobFileDescriptor(fd);
        if (status) {
            doThrowRE(env, "Could not write bitmap blob file descriptor.");
            return JNI_FALSE;
        }
        return JNI_TRUE;
    }

    //mutableCopy=true:表示bitmap是可變的
    const bool mutableCopy = !bitmap.isImmutable();
    //回傳像素存盤所需的最小記憶體
    size_t size = bitmap.computeByteSize();
    android::Parcel::WritableBlob blob;
    //獲取到一塊blob緩沖區,往下翻有代碼分析
    status = p->writeBlob(size, mutableCopy, &blob);
    ......
}

我們來看看writeBlob里面做了什么事情

5.6-Parcel::writeBlob

//frameworks/native/libs/binder/Parcel.cpp

static const size_t BLOB_INPLACE_LIMIT = 16 * 1024;  // 16k

status_t Parcel::writeBlob(size_t len, bool mutableCopy, WritableBlob* outBlob)
{
    status_t status;
    if (!mAllowFds || len <= BLOB_INPLACE_LIMIT) {
        //如果不允許帶FD 或者 資料小于等于16k,則直接將圖片寫入到parcel中
        status = writeInt32(BLOB_INPLACE);
        if (status) return status;
        void* ptr = writeInplace(len);
        if (!ptr) return NO_MEMORY;
        outBlob->init(-1, ptr, len, false);
        return NO_ERROR;
    }
    //不滿足上面的條件,即(允許Fd && len > 16k):
    //創建一個新的ashmem區域并回傳檔案描述符FD
    //ashmem-dev.cpp里面有注釋說明:
    //https://cs.android.com/android/platform/superproject/+/master:system/core/libcutils/ashmem-dev.cpp
    int fd = ashmem_create_region("Parcel Blob", len);
    if (fd < 0) return NO_MEMORY;
    //設定ashmem這塊區域是“可讀可寫”
    int result = ashmem_set_prot_region(fd, PROT_READ | PROT_WRITE);
    if (result < 0) {
        status = result;
    } else {
         //根據fd,映射 “len大小” 的mmap的空間
         void* ptr = ::mmap(nullptr, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
         ......
         if (!status) {
           //將fd寫入到parcel中
           status = writeFileDescriptor(fd, true /*takeOwnership*/);
            if (!status) {
                outBlob->init(fd, ptr, len, mutableCopy);
                return NO_ERROR;
             }
        }
        ......
    }
    ......
}

看到這里,大家應該知道我們為什么先分析Intent傳遞資料大小的上限了吧;
目錄5下面的 5.2-Bundle.writeToParcel已經說明清楚了,Intent啟動Activity的時候,禁用掉了檔案描述符;
所以: 在執行writeBlob方法只能執行到第一個分支,直接將圖片寫入到parcel中,我們在目錄4給出Intent傳遞資料大小限制的結論;

那么如何不受Intent禁用檔案描述符和資料大小的限制?

6.跨行程傳大圖

在Parcel類中看到writeValue方法里面有個分支,判斷當前value是不是IBinder,如果是IBinder型別的會呼叫writeStrongBinder把這個物件寫入到Parcel中;

所以我們可以使用Bundle的putBinder來把IBinder物件寫入到Parcel中,通過putBinder不會受Intent禁用檔案描述符的影響,資料大小也沒有限制,Bitmap寫入到parcel中默認是true,可以使用匿名共享記憶體(Ashmem);

6.1-單行程下putBinder用法

//定義一個IntentBinder,此方法僅在『同一個行程』下有效哦,切記切記!!!!
class IntentBinder(val imageBmp:Bitmap? = null): Binder()

//------------------------使用如下--------------------------//
//com.xxx.xxx.MainActivity
val bitmap = BitmapFactory.decodeStream(...)
startActivity(Intent(this,SecondActivity::class.java).putExtras(Bundle().apply {
        putBinder("myBinder",IntentBinder(bitmap))
}))

//------------------------獲取Bitmap并顯示如下--------------------------//
//com.xxx.xxx.SecondActivity
val bundle: Bundle? = intent.extras
val imageBinder:IntentBinder? = bundle?.getBinder("myBinder") as IntentBinder?
//拿到Binder中的Bitmap
val bitmap = imageBinder?.imageBmp
//自行壓縮后顯示到ImageView上.....

注意: 這個用法不能跨行程,喜歡動手的同學,可以試一試,給SecondActivity配置一個android:process=":remote",你會發現會報一個強制轉換的例外錯誤

//錯誤的用在多行程場景下,報錯如下:
java.lang.ClassCastException: android.os.BinderProxy cannot be cast to com.xxx.xxx.IntentBinder

🤔為什么可以通過這種方式傳遞物件?
Binder會為我們的物件創建一個全域的JNI參考,點擊查看android_util_Binder.cpp

//frameworks/base/core/jni/android_util_Binder.cpp
......
static struct bindernative_offsets_t
{
    // Class state.
    jclass mClass;
    jmethodID mExecTransact;
    jmethodID mGetInterfaceDescriptor;

    // Object state.
    jfieldID mObject;

} gBinderOffsets;
......
static const JNINativeMethod gBinderMethods[] = {
     /* name, signature, funcPtr */
    // @CriticalNative
    { "getCallingPid", "()I", (void*)android_os_Binder_getCallingPid },
    // @CriticalNative
    { "getCallingUid", "()I", (void*)android_os_Binder_getCallingUid },
    ......
    { "getExtension", "()Landroid/os/IBinder;", (void*)android_os_Binder_getExtension },
    { "setExtension", "(Landroid/os/IBinder;)V", (void*)android_os_Binder_setExtension },
};

const char* const kBinderPathName = "android/os/Binder";

//呼叫下面這個方法,完成Binder類的注冊
static int int_register_android_os_Binder(JNIEnv* env)
{
    //獲取Binder的class物件
    jclass clazz = FindClassOrDie(env, kBinderPathName);

    //內部創建全域參考,并將clazz保存到全域變數中
    gBinderOffsets.mClass = MakeGlobalRefOrDie(env, clazz);

    //獲取Java層的Binder的成員方法execTransact
    gBinderOffsets.mExecTransact = GetMethodIDOrDie(env, clazz, "execTransact", "(IJJI)Z");

    //獲取Java層的Binder的成員方法getInterfaceDescriptor
    gBinderOffsets.mGetInterfaceDescriptor = GetMethodIDOrDie(env, clazz, "getInterfaceDescriptor",
        "()Ljava/lang/String;");

    //獲取Java層的Binder的成員變數mObject
    gBinderOffsets.mObject = GetFieldIDOrDie(env, clazz, "mObject", "J");

    //注冊gBinderMethods中定義的函式
    return RegisterMethodsOrDie(
        env, kBinderPathName,
        gBinderMethods, NELEM(gBinderMethods));
}
......

6.2-多行程下putBinder用法

//先定義一個IGetBitmapService.aidl
package com.xxx.aidl;
interface IGetBitmapService {
    Bitmap getIntentBitmap();
}

//------------------------使用如下--------------------------//
//com.xxx.xxx.MainActivity      👉行程A
val bitmap = BitmapFactory.decodeStream(...)
startActivity(Intent(this,SecondActivity::class.java).putExtras(Bundle().apply {
    putBinder("myBinder",object: IGetBitmapService.Stub() {
        override fun getIntentBitmap(): Bitmap {
            return bitmap
        }
    })
}))

//------------------------獲取Bitmap并顯示如下--------------------------//
//com.xxx.xxx.SecondActivity      👉行程B
val bundle: Bundle? = intent.extras
//回傳IGetBitmapService型別
val getBitmapService = IGetBitmapService.Stub.asInterface(bundle?.getBinder("myBinder"))
val bitmap = getBitmapService.intentBitmap
//自行壓縮后顯示到ImageView上.....

推薦

從零開始打造自定義圖片加載框架

即學即用的Android高級技能大長圖加載原理及手寫實作

Android App開發——如何從打造一款可商用的圖片加載框架?

Android進階之如何打造一個圖片加載框架

更多Android知識點在下方小卡片中

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/302821.html

標籤:其他

上一篇:如何成為一名合格的 Android 開發工程師?

下一篇:21年最接地氣的 Android 面試總結心得

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more