Android开发者文档笔记(三)

**在APP中使用图片和动画


*如何有效地加载容量大的位图


图片有各种大小和形状,在很多情况下它们的大小超过了所要求的平台的用户界面的分辨率。例如,系统应用程序显示图片库通常使用Android设备的相机分辨率远高于你的设备的屏幕密度。鉴于手机的运行内存有限,理想情况下我们只需要加载一个适应UI组件大小的低分辨率版本的图片到于内存中。




在各种资源中,Bitmapfactory类提供了许多加工方法(如加工字节数组,文件,资源等等)用来生成位图Bitmap。这些方法很容就把内存分配出去,从而导致内存泄漏异常。为此,在生成位图之前,我们必须查看你所要生成的位图大小。


BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;


在知道了大小之后,我们就可以决定以什么样的方式载入图片,通过在Bitmapfactory.Options类中设置inSampleSize为true来告诉编译器,我们所要进行的改变。例如,一个分辨率为2048x1536的图像变成inSampleSize=4的分辨率为512x384的图像,此时图片所占用的内存就是0.75MB,而不是12MB的完整图像(ARGB_8888的图片配置)。有一个方法可以计算样本大小值inSampleSzie=2的幂次方的目标的高度和宽度:

//计算缩小的比例值
public static int calculateInSampleSize(
            BitmapFactory.Options options, int reqWidth, int reqHeight) {
    // Raw height and width of image
    final int height = options.outHeight;
    final int width = options.outWidth;
    int inSampleSize = 1;


    if (height > reqHeight || width > reqWidth) {


        final int halfHeight = height / 2;
        final int halfWidth = width / 2;


        // Calculate the largest inSampleSize value that is a power of 2 and keeps both
        // height and width larger than the requested height and width.
        while ((halfHeight / inSampleSize) > reqHeight
                && (halfWidth / inSampleSize) > reqWidth) {
            inSampleSize *= 2;
        }
    }


    return inSampleSize;
}





//重绘缩小后的位图
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
        int reqWidth, int reqHeight) {


    // First decode with inJustDecodeBounds=true to check dimensions
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeResource(res, resId, options);


    // Calculate inSampleSize
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);


    // Decode bitmap with inSampleSize set
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeResource(res, resId, options);
}


//在UI组件中添加图像
mImageView.setImageBitmap(
    decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));




这就是降分辨率的整个过程,计算完整图像的大小,再算出降到目标分辨率所需要的参数值,通过调整图像的边界,重新生成Bitmap对象





*在AsyncTask中处理图像




对于从外部内存获取或者从网络中下载下来的图像的分辨率的降低处理不应该放在UI线程中进行,由于有网速,图片大小以及处理能力的差异,降分辨率处理的时间机油可能变得很长,若在UI线程中进行就会导致线程阻塞而使app崩溃。

//下载
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
    private final WeakReference<ImageView> imageViewReference;
    private int data = 0;


    public BitmapWorkerTask(ImageView imageView) {
        // Use a WeakReference to ensure the ImageView can be garbage collected
        imageViewReference = new WeakReference<ImageView>(imageView);
    }


    // Decode image in background.
    @Override
    protected Bitmap doInBackground(Integer... params) {
        data = params[0];
        return decodeSampledBitmapFromResource(getResources(), data, 100, 100));
    }


    // Once complete, see if ImageView is still around and set bitmap.
    @Override
    protected void onPostExecute(Bitmap bitmap) {
        if (imageViewReference != null && bitmap != null) {
            final ImageView imageView = imageViewReference.get();
            if (imageView != null) {
                imageView.setImageBitmap(bitmap);
            }
        }
    }
}


//启动
public void loadBitmap(int resId, ImageView imageView) {
    BitmapWorkerTask task = new BitmapWorkerTask(imageView);
    task.execute(resId);
}



在上述代码中,使用的是AsyncTask这个方法,并且添加了垃圾回收机制用来回收不用的图像资源。当然,如果加载量多的话,可以使用一些第三方的框架,如Volley。




















*并发处理:
例子下载


