Android6.0 Bitmap存储以及Parcel传输

如果想要对Android Bitmap进行更多的操作,理解好Bitmap的实现将会有非常大的帮助,另外Android在6.0中增加了asm存储图片。这篇文章就通过源码来分析Android6.0中的Bitmap。本文主要分析Java层与native层的Bitmap,以及Bitmap的储存和Parcel传输。源码基于6.0,所以会有一些新的特性。

Bitmap存储方式以及包含的属性

计算机里面图片都是作为数组来存储的,而在Android中Bitmap也是一样。在Java层的Bitmap数组保存为mBuffer。而在native层,Bitmap有四种保存方式,在Bitmap.h文件中有个枚举类:

enum class PixelStorageType {
    Invalid,
    External,
    Java,
    Ashmem,
};

Invalid表示图片已经失效了,一般图片free掉之后就会是这种状态。External是外部存储。Java是表示这个Bitmap对应着Java的Bitmap,此时Bitmap会保存着Java层Bitmap的存储数组的弱引用。而Ashmem则是对应着匿名共享内存,表示图片是存储在匿名共享内存当中。后三种类型在Bitmap中对应着一个union类型:

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;

另外因为图片是直接保存在一片内存区域,那么它也可以保存在匿名共享内存当中,这就是Fresco在5.0之前干的事情,而将图片放到匿名共享内存当中,不会自动GC,应用会更加流畅,因为不在Java堆,也不用关心Java堆大小的限制而导致OOM。

另外还包含几种属性:

width, height: 图片宽度和高度

mDensity: 设备密度

colorType: 图片颜色类型,RGB或者gray等,图片通道数量

rowBytes: 用来表示图片像素的字节数

alphaType: 图像透明度类型,是否有透明度或者没有透明度

isMutable: 是否易变的

这些属性在进行Parcel传输的时候,都会通过Parcel传递,另外也是为了方便图片操作。

Java层与native层Bitmap

Bitmap的主要实现是在native层,Java层的Bitmap相当于是native层的接口。

Java层Bitmap

Bitmap实际上分为Java层和native层的,Java层包含了一个mBuffer数组用来存储像素,但总的来说Java层只是一个方便Java层应用访问的接口,最终还是通过native层来保存图片内容。在Java层中,我们常用的接口可能是createBitmap,getPixel,setPixel等,但实际上这些函数最终都是调用native层接口实现的,下面是Java层Bitmap的创建函数:

private static Bitmap createBitmap(DisplayMetrics display, int width, int height,
        Config config, boolean hasAlpha) {
    if (width <= 0 || height <= 0) {
        throw new IllegalArgumentException("width and height must be > 0");
    }
    Bitmap bm = nativeCreate(null, 0, width, width, height, config.nativeInt, true); // 这!!!
    if (display != null) {
        bm.mDensity = display.densityDpi;
    }
    bm.setHasAlpha(hasAlpha);
    if (config == Config.ARGB_8888 && !hasAlpha) {
        nativeErase(bm.mFinalizer.mNativeBitmap, 0xff000000);
    }
    // No need to initialize the bitmap to zeroes with other configs;
    // it is backed by a VM byte array which is by definition preinitialized
    // to all zeroes.
    return bm;
}

Bitmap还有很多native方法,具体可以看Bitmap native 方法。我们重点看createBitmap。

另外在Java层与native层对应的标记是mNativeBitmap变量,它保存的是native层Bitmap的指针地址。这样在native层通过reinterpret_cast即可得到具体的对象。关于这个,可以看Binder机制的实现Android源码代理模式—Binder

native层

既然Bitmap的具体实现都是在native,那么看一下native层的Bitmap,native层的Bitmap在frameworks/base/core/jni/android/graphics/Bitmap.cpp中,对应的jni注册部分也在该文件下。看一下native层Bitmap的创建nativeCreate对应的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;
        }
    }

    // ARGB_4444 is a deprecated format, convert automatically to 8888
    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));
}

