Android进阶:Bitmap的高效加载和Cache

二、Android中的缓存策略

===============

缓存策略在Android中应用广泛。使用缓存可以节省流量、提高效率

加载图片时,一般会从网络加载,然后缓存在存储设备上,这样下次就不用请求网络了。并且通常也会缓存一份到内存中,这样下次可以直接取内存中的缓存,要比从存储设备中取快很多。所以一般是先从内存中取,内存没有就取存储设备,也没有才会请求网络,这就是所谓的“三级缓存”。此策略同样适用其他文件类型。

缓存策略中的操作有 添加缓存、获取缓存、删除缓存。添加和获取比较好理解,删除缓存是啥意思?因为缓存大小是有限制的,像移动设备的 内存 和 设备存储都是有限的,不能无限制的添加,只能限定一个最大缓存,到达最大时就会删除一部分缓存。但是删除哪一部分缓存呢?删除 缓存创建时间最老的吗,如果它经常用到呢,好像不太完美,当然这也是一种缓存算法。

目前经典的缓存算法是LRU(Least Recently Used),最近最少使用。具体就是 当缓存满时,会先删除那些 近期 最少使用 的缓存。使用LRU算法的缓存有两种,LruCache和DiskLruCache,LruCache是使用内存缓存,DiskLruCache是实现磁盘缓存。

2.1 LruCache


LruCache是泛型类,使用方法如下: 提供最大缓存容量,创建LruCache实例,并重写其sizeOf方法来计算缓存对象的大小。最大容量和缓存对象大小单位要一致。

private void testLruCache() {

//当前进程的最大内存,单位M

long maxMemory = Runtime.getRuntime().maxMemory() / 1024 / 1024;

//取进程内存的1/8

int cacheMaxSize = (int) (maxMemory/8);

//创建Bitmap实例

mBitmapLruCache = new LruCache<String, Bitmap>(cacheMaxSize){

@Override

protected int sizeOf(String key, Bitmap value) {

//缓存对象bitmap的大小,单位M

return value.getByteCount()/1024/1024;

}

@Override

protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {

//移除旧缓存时会调用,可以在这里进行像资源回收的工作。

//evicted为true,表示此处移除是因为快满了要腾出空间

}

};

//添加缓存

mBitmapLruCache.put(“1”,mBitmap);

//获取缓存

Bitmap bitmap = mBitmapLruCache.get(“1”);

ivBitamp.setImageBitmap(bitmap);

//删除缓存,一般不会用,因为快满时会自动删近期最少使用的缓存,就是它的核心功能

mBitmapLruCache.remove(“1”);

}

可见使用很简单,那么LruCache是怎么完成 删除“近期最少使用” 的呢?看下LruCache的代码:

public class LruCache<K, V> {

//此map以强引用的方式存储缓存对象

private final LinkedHashMap<K, V> map;

//当前缓存的大小(带单位的)

private int size;

//缓存最大容量(带单位的)

private int maxSize;

public LruCache(int maxSize) {

if (maxSize <= 0) {

throw new IllegalArgumentException(“maxSize <= 0”);

}

this.maxSize = maxSize;

//LinkedHashMap是按照 访问顺序 排序的,所以get、put操作都会把要存的k-v放在队尾

this.map = new LinkedHashMap<K, V>(0, 0.75f, true);

}

/**

  • 获取缓存,同时会把此k-v放在链表的尾部

*/

public final V get(K key) {

if (key == null) {

throw new NullPointerException(“key == null”);

}

V mapValue;

//get是线程安全的操作

synchronized (this) {

//LinkedHashMap的get方法中调afterNodeAccess,会移到链表尾部

mapValue = map.get(key);

if (mapValue != null) {

hitCount++;

return mapValue;

}

missCount++;

}

}

/**

  • 缓存key-value,value会存在 队尾

  • @return 之前也是这个key存的value

*/

public final V put(K key, V value) {

if (key == null || value == null) {

//不允许 null key、null value

throw new NullPointerException(“key == null || value == null”);

}

V previous;

//可见put操作是线程安全的

synchronized (this) {

putCount++;

size += safeSizeOf(key, value);

//强引用存入map(不会被动地被系统回收),其因为是LinkedHashMap,会放在队尾

previous = map.put(key, value);

if (previous != null) {

/
/如果前面已这个key,那么替换后调整下当前缓存大小

size -= safeSizeOf(key, previous);

}

}

if (previous != null) {

entryRemoved(false, key, previous, value);

}

//重新调整大小

trimToSize(maxSize);

return previous;

}

/**

  • 比较 当前已缓存的大小 和最大容量,决定 是否删除

*/

private void trimToSize(int maxSize) {

while (true) {

K key;

V value;

synchronized (this) {

if (size < 0 || (map.isEmpty() && size != 0)) {

throw new IllegalStateException(getClass().getName()

  • “.sizeOf() is reporting inconsistent results!”);

}

if (size <= maxSize) {

//大小还没超过最大值

break;

}

//已经达到最大容量

//因为是访问顺序,所以遍历的最后一个就是最近没有访问的,那么就可以删掉它了!

Map.Entry<K, V> toEvict = null;

for (Map.Entry<K, V> entry : map.entrySet()) {

toEvict = entry;

}

// END LAYOUTLIB CHANGE

if (toEvict == null) {

break;

}

key = toEvict.getKey();

value = toEvict.getValue();

map.remove(key);

size -= safeSizeOf(key, value);

evictionCount++;

}

//因为是为了腾出空间,所以这个回调第一个参数是true

entryRemoved(true, key, value, null);

}

}

protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}

}

由以上代码及注释,可见LruCache的算法实现是依靠 设置了访问顺序的LinkedHashMap。因为是访问顺序模式,get、put操作都会调整k-v到链表尾部。在缓存将满时,遍历LinkedHashMap,因为是访问顺序模式,所以遍历的最后一个就是最近没有使用的,然后删除即可。

2.2 DiskLruCache


DiskLruCache是实现磁盘缓存,所以需要设备存储的读写权限;一般是从网络请求图片后缓存到磁盘中,所以还需要网络权限。

DiskLruCache,不是官方提供,所以需要引入依赖:

implementation ‘com.jakewharton:disklrucache:2.0.2’

  • DiskLruCache的创建,不是通过new,而是open方法,需要传入缓存目录、最大缓存容量。

  • 缓存的添加,是通过Editor,缓存对象的编辑器。传入图片url的key 调用DiskLruCache的edit方法获取Editor(如果缓存正在被编辑就会返回null),可以从Editor得到文件输出流,这样就可以写入到文件系统了。

  • 缓存的获取,传入图片url的key 调用DiskLruCache的get方法 得到SnapShot,可从SnapShoty获取文件输入流,这样就用BitmapFactory得到bitmap了。

  • 缓存的删除,DiskLruCache的remove方法可以删除key对应的缓存。

通过查看源码,发现LinkedHashMap内部也是维护了访问顺序的LinkedHashMap,原理上和LruCache是一致的。只是使用上有点点复杂,毕竟涉及文件的读写。

具体使用及注意点如下代码:

private void testDiskLruCache(String urlString) {

long maxSize = 5010241024;

try {

//一、创建DiskLruCache

//第一个参数是要存放的目录,这里选择外部缓存目录(若app卸载此目录也会删除);

//第二个是版本一般设1;第三个是缓存节点的value数量一般也是1;

//第四个是最大缓存容量这里取50M

mDiskLruCache = DiskLruCache.open(getExternalCacheDir(), 1, 1, maxSize);

//二、缓存的添加:1、通过Editor,把图片的url转成key,通过edit方法得到editor,然后获取输出流,就可以写到文件系统了。

DiskLruCache.Editor editor = mDiskLruCache.edit(hashKeyFormUrl(urlString));

if (editor != null) {

//参数index取0(因为上面的valueCount取的1)

OutputStream outputStream = editor.newOutputStream(0);

boolean downSuccess = downloadPictureToStream(urlString, outputStream);

if (downSuccess) {

//2、编辑提交,释放编辑器

editor.commit();

}else {

editor.abort();

}

//3、写到文件系统,会检查当前缓存大小,然后写到文件

mDiskLruCache.flush();

}

} catch (IOException e) {

e.printStackTrace();

}

//三、缓存的获取

try {

String key = hashKeyFormUrl(urlString);

DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);

FileInputStream inputStream = (FileInputStream)snapshot.getInputStream(0);

// Bitmap bitmap = BitmapFactory.decodeStream(inputStream);

// mIvBitamp.setImageBitmap(bitmap);

//注意,一般需要采样加载,但文件输入流是有序的文件流,采样时两次decodeStream影响文件流的文职属性,导致第二次decode是获取是null

//为解决此问题,可用文件描述符

FileDescriptor fd = inputStream.getFD();

//采样加载(就是前面讲的bitmap的高效加载)

BitmapFactory.Options options = new BitmapFactory.Options();

options.inJustDecodeBounds=true;

BitmapFactory.decodeFileDescriptor(fd,null,options);

ViewGroup.LayoutParams layoutParams = mIvBitamp.getLayoutParams();

options.inSampleSize = getInSampleSize(layoutParams.width, layoutParams.height, options.outWidth, options.outHeight);

options.inJustDecodeBounds = false;

Bitmap bitmap = BitmapFactory.decodeFileDescriptor(fd, null, options);

//存入内容缓存,绘制到view。(下次先从内存缓存获取,没有就从磁盘缓存获取,在没有就请求网络–“三级缓存”)

mBitmapLruCache.put(key,bitmap);

runOnUiThread(new Runnable() {

@Override

public void run() {

mIvBitamp.setImageBitmap(mBitmapLruCache.get(key));

}

});

} catch (IOException e) {

e.printStackTrace();

}

}

/**

  • 下载图片到文件输入流

*/

private boolean downloadPictureToStream(String urlString, OutputStream outputStream) {

URL url = null;

HttpURLConnection urlConnection = null;

BufferedInputStream in = null;

BufferedOutputStream out = null;

try {

url = new URL(urlString);

urlConnection = (HttpURLConnection) url.openConnection();

in = new BufferedInputStream(urlConnection.getInputStream());

out = new BufferedOutputStream(outputStream);

int b;

while ((b=in.read()) != -1) {

//写入文件输入流

out.write(b);

}

return true;

} catch (IOException e) {

e.printStackTrace();

}finally {

if (urlConnection != null) {

urlConnection.disconnect();

}

try {

if (in != null) {in.close();}

if (out != null) {out.close();}

} catch (IOException e) {

e.printStackTrace();

}

}

return false;

}

/**

  • 图片的url转成key,使用MD5

*/

private String hashKeyFormUrl(String url) {

try {

MessageDigest digest = MessageDigest.getInstance(“MD5”);

return byteToHexString(digest.digest(url.getBytes()));

} catch (NoSuchAlgorithmException e) {

e.printStackTrace();

}

return null;

}

private String byteToHexString(byte[] bytes) {

StringBuffer stringBuffer = new StringBuffer();

for (int i = 0; i < bytes.length; i++) {

String hex = Integer.toHexString(0XFF & bytes[i]);

if (hex.length()==1) {

stringBuffer.append(0);

}

stringBuffer.append(hex);

}

return stringBuffer.toString();

}

三、ImageLoader

=============

前面说的 Bitmap的高效加载、LruCache、DiskLruCache,是一个图片加载框架必备的功能点。下面就来封装一个ImageLoader。首先罗列 实现的要点

  • 图片压缩,就是采样加载
  • 内存缓存,LruCache
  • 磁盘缓存,DiskLruCache
  • 网络获取,请求网络url
  • 同步加载,外部子线程同步执行
  • 异步加载,ImageLoader内部线程异步执行

说明,

  1. 三级缓存“的逻辑:加载时 先从内存缓存获取,有就返回bitmap绘制图片到view,若没有就从磁盘缓存获取;磁盘缓存有就返回bitmap并缓存到内存缓存,没有就请求网络;网络请求回来,就缓存到磁盘缓存,然后从磁盘缓存获取返回。

  2. 同步加载,是在外部的子线程中执行,同步加载方法内部没有开线程,所以加载过程是耗时的 会阻塞 外部的子线程,加载完成后 需要自行切到主线程绘制到view。

  3. 异步加载,外部可在任意线程执行,因为内部实现是在子线程(线程池)加载,并且内部会通过Handler切到主线程,只需要传入view,内部就可直接绘制Bitmap到view。