在例子中使用了WeakReference这个方法,弱引用的概念是:在Java中一个对象要被回收,需要有两个条件:1、没有任何引用指向它。2、能被Java中的垃圾回收机制运行。
而简单的对象在被使用完后,是可以被直接回收,但是cache中缓存的数据是不会被GC运行的,所以也就不会被回收,需要手动清除,所以Java中引入了Weak Reference这个概念,
当一个对象仅仅被Weak Reference所指向时,必然可以被GC运行和回收


弱引用对象:WeakReference<BitmapWorkerTask> weakReferenceTask = new WeakReference(BitmapWorkerTask)(bitmapWorkerTask);
强引用:BitmapWorkerTask task = newBitmapWorkerTask();
判断对象是否被回收:weakReference.get();
如果对象创建后被闲置,就会自动回收


Soft Reference比Weak Reference多一个条件被回收:当运行内存不足时


使用弱引用的三个前提:1、对象有需要被Cache的价值,2、对象不容易被回收。3.对象占内存





*MemoryLruCache缓存图片:



//create a LruCacahe 
private LruCache<String, Bitmap> mMemoryCache;


@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    // Get max available VM memory, exceeding this amount will throw an
    // OutOfMemory exception. Stored in kilobytes as LruCache takes an
    // int in its constructor.
    final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);


    // Use 1/8th of the available memory for this memory cache.
    final int cacheSize = maxMemory / 8;


    mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
        @Override
        protected int sizeOf(String key, Bitmap bitmap) {
            // The cache size will be measured in kilobytes rather than
            // number of items.
            return bitmap.getByteCount() / 1024;
        }
    };
    ...
}


public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
    if (getBitmapFromMemCache(key) == null) {
        mMemoryCache.put(key, bitmap);
    }
}


public Bitmap getBitmapFromMemCache(String key) {
    return mMemoryCache.get(key);
}










//loading bitmap
public void loadBitmap(int resId, ImageView imageView) {
    final String imageKey = String.valueOf(resId);


    final Bitmap bitmap = getBitmapFromMemCache(imageKey);
    if (bitmap != null) {
        mImageView.setImageBitmap(bitmap);
    } else {
        mImageView.setImageResource(R.drawable.image_placeholder);
        BitmapWorkerTask task = new BitmapWorkerTask(mImageView);
        task.execute(resId);
    }
}




//how to load bitmap from disk or internet
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
    ...
    // Decode image in background.
    @Override
    protected Bitmap doInBackground(Integer... params) {
        final Bitmap bitmap = decodeSampledBitmapFromResource(
                getResources(), params[0], 100, 100));
        addBitmapToMemoryCache(String.valueOf(params[0]), bitmap);
        return bitmap;
    }
    ...
}	









*DiskLruCache缓存图片


private DiskLruCache mDiskLruCache;
private final Object mDiskCacheLock = new Object();
private boolean mDiskCacheStarting = true;
private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB
private static final String DISK_CACHE_SUBDIR = "thumbnails";


@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    // Initialize memory cache
    ...
    // Initialize disk cache on background thread
    File cacheDir = getDiskCacheDir(this, DISK_CACHE_SUBDIR);
    new InitDiskCacheTask().execute(cacheDir);
    ...
}


class InitDiskCacheTask extends AsyncTask<File, Void, Void> {
    @Override
    protected Void doInBackground(File... params) {
        synchronized (mDiskCacheLock) {
            File cacheDir = params[0];
            mDiskLruCache = DiskLruCache.open(cacheDir, DISK_CACHE_SIZE);
            mDiskCacheStarting = false; // Finished initialization
            mDiskCacheLock.notifyAll(); // Wake any waiting threads
        }
        return null;
    }
}


class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
    ...
    // Decode image in background.
    @Override
    protected Bitmap doInBackground(Integer... params) {
        final String imageKey = String.valueOf(params[0]);


        // Check disk cache in background thread
        Bitmap bitmap = getBitmapFromDiskCache(imageKey);


        if (bitmap == null) { // Not found in disk cache
            // Process as normal
            final Bitmap bitmap = decodeSampledBitmapFromResource(
                    getResources(), params[0], 100, 100));
        }


        // Add final bitmap to caches
        addBitmapToCache(imageKey, bitmap);


        return bitmap;
    }
    ...
}