看看Bitmap的创建函数,从一创建开始,Bitmap就是先出现在native层的,Android中2D绘图是由skia框架实现的,在上述代码中就对应着SkBitmap。

而对于Java存储类型的Bitmap的创建是由GraphicsJNI的allocateJavaPixelRef完成的,allocateJavaPixelRef是从Java层分配像素数组,看看allocateJavaPixelRef的源码

android::Bitmap* GraphicsJNI::allocateJavaPixelRef(JNIEnv* env, SkBitmap* bitmap,
                                             SkColorTable* ctable) {
    const SkImageInfo& info = bitmap->info();
    if (info.fColorType == kUnknown_SkColorType) {
        doThrowIAE(env, "unknown bitmap configuration");
        return NULL;
    }

    size_t size;
    if (!computeAllocationSize(*bitmap, &size)) {
        return NULL;
    }

    // we must respect the rowBytes value already set on the bitmap instead of
    // attempting to compute our own.
    const size_t rowBytes = bitmap->rowBytes();
    // 在这里分配
    jbyteArray arrayObj = (jbyteArray) env->CallObjectMethod(gVMRuntime,
                                                             gVMRuntime_newNonMovableArray,
                                                             gByte_class, size); //在这创建Java层Array
    if (env->ExceptionCheck() != 0) {
        return NULL;
    }
    SkASSERT(arrayObj);
    jbyte* addr = (jbyte*) env->CallLongMethod(gVMRuntime, gVMRuntime_addressOf, arrayObj); //获取地址
    if (env->ExceptionCheck() != 0) {
        return NULL;
    }
    SkASSERT(addr);
    android::Bitmap* wrapper = new android::Bitmap(env, arrayObj, (void*) addr,
            info, rowBytes, ctable); //创建native层对象, 在Bitmap构造函数中mPixelStorage中存储了jweak引用。
    wrapper->getSkBitmap(bitmap); // 在这里会将mPixelStorage的弱引用转换为强引用
    // since we‘re already allocated, we lockPixels right away
    // HeapAllocator behaves this way too
    bitmap->lockPixels();

    return wrapper;
}

可以看到,native层是通过JNI方法,在Java层创建一个数组对象的,这个数组是对应在Java层的Bitmap对象的buffer数组,所以图像还是保存在Java堆的。而在native层这里它是通过weak指针来引用的,在需要的时候会转换为strong指针,用完之后又去掉strong指针,这样这个数组对象还是能够被Java堆自动回收。可以看一下native层的Bitmap构造函数:

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);//创建对Java层对象的弱引用
    mPixelStorage.java.jstrongRef = nullptr;
    mPixelRef.reset(new WrappedPixelRef(this, address, info, rowBytes, ctable));
    // Note: this will trigger a call to onStrongRefDestroyed(), but
    // we want the pixel ref to have a ref count of 0 at this point
    mPixelRef->unref();
}

里面jstrongRef一开始是赋值为null的,但是在bitmap的getSkBitmap方法会使用weakRef给他赋值:

void Bitmap::getSkBitmap(SkBitmap* outBitmap) {
    assertValid();
    android::AutoMutex _lock(mLock);
    // Safe because mPixelRef is a WrappedPixelRef type, otherwise rowBytes()
    // would require locking the pixels first.
    outBitmap->setInfo(mPixelRef->info(), mPixelRef->rowBytes());
    outBitmap->setPixelRef(refPixelRefLocked())->unref(); //refPixelRefLocked
    outBitmap->setHasHardwareMipMap(hasHardwareMipMap());
}
void Bitmap::pinPixelsLocked() {  //refPixelRefLocked会调用这个方法
    switch (mPixelStorageType) {
    case PixelStorageType::Invalid:
        LOG_ALWAYS_FATAL("Cannot pin invalid pixels!");
        break;
    case PixelStorageType::External:
    case PixelStorageType::Ashmem:
        // Nothing to do
        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;
    }
    }
}

在native层随时添加删除一个强引用,这样有利于更好地配合Java堆的垃圾回收。图片的数组可能会是非常耗内存的。