详细如下

public class ImageLoader {

private static final String TAG = “ImageLoader”;

private static final long KEEP_ALIVE_TIME = 10L;

private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();

private static final int CORE_THREAD_SIZE = CPU_COUNT + 1;

private static final int THREAD_SIZE = CPU_COUNT * 2 + 1;

private static final int VIEW_TAG_URL = R.id.view_tag_url;

private static final Object object = new Object();

private ThreadPoolExecutor mExecutor;

private Handler mMainHandler;

private Context mApplicationContext;

private static volatile ImageLoader mImageLoader;

private LruCache<String, Bitmap> mLruCache;

private DiskLruCache mDiskLruCache;

/**

  • 磁盘缓存最大容量,50M

*/

private static final long DISK_LRU_CACHE_MAX_SIZE = 50 * 1024 * 1024;

/**

  • 当前进程的最大内存,取进程内存的1/8

*/

private static final long MEMORY_CACHE_MAX_SIZE = Runtime.getRuntime().maxMemory() / 8;

public ImageLoader(Context context) {

if (context == null) {

throw new RuntimeException(“context can not be null !”);

}

mApplicationContext = context.getApplicationContext();

initLruCache();

initDiskLruCache();

initAsyncLoad();

}

public static ImageLoader with(Context context){

if (mImageLoader == null) {

synchronized (object) {

if (mImageLoader == null) {

mImageLoader = new ImageLoader(context);

}

}

}

return mImageLoader;

}

private void initAsyncLoad() {

mExecutor = new ThreadPoolExecutor(CORE_THREAD_SIZE, THREAD_SIZE,

KEEP_ALIVE_TIME, TimeUnit.SECONDS,

new LinkedBlockingQueue(), new ThreadFactory() {

private final AtomicInteger count = new AtomicInteger(1);

@Override

public Thread newThread(Runnable runnable) {

return new Thread(runnable, "load bitmap thread "+ count.getAndIncrement());

}

});

mMainHandler = new Handler(Looper.getMainLooper()){

@Override

public void handleMessage(@NonNull Message msg) {

LoadResult result = (LoadResult) msg.obj;

ImageView imageView = result.imageView;

Bitmap bitmap = result.bitmap;

String url = result.url;

if (imageView == null || bitmap == null) {

return;

}

//此判断是 避免 ImageView在列表中复用导致图片错位的问题

if (url.equals(imageView.getTag(VIEW_TAG_URL))) {

imageView.setImageBitmap(bitmap);

}else {

Log.w(TAG, “handleMessage: set image bitmap,but url has changed,ignore!”);

}

}

};

}

private void initLruCache() {

mLruCache = new LruCache<String, Bitmap>((int) MEMORY_CACHE_MAX_SIZE){

@Override

protected int sizeOf(String key, Bitmap value) {

//缓存对象bitmap的大小,单位要和MEMORY_CACHE_MAX_SIZE一致

return value.getByteCount();

}

@Override

protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {

//移除旧缓存时会调用,可以在这里进行像资源回收的工作。

}

};

}

private void initDiskLruCache() {

File externalCacheDir = mApplicationContext.getExternalCacheDir();

if (externalCacheDir != null) {

long usableSpace = externalCacheDir.getUsableSpace();

if (usableSpace < DISK_LRU_CACHE_MAX_SIZE){

//剩余空间不够了

Log.e(TAG, “initDiskLruCache: “+“UsableSpace=”+usableSpace+” , not enough(target 50M),cannot creat diskLruCache!”);

return;

}

}

//一、创建DiskLruCache

//第一个参数是要存放的目录,这里选择外部缓存目录(若app卸载此目录也会删除);

//第二个是版本一般设1;第三个是缓存节点的value数量一般也是1;

//第四个是最大缓存容量这里取50M

try {

this.mDiskLruCache = DiskLruCache.open(mApplicationContext.getExternalCacheDir(), 1, 1, DISK_LRU_CACHE_MAX_SIZE);

} catch (IOException e) {

e.printStackTrace();

Log.e(TAG, "initDiskLruCache: "+e.getMessage());

}

}

/**

  • 缓存bitmap到内存

  • @param url url

  • @param bitmap bitmap

*/

private void addBitmapMemoryCache(String url, Bitmap bitmap) {

String key = UrlKeyTransformer.transform(url);

if (mLruCache.get(key) == null && bitmap != null) {

mLruCache.put(key,bitmap);

}

}

/**

  • 从内存缓存加载bitmap

  • @param url url

  • @return

*/

private Bitmap loadFromMemoryCache(String url) {

return mLruCache.get(UrlKeyTransformer.transform(url));

}

/**

  • 从磁盘缓存加载bitmap(并添加到内存缓存)

  • @param url url

  • @param requestWidth 要求的宽

  • @param requestHeight 要求的高

  • @return bitmap

*/

private Bitmap loadFromDiskCache(String url, int requestWidth, int requestHeight) throws IOException {

if (Looper.myLooper()==Looper.getMainLooper()) {

Log.w(TAG, “loadFromDiskCache from Main Thread may cause block !”);

}

if (mDiskLruCache == null) {

return null;

}

DiskLruCache.Snapshot snapshot = null;

String key = UrlKeyTransformer.transform(url);

snapshot = mDiskLruCache.get(key);

if (snapshot != null) {

FileInputStream inputStream = (FileInputStream)snapshot.getInputStream(0);

//Bitmap bitmap = BitmapFactory.decodeStream(inputStream);

//一般需要采样加载,但文件输入流是有序的文件流,采样时两次decodeStream影响文件流的位置属性,

//导致第二次decode是获取是null,为解决此问题,可用文件描述符。

FileDescriptor fd = inputStream.getFD();

Bitmap bitmap = BitmapSampleDecodeUtil.decodeFileDescriptor(fd, requestWidth, requestHeight);

addBitmapMemoryCache(url,bitmap);

return bitmap;

}

return null;

}

/**

  • 从网路加载图片 到磁盘缓存(然后再从磁盘中采样加载)

  • @param urlString urlString

  • @param requestWidth 要求的宽

  • @param requestHeight 要求的高

  • @return Bitmap

*/

private Bitmap loadFromHttp(String urlString, int requestWidth, int requestHeight) throws IOException {

//线程检查,不能是主线程

if (Looper.myLooper()==Looper.getMainLooper()) {

throw new RuntimeException(“Do not loadFromHttp from Main Thread!”);

}

if (mDiskLruCache == null) {

return null;

}

DiskLruCache.Editor editor = null;

editor = mDiskLruCache.edit(UrlKeyTransformer.transform(urlString));

if (editor != null) {

OutputStream outputStream = editor.newOutputStream(0);

if (downloadBitmapToStreamFromHttp(urlString, outputStream)) {

editor.commit();

}else {

editor.abort();

}

mDiskLruCache.flush();

}

return loadFromDiskCache(urlString, requestWidth, requestHeight);

}

/**

  • 从网络下载图片到文件输入流

  • @param urlString

  • @param outputStream

  • @return

*/

private boolean downloadBitmapToStreamFromHttp(String urlString, OutputStream outputStream) {

URL url = null;

HttpURLConnection urlConnection = null;

BufferedInputStream in = null;

BufferedOutputStream out = null;

try {

url = new URL(urlString);

urlConnection = (HttpURLConnection) url.openConnection();

in = new BufferedInputStream(urlConnection.getInputStream());

out = new BufferedOutputStream(outputStream);

int b;

while ((b=in.read()) != -1) {

//写入文件输入流

out.write(b);

}

return true;

} catch (IOException e) {

e.printStackTrace();

Log.e(TAG, "downloadBitmapToStreamFromHttp,failed : "+e.getMessage());

}finally {

if (urlConnection != null) {

urlConnection.disconnect();

}

IoUtil.close(in);

IoUtil.close(out);

}

return false;

}

/**

  • 从网络直接下载bitmap(无缓存、无采样)

  • @param urlString

  • @return

*/

private Bitmap downloadBitmapFromUrlDirectly(String urlString) {

URL url;

HttpURLConnection urlConnection = null;

BufferedInputStream bufferedInputStream = null;

try {

url = new URL(urlString);

urlConnection = (HttpURLConnection) url.openConnection();

bufferedInputStream = new BufferedInputStream(urlConnection.getInputStream());

return BitmapFactory.decodeStream(bufferedInputStream);

} catch (IOException e) {

e.printStackTrace();

Log.e(TAG, "downloadBitmapFromUrlDirectly,failed : "+e.getMessage());

}finally {

if (urlConnection != null) {

urlConnection.disconnect();

}

IoUtil.close(bufferedInputStream);

}

return null;

}

public Bitmap loadBitmap(String url){

return loadBitmap(url,0,0);

}

/**

  • 同步 加载bitmap

  • 不能在主线程执行。加载时 先从内存缓存获取,有就返回bitmap,若没有就从磁盘缓存获取;

  • 磁盘缓存有就返回bitmap并缓存到内存缓存,没有就请求网络;

  • 网络请求回来,就缓存到磁盘缓存,然后从磁盘缓存获取返回。

  • @return Bitmap

*/

public Bitmap loadBitmap(String url, int requestWidth, int requestHeight){

Bitmap bitmap = loadFromMemoryCache(url);

if (bitmap != null) {

Log.d(TAG, “loadBitmap: loadFromMemoryCache, url:”+url);

return bitmap;

}

try {

bitmap = loadFromDiskCache(url, requestWidth, requestHeight);

} catch (IOException e) {

e.printStackTrace();

}

if (bitmap != null) {

Log.d(TAG, “loadBitmap: loadFromDiskCache, url:”+url);

return bitmap;

}

try {

bitmap = loadFromHttp(url, requestWidth, requestHeight);

} catch (IOException e) {

e.printStackTrace();

}

if (bitmap != null){

Log.d(TAG, “loadBitmap: loadFromHttp, url:”+url);

return bitmap;

}

if (mDiskLruCache == null) {

Log.d(TAG, “loadBitmap: diskLruCache is null,load bitmap from url directly!”);

bitmap = downloadBitmapFromUrlDirectly(url);

}

return bitmap;

}

public void loadBitmapAsync(final String url, final ImageView imageView){

loadBitmapAsync(url,imageView,0,0);

}

/**

  • 异步 加载bitmap

  • 外部可在任意线程执行,因为内部实现是在子线程(线程池)加载,

  • 并且内部会通过Handler切到主线程,只需要传入view,内部就可直接绘制Bitmap到view。

  • @param url

  • @param imageView

  • @param requestWidth

  • @param requestHeight

*/

public void loadBitmapAsync(final String url, final ImageView imageView, final int requestWidth, final int requestHeight){

if (url == null || url.isEmpty() || imageView == null) {

return;

}

// 标记当前imageView要绘制图片的url

imageView.setTag(VIEW_TAG_URL, url);

mExecutor.execute(new Runnable() {

@Override

public void run() {

Bitmap loadBitmap = loadBitmap(url, requestWidth, requestHeight);

Message message = Message.obtain();

message.obj = new LoadResult(loadBitmap, url, imageView);

mMainHandler.sendMessage(message);

}

});

}

}

