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.cpp、 IPCThreadState.cpp、ProcessState.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上.....