在创建了native层的Bitmap后,再用GraphicsJNI的createBitmap创建Java层的Bitmap对象:

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;
    // The caller needs to have already set the alpha type properly, so the
    // native SkBitmap stays in sync with the Java Bitmap.
    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);//创建Java层Bitmap对象
    hasException(env); // For the side effect of logging.
    return obj;
}

在创建过程中,将刚刚创建的Java层Array和native层的bitmap指针也都会传给Java层Bitmap的构造函数。

另外对于External存储类型的Bitmap,它的创建如下:

Bitmap::Bitmap(void* address, void* context, FreeFunc freeFunc,
            const SkImageInfo& info, size_t rowBytes, SkColorTable* ctable)
        : mPixelStorageType(PixelStorageType::External) {
    mPixelStorage.external.address = address;
    mPixelStorage.external.context = context;
    mPixelStorage.external.freeFunc = freeFunc;
    mPixelRef.reset(new WrappedPixelRef(this, address, info, rowBytes, ctable));
    // Note: this will trigger a call to onStrongRefDestroyed(), but
    // we want the pixel ref to have a ref count of 0 at this point
    mPixelRef->unref();
}

而Ashmem则是保存一个fd,以及asm地址和大小:

Bitmap::Bitmap(void* address, int fd,
            const SkImageInfo& info, size_t rowBytes, SkColorTable* ctable)
        : mPixelStorageType(PixelStorageType::Ashmem) {
    mPixelStorage.ashmem.address = address;
    mPixelStorage.ashmem.fd = fd;
    mPixelStorage.ashmem.size = ashmem_get_size_region(fd);
    mPixelRef.reset(new WrappedPixelRef(this, address, info, rowBytes, ctable));
    // Note: this will trigger a call to onStrongRefDestroyed(), but
    // we want the pixel ref to have a ref count of 0 at this point
    mPixelRef->unref();
}

native层Bitmap会针对不同的存储类型,做不同的处理。

Parcel传递

首先在Java层Bitmap实现了Parcelable接口,所以他是能够通过Parcel来传递的,看看Bitmap的parcelable部分的源码:

public final class Bitmap implements Parcelable {
    ...
    /**
     * Write the bitmap and its pixels to the parcel. The bitmap can be
     * rebuilt from the parcel by calling CREATOR.createFromParcel().
     * @param p    Parcel object to write the bitmap data into
     */
    public void writeToParcel(Parcel p, int flags) {
        checkRecycled("Can‘t parcel a recycled bitmap");
        if (!nativeWriteToParcel(mFinalizer.mNativeBitmap, mIsMutable, mDensity, p)) {
            throw new RuntimeException("native writeToParcel failed");
        }
    }
    public static final Parcelable.Creator<Bitmap> CREATOR
              = new Parcelable.Creator<Bitmap>() {

        public Bitmap More ...createFromParcel(Parcel p) {
            Bitmap bm = nativeCreateFromParcel(p);
            if (bm == null) {
                 throw new RuntimeException("Failed to unparcel Bitmap");
            }
            return bm;
        }
        public Bitmap[] More ...newArray(int size) {
            return new Bitmap[size];
        }
    };
    ...
}

写入和读取分别调用了nativeWriteToParcel,nativeCreateFromParcel。先看看nativeWriteToParcel对应的native层方法Bitmap_writeToParcel:

static jboolean Bitmap_writeToParcel(JNIEnv* env, jobject,
                                     jlong bitmapHandle,
                                     jboolean isMutable, jint density,
                                     jobject parcel) {

    //根据handle创建native层图片,写入图片相关的一些附加信息,width,height,colorType,density等等。
    if (parcel == NULL) {
        SkDebugf("------- writeToParcel null parcel\n");
        return JNI_FALSE;
    }

    android::Parcel* p = android::parcelForJavaObject(env, parcel);
    SkBitmap bitmap;

    android::Bitmap* androidBitmap = reinterpret_cast<Bitmap*>(bitmapHandle);
    androidBitmap->getSkBitmap(&bitmap);

    p->writeInt32(isMutable);
    p->writeInt32(bitmap.colorType());
    p->writeInt32(bitmap.alphaType());
    p->writeInt32(bitmap.width());
    p->writeInt32(bitmap.height());
    p->writeInt32(bitmap.rowBytes());
    p->writeInt32(density);

    if (bitmap.colorType() == kIndex_8_SkColorType) {
        SkColorTable* ctable = bitmap.getColorTable();
        if (ctable != NULL) {
            int count = ctable->count();
            p->writeInt32(count);
            memcpy(p->writeInplace(count * sizeof(SkPMColor)),
                   ctable->readColors(), count * sizeof(SkPMColor));
        } else {
            p->writeInt32(0);   // indicate no ctable
        }
    }
    // 关键看这部分传输代码!!!!
    // Transfer the underlying ashmem region if we have one and it‘s immutable.
    android::status_t status;
    int fd = androidBitmap->getAshmemFd(); //获取匿名共享内存,如果是图片是在匿名共享内存
    if (fd >= 0 && !isMutable && p->allowFds()) { //如果成功获取,并且图片不是mutable,同时允许fd(mAllowFds默认为True)
        status = p->writeDupImmutableBlobFileDescriptor(fd); //最终会直接把文件fd传过去
        if (status) {
            doThrowRE(env, "Could not write bitmap blob file descriptor.");
            return JNI_FALSE;
        }
        return JNI_TRUE;
    }
    // 如果不能通过fd传递,则传输Blob数据,也就是相当于直接把像素数据传递过去。
    // Copy the bitmap to a new blob.
    bool mutableCopy = isMutable;

    size_t size = bitmap.getSize();
    android::Parcel::WritableBlob blob;
    status = p->writeBlob(size, mutableCopy, &blob);
    if (status) {
        doThrowRE(env, "Could not copy bitmap to parcel blob.");
        return JNI_FALSE;
    }

    bitmap.lockPixels();
    const void* pSrc =  bitmap.getPixels();
    if (pSrc == NULL) {
        memset(blob.data(), 0, size);
    } else {
        memcpy(blob.data(), pSrc, size);
    }
    bitmap.unlockPixels();

    blob.release();
    return JNI_TRUE;
}

从源码可以知道,如果是匿名共享内存存储,那么writeToParcel会通过匿名共享内存的方式将匿名共享文件传递过去,看看writeDupFileDescriptor方法:

status_t Parcel::writeDupFileDescriptor(int fd)
{
    int dupFd = dup(fd);
    if (dupFd < 0) {
        return -errno;
    }
    status_t err = writeFileDescriptor(dupFd, true /*takeOwnership*/);
    if (err) {
        close(dupFd);
    }
    return err;
}

如果是保存的数组数据,那么会直接将像素数据转换为Blob来传递。这是在6.0的源码中是如此的,在5.0的源码中,还没有增加这些东西,5.0的源码中只有普通的将像素存储区域memcopy来传。Android在3.0中增加了inBitmap,在4.4增加了不同大小的图片使用inBitmap。

而nativeCreateFromParcel对应了native层的Bitmap_createFromParcel,在6.0的源码里面源码如下(去掉了DEBUG_PARCEL):