/**

  • Bitmap采样压缩加载工具

  • @author hufeiyang

*/

public class BitmapSampleDecodeUtil {

private static final String TAG = “BitmapSampleDecodeUtil”;

最后

小编这些年深知大多数初中级Android工程师,想要提升自己,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助

因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人

都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

资料⬅专栏获取
Directly(url);

}

return bitmap;

}

public void loadBitmapAsync(final String url, final ImageView imageView){

loadBitmapAsync(url,imageView,0,0);

}

/**

  • 异步 加载bitmap

  • 外部可在任意线程执行,因为内部实现是在子线程(线程池)加载,

  • 并且内部会通过Handler切到主线程,只需要传入view,内部就可直接绘制Bitmap到view。

  • @param url

  • @param imageView

  • @param requestWidth

  • @param requestHeight

*/

public void loadBitmapAsync(final String url, final ImageView imageView, final int requestWidth, final int requestHeight){

if (url == null || url.isEmpty() || imageView == null) {

return;

}

// 标记当前imageView要绘制图片的url

imageView.setTag(VIEW_TAG_URL, url);

mExecutor.execute(new Runnable() {

@Override

public void run() {

Bitmap loadBitmap = loadBitmap(url, requestWidth, requestHeight);

Message message = Message.obtain();

message.obj = new LoadResult(loadBitmap, url, imageView);

mMainHandler.sendMessage(message);

}

});

}

}