public void addBitmapToCache(String key, Bitmap bitmap) {
    // Add to memory cache as before
    if (getBitmapFromMemCache(key) == null) {
        mMemoryCache.put(key, bitmap);
    }


    // Also add to disk cache
    synchronized (mDiskCacheLock) {
        if (mDiskLruCache != null && mDiskLruCache.get(key) == null) {
            mDiskLruCache.put(key, bitmap);
        }
    }
}


public Bitmap getBitmapFromDiskCache(String key) {
    synchronized (mDiskCacheLock) {
        // Wait while disk cache is started from background thread
        while (mDiskCacheStarting) {
            try {
                mDiskCacheLock.wait();
            } catch (InterruptedException e) {}
        }
        if (mDiskLruCache != null) {
            return mDiskLruCache.get(key);
        }
    }
    return null;
}


// Creates a unique subdirectory of the designated app cache directory. Tries to use external
// but if not mounted, falls back on internal storage.
public static File getDiskCacheDir(Context context, String uniqueName) {
    // Check if media is mounted or storage is built-in, if so, try and use external cache dir
    // otherwise use internal cache dir
    final String cachePath =
            Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) ||
                    !isExternalStorageRemovable() ? getExternalCacheDir(context).getPath() :
                            context.getCacheDir().getPath();


    return new File(cachePath + File.separator + uniqueName);
}



*其中使用到线程的同步控制,Disk的访问和写入都是不能同时写入或读取的,在这些地方需要多加注意。还有就是对于Disk缓存的读写,必须建立在Disk缓存初始化完成的前提下,因为初始化操作不是放在MainThread中进行的。



*当手机设备的配置改变时,比如转屏后,无论是Activity还是Fragment都需要重新创建。此时,如果你的Activity或者Fragment的UI中有大量数据,如有大量的图片显示,这样重新创建就会消耗更多重复的资源,当然Activity中是自动进行缓存,提供现场恢复。而Fragment就需要我们用Memory Cache进行手动缓存。以往设置Fragment现场恢复的时候,都认为这个方式有点鸡肋,因为并不能打到跟Activity一样的效果,但是,看了文档,才发现,Fragment必须手动申请Memoery Cache缓存才可以。



private LruCache<String, Bitmap> mMemoryCache;


@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    RetainFragment retainFragment =
            RetainFragment.findOrCreateRetainFragment(getFragmentManager());
    mMemoryCache = retainFragment.mRetainedCache;
    if (mMemoryCache == null) {
        mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
            ... // Initialize cache here as usual
        }
        retainFragment.mRetainedCache = mMemoryCache;
    }
    ...
}


class RetainFragment extends Fragment {
    private static final String TAG = "RetainFragment";
    public LruCache<String, Bitmap> mRetainedCache;


    public RetainFragment() {}


    public static RetainFragment findOrCreateRetainFragment(FragmentManager fm) {
        RetainFragment fragment = (RetainFragment) fm.findFragmentByTag(TAG);
        if (fragment == null) {
            fragment = new RetainFragment();
            fm.beginTransaction().add(fragment, TAG).commit();
        }
        return fragment;
    }


    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRetainInstance(true);
    }
}




*管理位图内存:
看不懂,实力不够,没明白意图


*提示:一般的androdi设备分配给一个app的内存是32mb,而一个Cache比较合理的是使用其内存的八分之一,假如一张为1024*843分辨率的32位真彩照片,那么这张照片的大小就应该为1024*843*4 bytes,而4MB/(1024*843*4 bytes)则是能缓存的张数。


  另外,8位的真彩照片一个像素点是8bit,1字节
16位的真彩照片一个像素点是16bit,2字节
24位的真彩照片一个像素点是24bit,3字节
32位的真彩照片一个像素点是32bit,4字节


黑白二值图像,不压缩的情况下一个像素点是1bit

你可能感兴趣的:(Android开发,内存,文档,应用,位图)