static jobject Bitmap_createFromParcel(JNIEnv* env, jobject, jobject parcel) {
    ......
    // 一开始读取图片相关的一些信息,比如说width, height, density, colorType等等,并存于SkImageInfo中。并且对ColorType的相关处理,这些占用的内存都很小,关键看像素的传递

    SkColorTable* ctable = NULL;
    if (colorType == kIndex_8_SkColorType) {
        int count = p->readInt32();
        if (count < 0 || count > 256) {
            // The data is corrupt, since SkColorTable enforces a value between 0 and 256,
            // inclusive.
            return NULL;
        }
        if (count > 0) {
            size_t size = count * sizeof(SkPMColor);
            const SkPMColor* src = (const SkPMColor*)p->readInplace(size);
            if (src == NULL) {
                return NULL;
            }
            ctable = new SkColorTable(src, count);
        }
    }

    // Read the bitmap blob.
    size_t size = bitmap->getSize();
    android::Parcel::ReadableBlob blob;
    android::status_t status = p->readBlob(size, &blob); //这里对应writeDupFileDescriptor
    if (status) {
        SkSafeUnref(ctable);
        doThrowRE(env, "Could not read bitmap blob.");
        return NULL;
    }
     // 关键看这部分传输代码!!!!
    // Map the bitmap in place from the ashmem region if possible otherwise copy.
    Bitmap* nativeBitmap;
    if (blob.fd() >= 0 && (blob.isMutable() || !isMutable) && (size >= ASHMEM_BITMAP_MIN_SIZE)) {

        // Dup the file descriptor so we can keep a reference to it after the Parcel
        // is disposed.
        int dupFd = dup(blob.fd());
        if (dupFd < 0) {
            blob.release();
            SkSafeUnref(ctable);
            doThrowRE(env, "Could not allocate dup blob fd.");
            return NULL;
        }

        // Map the pixels in place and take ownership of the ashmem region.
        nativeBitmap = GraphicsJNI::mapAshmemPixelRef(env, bitmap.get(),
                ctable, dupFd, const_cast<void*>(blob.data()), !isMutable);
        SkSafeUnref(ctable);
        if (!nativeBitmap) {
            close(dupFd);
            blob.release();
            doThrowRE(env, "Could not allocate ashmem pixel ref.");
            return NULL;
        }

        // Clear the blob handle, don‘t release it.
        blob.clear();
    } else {

        // Copy the pixels into a new buffer.
        nativeBitmap = GraphicsJNI::allocateJavaPixelRef(env, bitmap.get(), ctable);
        SkSafeUnref(ctable);
        if (!nativeBitmap) {
            blob.release();
            doThrowRE(env, "Could not allocate java pixel ref.");
            return NULL;
        }
        bitmap->lockPixels();
        memcpy(bitmap->getPixels(), blob.data(), size);
        bitmap->unlockPixels();

        // Release the blob handle.
        blob.release();
    }

    return GraphicsJNI::createBitmap(env, nativeBitmap,
            getPremulBitmapCreateFlags(isMutable), NULL, NULL, density);
}

这个是与writeToParcel相互对应的,如果是asm则直接读取文件fd,如果是数据,则传对应数据。

总结

上面就是Bitmap在Java层与native的表现,Bitmap的操作基本都是在native层,Java层与native层通过一个handle相互对应。在6.0Bitmap总共有四种存储形式,也增加了asm的存储。在进行Parcel传输的时候,针对asm,Parcel传输的fd,这样能够减少很多内存的消耗。在Android6.0内部,很多图片也开始存储在asm里面了。不过在Java层还没有提供将图片保存在匿名共享内存里面。

时间: 2024-08-30 01:08:47

Android6.0 Bitmap存储以及Parcel传输的相关文章

基于Android6.0的RIL框架层模块分析

本文与另外一篇分析RIL底层模块的文章是姐妹篇:基于Android6.0的RIL底层模块分析 根据手机网络制式的不同,通常系统中会扩展Phone.java这个类,扩展成GSMPhone和CDMAPhone.这个类主要是抽象整个手机来处理通信过程中与其他模块的交互.我们以GSMPhone为例,分析来电流程如何从底层传递到上层.该分析主要基于代码,所以会比较啰嗦. 以GSMPhone为例,在PhoneFactory类中有实例化该类的方法: public static Phone getGsmPhon

Android6.0机型上调用系统相机拍照返回的resultCode值始终等于0的问题

版权声明:本文为博主原创文章,未经博主允许不得转载. 正常情况下调用系统相机拍照: 如果拍照后点击的是“确定”图标,返回的resultCode = -1(Activity.RESULT_OK): 如果点击的是底部的“返回”键,返回的resultCode = 0(Activity.RESULT_CANCELED). 简单的调用系统相机的写法: //调用系统拍照 Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); String p