/**

  • Bitmap采样压缩加载工具

  • @author hufeiyang

*/

public class BitmapSampleDecodeUtil {

private static final String TAG = “BitmapSampleDecodeUtil”;

最后

小编这些年深知大多数初中级Android工程师,想要提升自己,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助

因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。

[外链图片转存中…(img-3Wmi1wP0-1719021347254)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人

都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

资料⬅专栏获取

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/31981.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C# 中的隐式和显式类型转换

当你需要转换值的类型时&#xff0c;可以使用类型转换。只有当你要转换的两种类型兼容时&#xff0c;才有可能。 当你收到错误“无法隐式将一种类型转换为另一种类型”时&#xff0c;说明你转换的两种类型不兼容。 int integer; // 声明一个名为 integer 的整型变量 integer …

项目启动 | 盘古信息助力鼎阳科技开启智能制造升级新征程

在全球数字化浪潮不断涌动的背景下&#xff0c;电子信息行业正迎来转型升级的关键阶段。近日&#xff0c;盘古信息与深圳市鼎阳科技股份有限公司&#xff08;简称“鼎阳科技”&#xff0c;股票代码&#xff1a;688112&#xff09;正式启动了IMS数字化智能制造工厂项目&#xff…

OpenFeign服务调用与负载均衡

目录 介绍使用高级特性超时控制重试机制默认HttpClient修改请求/响应报文压缩日志打印功能 相关文献 介绍 官网说明&#xff1a; Feign 是一个声明式 Web 服务客户端。它使编写 Web 服务客户端变得更加容易。要使用 Feign&#xff0c;请创建一个接口并对其进行注释。它具有可…

CSDN低质量分文章自动化获取

1. 背景 最近粉丝终于达到了5K&#xff0c;可是仍然无法通过优质作者申请&#xff0c;原来是平均质量分较低&#xff0c;优化了一些文章后分数提高仍然较慢&#xff0c;所以需要批量获取低质量文章&#xff0c;重点优化 2. 目标效果 3. 核心代码 其中的Cookie可以根据浏览器…

使用java +paho mqtt编写模拟发布温度及订阅的过程

启动mqtt 服务 创建项目&#xff0c;在项目中添加模块 添加文件夹 添加maven依赖 <dependencies><dependency><groupId>org.eclipse.paho</groupId><artifactId>org.eclipse.paho.client.mqttv3</artifactId><version>1.2.0<…

如何下载和安装SQLynx数据库管理工具? (MySQL作为测试数据库)

目录 1. 官网下载 2. 安装软件 3. 启动SQLynx软件 4. 开始使用 5. 执行第一条SQL语句 6. 总结 SQLynx是一款先进的Web SQL集成开发环境&#xff08;IDE&#xff09;&#xff0c;专为数据库管理、查询和数据分析设计。作为一个基于浏览器的工具&#xff08;同时也支持桌面…

Python xlwt库:写excel表格

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…

基于java+springboot+vue实现的电商应用系统(文末源码+Lw)241

摘 要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本电商应用系统就是在这样的大环境下诞生&#xff0c;其可以帮助管理者在短时间内处理完毕庞大的数据信息&a…

当flex-direction: column时,设置flex:1不生效解决办法

当需求是: 页面纵向排列,且最后一个元素撑满剩余高度 flex:1在横向排列时是可以的,但是纵向排列会失效,此时需要给最后一个子元素设置align-self: stretch;即可撑满剩余高度 <div class"father"><div class"child child1"></div><div…

Python抓取高考网图片

Python抓取高考网图片 一、项目介绍二、完整代码一、项目介绍 本次采集的目标是高考网(http://www.gaokao.com/gkpic/)的图片,实现图片自动下载。高考网主页如下图: 爬取的流程包括寻找数据接口,发送请求,解析图片链接,向图片链接发送请求获取数据,最后保存数据。 二…

C++设计模式——Composite组合模式

一&#xff0c;组合模式简介 真实世界中&#xff0c;像企业组织、文档、图形软件界面等案例&#xff0c;它们在结构上都是分层次的。将系统分层次的方式使得统一管理和添加不同子模块变得容易&#xff0c;在软件开发中&#xff0c;组合模式的设计思想和它们类似。 组合模式是…

DDP(Differential Dynamic Programming)算法举例

DDP(Differential Dynamic Programming)算法 基本原理 DDP(Differential Dynamic Programming)是一种用于求解非线性最优控制问题的递归算法。它基于动态规划的思想,通过线性化系统的动力学方程和二次近似代价函数,递归地优化控制策略。DDP的核心在于利用局部二次近似来…

(vue3)引入组件标红,...has no default export 组件没有默认导出

(vue3)引入组件标红&#xff0c;…has no default export 组件没有默认导出 一、项目背景&#xff1a; 创建的vitevue3ts项目页面有标红,但程序不报错 二、原因 由于之前安装了 Vetur 插件&#xff0c;Vetur 默认使用 eslint-plugin-vue&#xff0c;并且强制 export default …

数据结构——优先级队列(堆)Priority Queue详解

1. 优先级队列 队列是一种先进先出(FIFO)的数据结构&#xff0c;但有些情况下&#xff0c;操作的数据可能带有优先级&#xff0c;一般出队列时&#xff0c;可能需要优先级高的元素先出队列&#xff0c;该场景下&#xff0c;使用队列不合适 在这种情况下&#xff0c;数据结构应…

odoo 翻译字段sql查询语句

字段写法&#xff1a; name->>en_US 任务&#xff1a; 查询name字段中&#xff0c;包含ring的数据 SQL模糊查询 SELECT * FROM product_public_category WHERE name->>en_US LIKE %ring%; SQL精准查询 SELECT * FROM product_public_category WHERE name->…

深入解析MVC架构(Model-View-Controller Architecture)

目录 前言1. MVC架构概述1.1 模型&#xff08;Model&#xff09;1.1.1 数据管理1.1.2 业务逻辑 1.2 视图&#xff08;View&#xff09;1.2.1 数据展示1.2.2 用户界面设计 1.3 控制器&#xff08;Controller&#xff09;1.3.1 用户输入处理1.3.2 更新模型和视图 2. MVC架构的优缺…

易管理工厂设备日志采集工具

免费试用下载: Gitee下载 最新版本 优势: A. 开箱即用. 解压直接运行.不需额外安装. B. 批管理设备. 设备配置均在后台管理. C. 无人值守 客户端自启动,自更新. D. 稳定安全. 架构简单,内存占用小,通过授权访问.

反激开关电源输出假负载

1、为何需要假负载&#xff1f; 开关电源芯片的占空比最小不可能做到0%&#xff0c;都有一个最小导通时间&#xff0c;不过最小导通时间&#xff0c;在规格书中&#xff0c;不一定给出来 注意&#xff1a;如果没有最小导通时间&#xff0c;就相当于芯片都停止输出了&#xff…

29-Linux--守护进程

一.基础概念 1.守护进程&#xff1a;精灵进程&#xff0c;在后台为用户提高服务&#xff0c;是一个生存周期长&#xff0c;通常独立于控制终端并且周期性的执行任务火处理事件发生 2.ps axj&#xff1a;查看守护进程 3.进程组&#xff1a;多个进程的集合&#xff0c;由于管理…

Flask之模板

前言&#xff1a;本博客仅作记录学习使用&#xff0c;部分图片出自网络&#xff0c;如有侵犯您的权益&#xff0c;请联系删除 目录 一、模板的基本用法 1.1、创建模板 1.2、模板语法 1.3、渲染模板 二、模板辅助工具 2.1、上下文 2.2、全局对象 2.3、过滤器 2.4、测试…