Android拍照,相册选择图片以及Android6.0权限管理

概述 在android开发过程中,拍照或者从相册中选择图片是很常见的功能.下面要说得这个案例比较简单,用户点击按钮选择拍照或者打开相册选择图片,然后将选中的图片显示在手机上.android6.0后,推出了动态权限管理.以往我们将涉及到的权限全部写在清单文件中,只要用户安装了该程序,程序在运行过程中都会获得相应权限.android6.0后,对于一些特别敏感的权限,开发者必须在程序中进行声明.拍照和从相册选择图片都是涉及到用户隐私的敏感权限,必须在程序中进行声明. 大概的流程 创建布局文件,这里不多

Android6.0系统权限那些事

Android6.0带来了新的权限管理方式,本文主要来源于官方文档,加入了自己的理解,目的是想总结Android6.0权限管理的新方式,其他部分可能主要是总结式的带过,后续再详细分析. 一.Security Architecture(安全体系结构) Android安全体系结构的核心是: 默认情况下没有任何应用有权限去执行对其他应用.操作系统.用户有不利影响的操作.这是一个核心的设计理念.记住这句话对后面的权限管理可以很好的理解. 正式由于这样的设计理念,默认情况下应用不能去读写用户的私有数据(比

Android6.0系统添加那些新特性

??? 北京时间9月30日凌晨在美国旧金山举行2015年秋季新品公布会.在公布会上代号为"Marshmallow(棉花糖)"的安卓6.0系统正式推出.新系统的总体设计风格依旧保持扁平化的MeterialDesign风格. Android6.0在对软件体验与执行性能上进行了大幅度的优化.安卓权限系统被又一次设计了. ??? 全新的Android M相比眼下的Android Lollipop(5.0)有二十项重大的改进: ? ? 原文博客请參考:点击打开链接 ??? 一:App Permi

Openerp 7.0 附件存储位置

? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58         我们知道对OpenERP中的每个内部对象(比如:业务伙伴,采购订单,销售订单,发货单,等等)我们都可以添加任意的附件,如图片,文档,视频等.那么这些附

Android6.0使用BaiDu地图SDK动态获取定位权限

1.报错原因: 在集成百度地图SDK的时候在手机上无法定位,检查没有任何错误,最后通过搜索才知道是Android版本为6.0的问题,这是因为在Android6.0采用了运行时权限(RuntimePermissions),Android6.0的权限一般分为两种,一种时普通权限,可以直接获取,其它的运行时权限,需要提示用户手动同意之后,才能获取. 失败的原因就是,小米手机MIUI是Android6.0.1,如果不加动态获取权限的代码,是不会提示的,没有得到权限,当然无法定位. 2.解决代码: pri

Android6.0获取权限

照着<第一行代码>打代码,然并卵,感叹技术进步的神速.最后提醒一点:IT类的书籍一定要注意出版时间!出版时间!出版时间!重要的事情说三遍 问题出在android6.0的权限获取问题上,以前只要在Manifest.xml一次性获取便可以了,android6.0之后要手动去获得运行时权限才行. 1. 新增的api ContextCompact.checkSelfPermission()--->检查是否有权限 ActivityCompat.requestPermission()--->去

oracle11g 数据库导出报“ EXP-00003: 未找到段 (0,0) 的存储定义”错误的解决方案

导出oracle11.2.0.2的服务器的数据时,报"EXP-00003: 未找到段 (0,0) 的存储定义"错误.初步分析是由于数据表是空表导致该问题. Oracle 11G在用EXPORT导出时,空表不能导出 11GR2中有个新特性,当表无数据时,不分配segment,以节省空间 解决方法:一. insert一行,再rollback就产生segment了. 该方法是在在空表中插入数据,再删除,则产生segment.导出时则可导出空表.二. 设置deferred_segment_cr