文章目录
  1. 1. 前言
  2. 2. 什么是三级缓存
  3. 3. 三级缓存原理
  4. 4.
    1. 4.0.1. 内存缓存(MemoryCache)
    2. 4.0.2. 本地缓存(保存到文件LocalCache)
    3. 4.0.3. md5
    4. 4.0.4. 网络缓存(NetCache)
    5. 4.0.5. 提供调用方法ImageLoder.disPlayImage
    6. 4.0.6. DiskLruCache硬盘缓存
    7. 4.0.7. DiskCache(DiskLruCache )

前言

现在的Android应用程序中,不可避免的都会使用到图片,如果每次加载图片的时候都要从网络重新拉取,这样不但很耗费用户的流量,而且图片加载的也会很慢,用户体验很不好。所以一个应用的图片缓存策略是很重要的。通常情况下,Android应用程序中图片的缓存策略采用“内存-本地-网络”三级缓存策略。

​ 当然现在处理网络图片的时候,一般人都会选择UIL或Glide,它已经将网络缓存处理的相当好了,并且glide还能加载gif,面试的时候通常也会问到图片缓存是怎么做的,这里就来简单实现这个功能。

什么是三级缓存

  • 网络缓存, 不优先加载, 速度慢,浪费流量
  • 本地缓存, 次优先加载, 速度快
  • 内存缓存, 优先加载, 速度最快

三级缓存原理

  • 首次加载 Android App 时,肯定要通过网络交互来获取图片,之后我们可以将图片保存至本地SD卡和内存中

  • 之后运行 App 时,优先访问内存中的图片缓存,若内存中没有,则加载本地SD卡中的图片

  • 总之,只在初次访问新内容时,才通过网络获取图片资源

内存缓存(MemoryCache)

内存缓存说白了就是在内存中保存一份图片集合,首先会想到HashMap这种键值对的形式来进行保存,以url作为key,bitmap作为value。

  • 通过 HashMap键值对的方式保存图片,key为地址,value为图片对象,但因是强引用对象,很容易造成内存溢出,可以尝试SoftReference软引用对象
  • 通过 HashMap>SoftReference 为软引用对象(GC垃圾回收会自动回收软引用对象),但在Android2.3+后,系统会优先考虑回收弱引用对象,官方提出使用LruCache
  • 通过 LruCache least recentlly use 最少最近使用算法

    会将内存控制在一定的大小内, 超出最大值时会自动回收, 这个最大值开发者自己定

    所以呢这里使用LruCache来进行:

    这个类非常适合用来缓存图片,它的主要算法原理是把最近使用的对象用强引用存储在 LinkedHashMap 中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除。

    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
    59
    package com.losileeya.imageloader.ImageLoder;
    import android.graphics.Bitmap;
    import android.util.LruCache;
    /**
    * User: Losileeya (847457332@qq.com)
    * Date: 2016-08-31
    * Time: 11:08
    * 类描述:内存缓存
    *
    * @version :
    */
    public class MemoryCache {
    // private HashMap<String,Bitmap> mMemoryCache=new HashMap<>();//1.因为强引用,容易造成内存溢出,所以考虑使用下面弱引用的方法
    // private HashMap<String, SoftReference<Bitmap>> mMemoryCache = new HashMap<>();//2.因为在Android2.3+后,系统会优先考虑回收弱引用对象,官方提出使用LruCache
    private LruCache <String,Bitmap> mMemoryCache=null;
    public MemoryCache(){
    long mCurrentMemory = Runtime.getRuntime().maxMemory();//得到手机最大允许内存
    long maxSize=mCurrentMemory/8;//得到手机最大允许内存的1/8,即超过指定内存,则开始回收
    mMemoryCache=new LruCache<String,Bitmap>((int)maxSize){
    @Override
    protected int sizeOf(String key, Bitmap value) {
    return value.getRowBytes()*value.getHeight();
    }
    };
    }
    /**
    * 从内存中读图片
    *
    * @param key
    */
    public Bitmap getBitmapFormMemory(String key){
    //Bitmap bitmap = mMemoryCache.get(url);//1.强引用方法
    /*2.弱引用方法
    SoftReference<Bitmap> bitmapSoftReference = mMemoryCache.get(url);
    if (bitmapSoftReference != null) {
    Bitmap bitmap = bitmapSoftReference.get();
    return bitmap;
    }
    */
    Bitmap bitmap=mMemoryCache.get(key);
    return bitmap;
    }
    /**
    * 往内存中写图片
    *
    * @param key
    * @param bitmap
    */
    public void putBitmapToMemory(String key,Bitmap bitmap){
    //mMemoryCache.put(url, bitmap);//1.强引用方法
    /*2.弱引用方法
    mMemoryCache.put(url, new SoftReference<>(bitmap));
    */
    mMemoryCache.put(key,bitmap);
    }
    }

    本地缓存(保存到文件LocalCache)

    从网络加载完图片之后,将图片保存到本地SD卡中。在加载图片的时候,判断一下SD卡中是否有图片缓存,如果有,就直接从SD卡加载图片。本地缓存的工具类中有两个公共的方法,分别是向本地SD卡设置网络图片,获取SD卡中的图片。设置图片的时候采用键值对的形式进行存储,将图片的url作为键,作为文件的名字,图片的Bitmap作位值来保存。由于url含有特殊字符,不能直接作为图片的名字来存储,故采用url的MD5值作为文件的名字。

  • 在初次通过网络获取图片后,我们可以在本地SD卡中将图片保存起来

  • 可以使用MD5加密图片的网络地址,来作为图片的名称保存

    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
    59
    60
    61
    62
    63
    64
    package com.losileeya.imageloader.ImageLoder;
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.os.Environment;
    import com.losileeya.imageloader.utils.Md5Encoder;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    /**
    * User: Losileeya (847457332@qq.com)
    * Date: 2016-08-31
    * Time: 10:49
    * 类描述:把bitmap保存到sd卡
    *
    * @version :
    */
    public class LocalCache {
    /**
    * 文件保存的路径
    */
    private static final String CACHE_PATH= Environment.getExternalStorageDirectory().getAbsolutePath()+"/LosileeyaLocalCache";
    /**
    * 向本地SD卡写网络图片
    *
    * @param url
    * @param bitmap
    */
    public void setBitmapToLocal(String url, Bitmap bitmap){
    try {
    String fileName= Md5Encoder.encode(url);// 文件的名字
    File file=new File(CACHE_PATH,fileName);// 创建文件流,指向该路径,文件名叫做fileName
    //通过得到文件的父文件,判断父文件是否存在
    File parentFile = file.getParentFile();
    if (!parentFile.exists()){// 文件夹不存在
    parentFile.mkdirs();// 创建文件夹
    }
    //把图片保存至本地
    bitmap.compress(Bitmap.CompressFormat.JPEG,100,new FileOutputStream(file));
    } catch (Exception e) {
    e.printStackTrace();
    }
    }
    /**
    * 从本地SD卡获取网络图片,key是url的MD5值
    *
    * @param url
    * @return
    */
    public Bitmap getBitampFormLocal(String url){
    try {
    String fileName = Md5Encoder.encode(url);//把图片的url当做文件名,并进行MD5加密
    File file=new File(CACHE_PATH,fileName);
    Bitmap bitmap= BitmapFactory.decodeStream(new FileInputStream(file));
    return bitmap;
    }catch (Exception e){
    e.printStackTrace();
    }
    return null;
    }
    }

    md5

    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
    package com.losileeya.imageloader.utils;
    import java.security.MessageDigest;
    /**
    * User: Losileeya (847457332@qq.com)
    * Date: 2016-08-31
    * Time: 11:02
    * 类描述:
    *
    * @version :
    */
    public class Md5Encoder {
    public static String encode(String str) throws Exception {
    byte[] hash = MessageDigest.getInstance("MD5").digest(str.getBytes("UTF-8"));
    StringBuilder hex = new StringBuilder(hash.length * 2);
    for (byte b : hash) {
    if ((b & 0xFF) < 0x10) {
    hex.append("0");
    }
    hex.append(Integer.toHexString(b & 0xFF));
    }
    return hex.toString();
    }
    }

    网络缓存(NetCache)

    网络拉取图片严格来讲不能称之为缓存,实质上就是下载url对应的图片,我们这里姑且把它看作是缓存的一种。

    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
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    package com.losileeya.imageloader.ImageLoder;
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.os.AsyncTask;
    import android.widget.ImageView;
    import java.io.IOException;
    import java.net.HttpURLConnection;
    import java.net.URL;
    import java.util.concurrent.Executors;
    /**
    * User: Losileeya (847457332@qq.com)
    * Date: 2016-08-31
    * Time: 10:50
    * 类描述:网络下载图片
    *
    * @version :
    */
    public class NetCache {
    private LocalCache mLocalCache;
    private MemoryCache mMemoryCache;
    public NetCache(LocalCache localCache, MemoryCache memoryCache) {
    mLocalCache = localCache;
    mMemoryCache = memoryCache;
    }
    /**
    * 从网络下载图片
    *
    * @param ivPic 显示图片的imageview
    * @param url 下载图片的网络地址
    */
    public void getBitmapFromNet(ImageView ivPic, String url) {
    // new BitmapTask().execute(ivPic, url);//启动AsyncTask
    new BitmapTask().executeOnExecutor(Executors.newFixedThreadPool(5),ivPic,url);//启动5个线程的线程池AsyncTask
    }
    /**
    * AsyncTask就是对handler和线程池的封装
    * 第一个泛型:参数类型
    * 第二个泛型:更新进度的泛型
    * 第三个泛型:onPostExecute的返回结果
    */
    class BitmapTask extends AsyncTask<Object, Void, Bitmap> {
    private ImageView ivPic;
    private String url;
    /**
    * 后台耗时操作,存在于子线程中
    *
    * @param params
    * @return
    */
    @Override
    protected Bitmap doInBackground(Object... params) {
    ivPic = (ImageView) params[0];
    url = (String) params[1];
    return downLoadBitmap(url);
    }
    /**
    * 更新进度,在主线程中
    *
    * @param values
    */
    @Override
    protected void onProgressUpdate(Void[] values) {
    super.onProgressUpdate(values);
    }
    /**
    * 耗时方法结束后执行该方法,主线程中
    *
    * @param result
    */
    @Override
    protected void onPostExecute(Bitmap result) {
    if (result != null) {
    ivPic.setImageBitmap(result);
    System.out.println("从网络缓存图片啦.....");
    //从网络获取图片后,保存至本地缓存
    mLocalCache.setBitmapToLocal(url, result);
    //保存至内存中
    mMemoryCache.putBitmapToMemory(url, result);
    //保存置硬盘disk
    // DiskCache.openCache(context);
    // try {
    // DiskCache.putBitmapToDisk( result,url);
    // DiskCache.closeCache();
    // } catch (Exception e) {
    // e.printStackTrace();
    // }
    }
    }
    }
    /**
    * 网络下载图片
    *
    * @param url
    * @return
    */
    private Bitmap downLoadBitmap(String url) {
    HttpURLConnection conn = null;
    try {
    conn = (HttpURLConnection) new URL(url).openConnection();
    conn.setConnectTimeout(5000);
    conn.setReadTimeout(5000);
    conn.setRequestMethod("GET");
    int responseCode = conn.getResponseCode();
    if (responseCode == 200) {
    //图片压缩
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inSampleSize = 2;//宽高压缩为原来的1/2
    options.inPreferredConfig = Bitmap.Config.ARGB_4444;
    Bitmap bitmap = BitmapFactory.decodeStream(conn.getInputStream(), null, options);
    return bitmap;
    }
    } catch (IOException e) {
    e.printStackTrace();
    }finally {
    if(conn!=null){
    conn.disconnect();
    conn=null;
    }
    }
    return null;
    }
    }

    提供调用方法ImageLoder.disPlayImage

    严格的来说需要提供一个门面来加载显示图片的,这里就简单的加载了:

    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
    59
    60
    61
    62
    63
    64
    65
    66
    package com.losileeya.imageloader.ImageLoder;
    import android.content.Context;
    import android.graphics.Bitmap;
    import android.widget.ImageView;
    import com.losileeya.imageloader.R;
    /**
    * User: Losileeya (847457332@qq.com)
    * Date: 2016-08-31
    * Time: 10:49
    * 类描述:实现三级缓存,加载显示图片
    *
    * @version :
    */
    public class ImageLoder {
    private NetCache mNetCache;
    private LocalCache mLocalCache;
    private MemoryCache mMemoryCache;
    private Context context;
    public ImageLoder(Context context) {
    mLocalCache = new LocalCache();
    mMemoryCache = new MemoryCache();
    mNetCache = new NetCache(mLocalCache,mMemoryCache);
    }
    public void disPlayImage(ImageView pic, String url) {
    pic.setImageResource(R.mipmap.ic_launcher);
    Bitmap bitmap;
    //内存缓存
    bitmap=mMemoryCache.getBitmapFormMemory(url);
    if (bitmap!=null){
    pic.setImageBitmap(bitmap);
    System.out.println("从内存获取图片啦.....");
    return;
    }
    //本地缓存
    bitmap = mLocalCache.getBitampFormLocal(url);
    // DiskCache.openCache(context);
    // if(DiskCache.hasCache(url)){
    // try {
    // bitmap= DiskCache.getBitmapFormDisk(url);
    // } catch (Exception e) {
    // e.printStackTrace();
    // }
    // }
    if(bitmap !=null){
    pic.setImageBitmap(bitmap);
    System.out.println("从本地获取图片啦.....");
    //从本地获取图片后,保存至内存中
    mMemoryCache.putBitmapToMemory(url,bitmap);
    //从disk获取图片后,保存到内存
    // try {
    // DiskCache.putBitmapToDisk(bitmap,url);
    // DiskCache.closeCache();
    // } catch (Exception e) {
    // e.printStackTrace();
    // }
    return;
    }
    //网络缓存
    mNetCache.getBitmapFromNet(pic,url);
    }
    }

    从代码里我们可以看出,显示我们就是按顺序判断是否在内存中有bitmap,有就显示,没有就继续从本地找bitmap都没有去网络下载显示。

    上述代码里面注释了DiskCache,因为自己写的一般保存到本地的方法都为存文件到sd卡,这里就顺带介绍下DiskLruCache。

    DiskLruCache硬盘缓存

    从各大图片缓存框架我们知道从网络上获取到之后都会存入到本地缓存中使用的是DiskLruCache,

    由于DiskLruCache并不是由Google官方编写的,所以这个类并没有被包含在Android API当中,我们需要将这个类从网上下载下来,然后手动添加到项目当中。(代码有点多)

    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
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    311
    312
    313
    314
    315
    316
    317
    318
    319
    320
    321
    322
    323
    324
    325
    326
    327
    328
    329
    330
    331
    332
    333
    334
    335
    336
    337
    338
    339
    340
    341
    342
    343
    344
    345
    346
    347
    348
    349
    350
    351
    352
    353
    354
    355
    356
    357
    358
    359
    360
    361
    362
    363
    364
    365
    366
    367
    368
    369
    370
    371
    372
    373
    374
    375
    376
    377
    378
    379
    380
    381
    382
    383
    384
    385
    386
    387
    388
    389
    390
    391
    392
    393
    394
    395
    396
    397
    398
    399
    400
    401
    402
    403
    404
    405
    406
    407
    408
    409
    410
    411
    412
    413
    414
    415
    416
    417
    418
    419
    420
    421
    422
    423
    424
    425
    426
    427
    428
    429
    430
    431
    432
    433
    434
    435
    436
    437
    438
    439
    440
    441
    442
    443
    444
    445
    446
    447
    448
    449
    450
    451
    452
    453
    454
    455
    456
    457
    458
    459
    460
    461
    462
    463
    464
    465
    466
    467
    468
    469
    470
    471
    472
    473
    474
    475
    476
    477
    478
    479
    480
    481
    482
    483
    484
    485
    486
    487
    488
    489
    490
    491
    492
    493
    494
    495
    496
    497
    498
    499
    500
    501
    502
    503
    504
    505
    506
    507
    508
    509
    510
    511
    512
    513
    514
    515
    516
    517
    518
    519
    520
    521
    522
    523
    524
    525
    526
    527
    528
    529
    530
    531
    532
    533
    534
    535
    536
    537
    538
    539
    540
    541
    542
    543
    544
    545
    546
    547
    548
    549
    550
    551
    552
    553
    554
    555
    556
    557
    558
    559
    560
    561
    562
    563
    564
    565
    566
    567
    568
    569
    570
    571
    572
    573
    574
    575
    576
    577
    578
    579
    580
    581
    582
    583
    584
    585
    586
    587
    588
    589
    590
    591
    592
    593
    594
    595
    596
    597
    598
    599
    600
    601
    602
    603
    604
    605
    606
    607
    608
    609
    610
    611
    612
    613
    614
    615
    616
    617
    618
    619
    620
    621
    622
    623
    624
    625
    626
    627
    628
    629
    630
    631
    632
    633
    634
    635
    636
    637
    638
    639
    640
    641
    642
    643
    644
    645
    646
    647
    648
    649
    650
    651
    652
    653
    654
    655
    656
    657
    658
    659
    660
    661
    662
    663
    664
    665
    666
    667
    668
    669
    670
    671
    672
    673
    674
    675
    676
    677
    678
    679
    680
    681
    682
    683
    684
    685
    686
    687
    688
    689
    690
    691
    692
    693
    694
    695
    696
    697
    698
    699
    700
    701
    702
    703
    704
    705
    706
    707
    708
    709
    710
    711
    712
    713
    714
    715
    716
    717
    718
    719
    720
    721
    722
    723
    724
    725
    726
    727
    728
    729
    730
    731
    732
    733
    734
    735
    736
    737
    738
    739
    740
    741
    742
    743
    744
    745
    746
    747
    748
    749
    750
    751
    752
    753
    754
    755
    756
    757
    758
    759
    760
    761
    762
    763
    764
    765
    766
    767
    768
    769
    770
    771
    772
    773
    774
    775
    776
    777
    778
    779
    780
    781
    782
    783
    784
    785
    786
    787
    788
    789
    790
    791
    792
    793
    794
    795
    796
    797
    798
    799
    800
    801
    802
    803
    804
    805
    806
    807
    808
    809
    810
    811
    812
    813
    814
    815
    816
    817
    818
    819
    820
    821
    822
    823
    824
    825
    826
    827
    828
    829
    830
    831
    832
    833
    834
    835
    836
    837
    838
    839
    840
    841
    842
    843
    844
    845
    846
    847
    848
    849
    850
    851
    852
    853
    854
    855
    856
    857
    858
    859
    860
    861
    862
    863
    864
    865
    866
    867
    868
    869
    870
    871
    872
    873
    874
    875
    876
    877
    878
    879
    880
    881
    882
    883
    884
    885
    886
    887
    888
    889
    890
    891
    892
    893
    894
    895
    896
    897
    898
    899
    900
    901
    902
    903
    904
    905
    906
    907
    908
    909
    910
    911
    912
    913
    914
    915
    916
    917
    918
    919
    920
    921
    922
    923
    924
    925
    926
    927
    928
    929
    930
    931
    932
    933
    934
    935
    936
    937
    938
    939
    940
    941
    942
    943
    944
    945
    946
    947
    948
    949
    950
    951
    952
    953
    954
    955
    956
    957
    958
    959
    960
    961
    962
    963
    964
    965
    966
    967
    968
    969
    970
    971
    972
    973
    974
    975
    976
    977
    package com.losileeya.imageloader.utils;
    /**
    * User: Losileeya (847457332@qq.com)
    * Date: 2016-08-31
    * Time: 10:51
    * 类描述:
    *
    * @version :
    */
    /*
    * Copyright (C) 2011 The Android Open Source Project
    *
    * Licensed under the Apache License, Version 2.0 (the "License");
    * you may not use this file except in compliance with the License.
    * You may obtain a copy of the License at
    *
    * http://www.apache.org/licenses/LICENSE-2.0
    *
    * Unless required by applicable law or agreed to in writing, software
    * distributed under the License is distributed on an "AS IS" BASIS,
    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    * See the License for the specific language governing permissions and
    * limitations under the License.
    */
    import java.io.BufferedInputStream;
    import java.io.BufferedWriter;
    import java.io.Closeable;
    import java.io.EOFException;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.io.FileOutputStream;
    import java.io.FileWriter;
    import java.io.FilterOutputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.io.OutputStream;
    import java.io.OutputStreamWriter;
    import java.io.Reader;
    import java.io.StringWriter;
    import java.io.Writer;
    import java.lang.reflect.Array;
    import java.nio.charset.Charset;
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.Iterator;
    import java.util.LinkedHashMap;
    import java.util.Map;
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.LinkedBlockingQueue;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    /**
    * *****************************************************************************
    * Taken from the JB source code, can be found in:
    * libcore/luni/src/main/java/libcore/io/DiskLruCache.java
    * or direct link:
    * https://android.googlesource.com/platform/libcore/+/android-4.1.1_r1/luni/src/main/java/libcore/io/DiskLruCache.java
    * *****************************************************************************
    * <p/>
    * A cache that uses a bounded amount of space on a filesystem. Each cache
    * entry has a string key and a fixed number of values. Values are byte
    * sequences, accessible as streams or files. Each value must be between {@code
    * 0} and {@code Integer.MAX_VALUE} bytes in length.
    * <p/>
    * <p>The cache stores its data in a directory on the filesystem. This
    * directory must be exclusive to the cache; the cache may delete or overwrite
    * files from its directory. It is an error for multiple processes to use the
    * same cache directory at the same time.
    * <p/>
    * <p>This cache limits the number of bytes that it will store on the
    * filesystem. When the number of stored bytes exceeds the limit, the cache will
    * remove entries in the background until the limit is satisfied. The limit is
    * not strict: the cache may temporarily exceed it while waiting for files to be
    * deleted. The limit does not include filesystem overhead or the cache
    * journal so space-sensitive applications should set a conservative limit.
    * <p/>
    * <p>Clients call {@link #edit} to create or update the values of an entry. An
    * entry may have only one editor at one time; if a value is not available to be
    * edited then {@link #edit} will return null.
    * <ul>
    * <li>When an entry is being <strong>created</strong> it is necessary to
    * supply a full set of values; the empty value should be used as a
    * placeholder if necessary.
    * <li>When an entry is being <strong>edited</strong>, it is not necessary
    * to supply data for every value; values default to their previous
    * value.
    * </ul>
    * Every {@link #edit} call must be matched by a call to {@link Editor#commit}
    * or {@link Editor#abort}. Committing is atomic: a read observes the full set
    * of values as they were before or after the commit, but never a mix of values.
    * <p/>
    * <p>Clients call {@link #get} to read a snapshot of an entry. The read will
    * observe the value at the time that {@link #get} was called. Updates and
    * removals after the call do not impact ongoing reads.
    * <p/>
    * <p>This class is tolerant of some I/O errors. If files are missing from the
    * filesystem, the corresponding entries will be dropped from the cache. If
    * an error occurs while writing a cache value, the edit will fail silently.
    * Callers should handle other problems by catching {@code IOException} and
    * responding appropriately.
    */
    public final class DiskLruCache implements Closeable {
    static final String JOURNAL_FILE = "journal";
    static final String JOURNAL_FILE_TMP = "journal.tmp";
    static final String MAGIC = "com.losileeya.DiskLruCache";
    static final String VERSION_1 = "1";
    static final long ANY_SEQUENCE_NUMBER = -1;
    private static final String CLEAN = "CLEAN";
    private static final String DIRTY = "DIRTY";
    private static final String REMOVE = "REMOVE";
    private static final String READ = "READ";
    private static final Charset UTF_8 = Charset.forName("UTF-8");
    private static final int IO_BUFFER_SIZE = 8 * 1024;
    /*
    * This cache uses a journal file named "journal". A typical journal file
    * looks like this:
    * libcore.io.DiskLruCache
    * 1
    * 100
    * 2
    *
    * CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054
    * DIRTY 335c4c6028171cfddfbaae1a9c313c52
    * CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342
    * REMOVE 335c4c6028171cfddfbaae1a9c313c52
    * DIRTY 1ab96a171faeeee38496d8b330771a7a
    * CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234
    * READ 335c4c6028171cfddfbaae1a9c313c52
    * READ 3400330d1dfc7f3f7f4b8d4d803dfcf6
    *
    * The first five lines of the journal form its header. They are the
    * constant string "libcore.io.DiskLruCache", the disk cache's version,
    * the application's version, the value count, and a blank line.
    *
    * Each of the subsequent lines in the file is a record of the state of a
    * cache entry. Each line contains space-separated values: a state, a key,
    * and optional state-specific values.
    * o DIRTY lines track that an entry is actively being created or updated.
    * Every successful DIRTY action should be followed by a CLEAN or REMOVE
    * action. DIRTY lines without a matching CLEAN or REMOVE indicate that
    * temporary files may need to be deleted.
    * o CLEAN lines track a cache entry that has been successfully published
    * and may be read. A publish line is followed by the lengths of each of
    * its values.
    * o READ lines track accesses for LRU.
    * o REMOVE lines track entries that have been deleted.
    *
    * The journal file is appended to as cache operations occur. The journal may
    * occasionally be compacted by dropping redundant lines. A temporary file named
    * "journal.tmp" will be used during compaction; that file should be deleted if
    * it exists when the cache is opened.
    */
    private final File directory;
    private final File journalFile;
    private final File journalFileTmp;
    private final int appVersion;
    private final long maxSize;
    private final int valueCount;
    private long size = 0;
    private Writer journalWriter;
    private final LinkedHashMap<String, Entry> lruEntries
    = new LinkedHashMap<String, Entry>(0, 0.75f, true);
    private int redundantOpCount;
    /**
    * To differentiate between old and current snapshots, each entry is given
    * a sequence number each time an edit is committed. A snapshot is stale if
    * its sequence number is not equal to its entry's sequence number.
    */
    private long nextSequenceNumber = 0;
    /* From java.util.Arrays */
    @SuppressWarnings("unchecked")
    private static <T> T[] copyOfRange(T[] original, int start, int end) {
    final int originalLength = original.length; // For exception priority compatibility.
    if (start > end) {
    throw new IllegalArgumentException();
    }
    if (start < 0 || start > originalLength) {
    throw new ArrayIndexOutOfBoundsException();
    }
    final int resultLength = end - start;
    final int copyLength = Math.min(resultLength, originalLength - start);
    final T[] result = (T[]) Array
    .newInstance(original.getClass().getComponentType(), resultLength);
    System.arraycopy(original, start, result, 0, copyLength);
    return result;
    }
    /**
    * Returns the remainder of 'reader' as a string, closing it when done.
    */
    public static String readFully(Reader reader) throws IOException {
    try {
    StringWriter writer = new StringWriter();
    char[] buffer = new char[1024];
    int count;
    while ((count = reader.read(buffer)) != -1) {
    writer.write(buffer, 0, count);
    }
    return writer.toString();
    } finally {
    reader.close();
    }
    }
    /**
    * Returns the ASCII characters up to but not including the next "\r\n", or
    * "\n".
    *
    * @throws java.io.EOFException if the stream is exhausted before the next newline
    * character.
    */
    public static String readAsciiLine(InputStream in) throws IOException {
    // TODO: support UTF-8 here instead
    StringBuilder result = new StringBuilder(80);
    while (true) {
    int c = in.read();
    if (c == -1) {
    throw new EOFException();
    } else if (c == '\n') {
    break;
    }
    result.append((char) c);
    }
    int length = result.length();
    if (length > 0 && result.charAt(length - 1) == '\r') {
    result.setLength(length - 1);
    }
    return result.toString();
    }
    /**
    * Closes 'closeable', ignoring any checked exceptions. Does nothing if 'closeable' is null.
    */
    public static void closeQuietly(Closeable closeable) {
    if (closeable != null) {
    try {
    closeable.close();
    } catch (RuntimeException rethrown) {
    throw rethrown;
    } catch (Exception ignored) {
    }
    }
    }
    /**
    * Recursively delete everything in {@code dir}.
    */
    // TODO: this should specify paths as Strings rather than as Files
    public static void deleteContents(File dir) throws IOException {
    File[] files = dir.listFiles();
    if (files == null) {
    throw new IllegalArgumentException("not a directory: " + dir);
    }
    for (File file : files) {
    if (file.isDirectory()) {
    deleteContents(file);
    }
    if (!file.delete()) {
    throw new IOException("failed to delete file: " + file);
    }
    }
    }
    /**
    * This cache uses a single background thread to evict entries.
    */
    private final ExecutorService executorService = new ThreadPoolExecutor(0, 1,
    60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
    private final Callable<Void> cleanupCallable = new Callable<Void>() {
    @Override
    public Void call() throws Exception {
    synchronized (DiskLruCache.this) {
    if (journalWriter == null) {
    return null; // closed
    }
    trimToSize();
    if (journalRebuildRequired()) {
    rebuildJournal();
    redundantOpCount = 0;
    }
    }
    return null;
    }
    };
    private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize) {
    this.directory = directory;
    this.appVersion = appVersion;
    this.journalFile = new File(directory, JOURNAL_FILE);
    this.journalFileTmp = new File(directory, JOURNAL_FILE_TMP);
    this.valueCount = valueCount;
    this.maxSize = maxSize;
    }
    /**
    * Opens the cache in {@code directory}, creating a cache if none exists
    * there.
    *
    * @param directory a writable directory
    * @param appVersion
    * @param valueCount the number of values per cache entry. Must be positive.
    * @param maxSize the maximum number of bytes this cache should use to store
    * @throws java.io.IOException if reading or writing the cache directory fails
    */
    public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
    throws IOException {
    if (maxSize <= 0) {
    throw new IllegalArgumentException("maxSize <= 0");
    }
    if (valueCount <= 0) {
    throw new IllegalArgumentException("valueCount <= 0");
    }
    // prefer to pick up where we left off
    DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
    if (cache.journalFile.exists()) {
    try {
    cache.readJournal();
    cache.processJournal();
    cache.journalWriter = new BufferedWriter(new FileWriter(cache.journalFile, true),
    IO_BUFFER_SIZE);
    return cache;
    } catch (IOException journalIsCorrupt) {
    // System.logW("DiskLruCache " + directory + " is corrupt: "
    // + journalIsCorrupt.getMessage() + ", removing");
    cache.delete();
    }
    }
    // create a new empty cache
    directory.mkdirs();
    cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
    cache.rebuildJournal();
    return cache;
    }
    private void readJournal() throws IOException {
    InputStream in = new BufferedInputStream(new FileInputStream(journalFile), IO_BUFFER_SIZE);
    try {
    String magic = readAsciiLine(in);
    String version = readAsciiLine(in);
    String appVersionString = readAsciiLine(in);
    String valueCountString = readAsciiLine(in);
    String blank = readAsciiLine(in);
    if (!MAGIC.equals(magic)
    || !VERSION_1.equals(version)
    || !Integer.toString(appVersion).equals(appVersionString)
    || !Integer.toString(valueCount).equals(valueCountString)
    || !"".equals(blank)) {
    throw new IOException("unexpected journal header: ["
    + magic + ", " + version + ", " + valueCountString + ", " + blank + "]");
    }
    while (true) {
    try {
    readJournalLine(readAsciiLine(in));
    } catch (EOFException endOfJournal) {
    break;
    }
    }
    } finally {
    closeQuietly(in);
    }
    }
    private void readJournalLine(String line) throws IOException {
    String[] parts = line.split(" ");
    if (parts.length < 2) {
    throw new IOException("unexpected journal line: " + line);
    }
    String key = parts[1];
    if (parts[0].equals(REMOVE) && parts.length == 2) {
    lruEntries.remove(key);
    return;
    }
    Entry entry = lruEntries.get(key);
    if (entry == null) {
    entry = new Entry(key);
    lruEntries.put(key, entry);
    }
    if (parts[0].equals(CLEAN) && parts.length == 2 + valueCount) {
    entry.readable = true;
    entry.currentEditor = null;
    entry.setLengths(copyOfRange(parts, 2, parts.length));
    } else if (parts[0].equals(DIRTY) && parts.length == 2) {
    entry.currentEditor = new Editor(entry);
    } else if (parts[0].equals(READ) && parts.length == 2) {
    // this work was already done by calling lruEntries.get()
    } else {
    throw new IOException("unexpected journal line: " + line);
    }
    }
    /**
    * Computes the initial size and collects garbage as a part of opening the
    * cache. Dirty entries are assumed to be inconsistent and will be deleted.
    */
    private void processJournal() throws IOException {
    deleteIfExists(journalFileTmp);
    for (Iterator<Entry> i = lruEntries.values().iterator(); i.hasNext(); ) {
    Entry entry = i.next();
    if (entry.currentEditor == null) {
    for (int t = 0; t < valueCount; t++) {
    size += entry.lengths[t];
    }
    } else {
    entry.currentEditor = null;
    for (int t = 0; t < valueCount; t++) {
    deleteIfExists(entry.getCleanFile(t));
    deleteIfExists(entry.getDirtyFile(t));
    }
    i.remove();
    }
    }
    }
    /**
    * Creates a new journal that omits redundant information. This replaces the
    * current journal if it exists.
    */
    private synchronized void rebuildJournal() throws IOException {
    if (journalWriter != null) {
    journalWriter.close();
    }
    Writer writer = new BufferedWriter(new FileWriter(journalFileTmp), IO_BUFFER_SIZE);
    writer.write(MAGIC);
    writer.write("\n");
    writer.write(VERSION_1);
    writer.write("\n");
    writer.write(Integer.toString(appVersion));
    writer.write("\n");
    writer.write(Integer.toString(valueCount));
    writer.write("\n");
    writer.write("\n");
    for (Entry entry : lruEntries.values()) {
    if (entry.currentEditor != null) {
    writer.write(DIRTY + ' ' + entry.key + '\n');
    } else {
    writer.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');
    }
    }
    writer.close();
    journalFileTmp.renameTo(journalFile);
    journalWriter = new BufferedWriter(new FileWriter(journalFile, true), IO_BUFFER_SIZE);
    }
    private static void deleteIfExists(File file) throws IOException {
    // try {
    // Libcore.os.remove(file.getPath());
    // } catch (ErrnoException errnoException) {
    // if (errnoException.errno != OsConstants.ENOENT) {
    // throw errnoException.rethrowAsIOException();
    // }
    // }
    if (file.exists() && !file.delete()) {
    throw new IOException();
    }
    }
    /**
    * Returns a snapshot of the entry named {@code key}, or null if it doesn't
    * exist is not currently readable. If a value is returned, it is moved to
    * the head of the LRU queue.
    */
    public synchronized Snapshot get(String key) throws IOException {
    checkNotClosed();
    validateKey(key);
    Entry entry = lruEntries.get(key);
    if (entry == null) {
    return null;
    }
    if (!entry.readable) {
    return null;
    }
    /*
    * Open all streams eagerly to guarantee that we see a single published
    * snapshot. If we opened streams lazily then the streams could come
    * from different edits.
    */
    InputStream[] ins = new InputStream[valueCount];
    try {
    for (int i = 0; i < valueCount; i++) {
    ins[i] = new FileInputStream(entry.getCleanFile(i));
    }
    } catch (FileNotFoundException e) {
    // a file must have been deleted manually!
    return null;
    }
    redundantOpCount++;
    journalWriter.append(READ + ' ' + key + '\n');
    if (journalRebuildRequired()) {
    executorService.submit(cleanupCallable);
    }
    return new Snapshot(key, entry.sequenceNumber, ins);
    }
    /**
    * Returns an editor for the entry named {@code key}, or null if another
    * edit is in progress.
    */
    public Editor edit(String key) throws IOException {
    return edit(key, ANY_SEQUENCE_NUMBER);
    }
    private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException {
    checkNotClosed();
    validateKey(key);
    Entry entry = lruEntries.get(key);
    if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER
    && (entry == null || entry.sequenceNumber != expectedSequenceNumber)) {
    return null; // snapshot is stale
    }
    if (entry == null) {
    entry = new Entry(key);
    lruEntries.put(key, entry);
    } else if (entry.currentEditor != null) {
    return null; // another edit is in progress
    }
    Editor editor = new Editor(entry);
    entry.currentEditor = editor;
    // flush the journal before creating files to prevent file leaks
    journalWriter.write(DIRTY + ' ' + key + '\n');
    journalWriter.flush();
    return editor;
    }
    /**
    * Returns the directory where this cache stores its data.
    */
    public File getDirectory() {
    return directory;
    }
    /**
    * Returns the maximum number of bytes that this cache should use to store
    * its data.
    */
    public long maxSize() {
    return maxSize;
    }
    /**
    * Returns the number of bytes currently being used to store the values in
    * this cache. This may be greater than the max size if a background
    * deletion is pending.
    */
    public synchronized long size() {
    return size;
    }
    private synchronized void completeEdit(Editor editor, boolean success) throws IOException {
    Entry entry = editor.entry;
    if (entry.currentEditor != editor) {
    throw new IllegalStateException();
    }
    // if this edit is creating the entry for the first time, every index must have a value
    if (success && !entry.readable) {
    for (int i = 0; i < valueCount; i++) {
    if (!entry.getDirtyFile(i).exists()) {
    editor.abort();
    throw new IllegalStateException("edit didn't create file " + i);
    }
    }
    }
    for (int i = 0; i < valueCount; i++) {
    File dirty = entry.getDirtyFile(i);
    if (success) {
    if (dirty.exists()) {
    File clean = entry.getCleanFile(i);
    dirty.renameTo(clean);
    long oldLength = entry.lengths[i];
    long newLength = clean.length();
    entry.lengths[i] = newLength;
    size = size - oldLength + newLength;
    }
    } else {
    deleteIfExists(dirty);
    }
    }
    redundantOpCount++;
    entry.currentEditor = null;
    if (entry.readable | success) {
    entry.readable = true;
    journalWriter.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');
    if (success) {
    entry.sequenceNumber = nextSequenceNumber++;
    }
    } else {
    lruEntries.remove(entry.key);
    journalWriter.write(REMOVE + ' ' + entry.key + '\n');
    }
    if (size > maxSize || journalRebuildRequired()) {
    executorService.submit(cleanupCallable);
    }
    }
    /**
    * We only rebuild the journal when it will halve the size of the journal
    * and eliminate at least 2000 ops.
    */
    private boolean journalRebuildRequired() {
    final int REDUNDANT_OP_COMPACT_THRESHOLD = 2000;
    return redundantOpCount >= REDUNDANT_OP_COMPACT_THRESHOLD
    && redundantOpCount >= lruEntries.size();
    }
    /**
    * Drops the entry for {@code key} if it exists and can be removed. Entries
    * actively being edited cannot be removed.
    *
    * @return true if an entry was removed.
    */
    public synchronized boolean remove(String key) throws IOException {
    checkNotClosed();
    validateKey(key);
    Entry entry = lruEntries.get(key);
    if (entry == null || entry.currentEditor != null) {
    return false;
    }
    for (int i = 0; i < valueCount; i++) {
    File file = entry.getCleanFile(i);
    if (!file.delete()) {
    throw new IOException("failed to delete " + file);
    }
    size -= entry.lengths[i];
    entry.lengths[i] = 0;
    }
    redundantOpCount++;
    journalWriter.append(REMOVE + ' ' + key + '\n');
    lruEntries.remove(key);
    if (journalRebuildRequired()) {
    executorService.submit(cleanupCallable);
    }
    return true;
    }
    /**
    * Returns true if this cache has been closed.
    */
    public boolean isClosed() {
    return journalWriter == null;
    }
    private void checkNotClosed() {
    if (journalWriter == null) {
    throw new IllegalStateException("cache is closed");
    }
    }
    /**
    * Force buffered operations to the filesystem.
    */
    public synchronized void flush() throws IOException {
    checkNotClosed();
    trimToSize();
    journalWriter.flush();
    }
    /**
    * Closes this cache. Stored values will remain on the filesystem.
    */
    public synchronized void close() throws IOException {
    if (journalWriter == null) {
    return; // already closed
    }
    for (Entry entry : new ArrayList<Entry>(lruEntries.values())) {
    if (entry.currentEditor != null) {
    entry.currentEditor.abort();
    }
    }
    trimToSize();
    journalWriter.close();
    journalWriter = null;
    }
    private void trimToSize() throws IOException {
    while (size > maxSize) {
    // Map.Entry<String, Entry> toEvict = lruEntries.eldest();
    final Map.Entry<String, Entry> toEvict = lruEntries.entrySet().iterator().next();
    remove(toEvict.getKey());
    }
    }
    /**
    * Closes the cache and deletes all of its stored values. This will delete
    * all files in the cache directory including files that weren't created by
    * the cache.
    */
    public void delete() throws IOException {
    close();
    deleteContents(directory);
    }
    private void validateKey(String key) {
    if (key.contains(" ") || key.contains("\n") || key.contains("\r")) {
    throw new IllegalArgumentException(
    "keys must not contain spaces or newlines: \"" + key + "\"");
    }
    }
    private static String inputStreamToString(InputStream in) throws IOException {
    return readFully(new InputStreamReader(in, UTF_8));
    }
    /**
    * A snapshot of the values for an entry.
    */
    public final class Snapshot implements Closeable {
    private final String key;
    private final long sequenceNumber;
    private final InputStream[] ins;
    private Snapshot(String key, long sequenceNumber, InputStream[] ins) {
    this.key = key;
    this.sequenceNumber = sequenceNumber;
    this.ins = ins;
    }
    /**
    * Returns an editor for this snapshot's entry, or null if either the
    * entry has changed since this snapshot was created or if another edit
    * is in progress.
    */
    public Editor edit() throws IOException {
    return DiskLruCache.this.edit(key, sequenceNumber);
    }
    /**
    * Returns the unbuffered stream with the value for {@code index}.
    */
    public InputStream getInputStream(int index) {
    return ins[index];
    }
    /**
    * Returns the string value for {@code index}.
    */
    public String getString(int index) throws IOException {
    return inputStreamToString(getInputStream(index));
    }
    @Override
    public void close() {
    for (InputStream in : ins) {
    closeQuietly(in);
    }
    }
    }
    /**
    * Edits the values for an entry.
    */
    public final class Editor {
    private final Entry entry;
    private boolean hasErrors;
    private Editor(Entry entry) {
    this.entry = entry;
    }
    /**
    * Returns an unbuffered input stream to read the last committed value,
    * or null if no value has been committed.
    */
    public InputStream newInputStream(int index) throws IOException {
    synchronized (DiskLruCache.this) {
    if (entry.currentEditor != this) {
    throw new IllegalStateException();
    }
    if (!entry.readable) {
    return null;
    }
    return new FileInputStream(entry.getCleanFile(index));
    }
    }
    /**
    * Returns the last committed value as a string, or null if no value
    * has been committed.
    */
    public String getString(int index) throws IOException {
    InputStream in = newInputStream(index);
    return in != null ? inputStreamToString(in) : null;
    }
    /**
    * Returns a new unbuffered output stream to write the value at
    * {@code index}. If the underlying output stream encounters errors
    * when writing to the filesystem, this edit will be aborted when
    * {@link #commit} is called. The returned output stream does not throw
    * IOExceptions.
    */
    public OutputStream newOutputStream(int index) throws IOException {
    synchronized (DiskLruCache.this) {
    if (entry.currentEditor != this) {
    throw new IllegalStateException();
    }
    return new FaultHidingOutputStream(new FileOutputStream(entry.getDirtyFile(index)));
    }
    }
    /**
    * Sets the value at {@code index} to {@code value}.
    */
    public void set(int index, String value) throws IOException {
    Writer writer = null;
    try {
    writer = new OutputStreamWriter(newOutputStream(index), UTF_8);
    writer.write(value);
    } finally {
    closeQuietly(writer);
    }
    }
    /**
    * Commits this edit so it is visible to readers. This releases the
    * edit lock so another edit may be started on the same key.
    */
    public void commit() throws IOException {
    if (hasErrors) {
    completeEdit(this, false);
    remove(entry.key); // the previous entry is stale
    } else {
    completeEdit(this, true);
    }
    }
    /**
    * Aborts this edit. This releases the edit lock so another edit may be
    * started on the same key.
    */
    public void abort() throws IOException {
    completeEdit(this, false);
    }
    private class FaultHidingOutputStream extends FilterOutputStream {
    private FaultHidingOutputStream(OutputStream out) {
    super(out);
    }
    @Override
    public void write(int oneByte) {
    try {
    out.write(oneByte);
    } catch (IOException e) {
    hasErrors = true;
    }
    }
    @Override
    public void write(byte[] buffer, int offset, int length) {
    try {
    out.write(buffer, offset, length);
    } catch (IOException e) {
    hasErrors = true;
    }
    }
    @Override
    public void close() {
    try {
    out.close();
    } catch (IOException e) {
    hasErrors = true;
    }
    }
    @Override
    public void flush() {
    try {
    out.flush();
    } catch (IOException e) {
    hasErrors = true;
    }
    }
    }
    }
    private final class Entry {
    private final String key;
    /**
    * Lengths of this entry's files.
    */
    private final long[] lengths;
    /**
    * True if this entry has ever been published
    */
    private boolean readable;
    /**
    * The ongoing edit or null if this entry is not being edited.
    */
    private Editor currentEditor;
    /**
    * The sequence number of the most recently committed edit to this entry.
    */
    private long sequenceNumber;
    private Entry(String key) {
    this.key = key;
    this.lengths = new long[valueCount];
    }
    public String getLengths() throws IOException {
    StringBuilder result = new StringBuilder();
    for (long size : lengths) {
    result.append(' ').append(size);
    }
    return result.toString();
    }
    /**
    * Set lengths using decimal numbers like "10123".
    */
    private void setLengths(String[] strings) throws IOException {
    if (strings.length != valueCount) {
    throw invalidLengths(strings);
    }
    try {
    for (int i = 0; i < strings.length; i++) {
    lengths[i] = Long.parseLong(strings[i]);
    }
    } catch (NumberFormatException e) {
    throw invalidLengths(strings);
    }
    }
    private IOException invalidLengths(String[] strings) throws IOException {
    throw new IOException("unexpected journal line: " + Arrays.toString(strings));
    }
    public File getCleanFile(int i) {
    return new File(directory, key + "." + i);
    }
    public File getDirtyFile(int i) {
    return new File(directory, key + "." + i + ".tmp");
    }
    }
    }

    首先你要知道,DiskLruCache是不能new出实例的,如果我们要创建一个DiskLruCache的实例,则需要调用它的open()方法,接口如下所示:

    1
    public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)

    DiskCache(DiskLruCache )

    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
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    package com.losileeya.imageloader.ImageLoder;
    import android.content.Context;
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.os.Environment;
    import com.losileeya.imageloader.utils.DiskLruCache;
    import com.losileeya.imageloader.utils.Md5Encoder;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    /**
    * User: Losileeya (847457332@qq.com)
    * Date: 2016-08-31
    * Time: 10:50
    * 类描述:DiskLruCache
    *
    * @version :
    */
    public class DiskCache {
    private static DiskLruCache mCache;
    /**
    * 打开DiskLruCache。
    */
    public static void openCache(Context context) {
    try {
    if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
    || !Environment.isExternalStorageRemovable()) {
    mCache = DiskLruCache.open(context.getExternalCacheDir(), 0, 1, 10 * 1024 * 1024);
    } else {
    mCache = DiskLruCache.open(context.getCacheDir(), 0, 1, 10 * 1024 * 1024);
    }
    } catch (IOException e) {
    e.printStackTrace();
    }
    }
    /**
    * 把图片写入disk内存
    */
    public static void putBitmapToDisk(Bitmap bitmap, String keyCache) throws Exception {
    if (mCache == null) throw new IllegalStateException("Must call openCache() first!");
    DiskLruCache.Editor editor = mCache.edit(Md5Encoder.encode(keyCache));
    if (editor != null) {
    OutputStream outputStream = editor.newOutputStream(0);
    boolean success = bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream);
    if (success) {
    editor.commit();
    } else {
    editor.abort();
    }
    }
    }
    /**
    * 从disk中获取图片
    */
    public static Bitmap getBitmapFormDisk(String keyCache) throws Exception {
    if (mCache == null) throw new IllegalStateException("Must call openCache() first!");
    DiskLruCache.Snapshot snapshot = mCache.get(Md5Encoder.encode(keyCache));
    if (snapshot != null) {
    InputStream inputStream = snapshot.getInputStream(0);
    Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
    return bitmap;
    }
    return null;
    }
    /**
    * 检查缓存是否存在。
    */
    public static boolean hasCache(String keyCache) {
    try {
    return mCache.get(Md5Encoder.encode(keyCache)) != null;
    }catch (Exception e) {
    e.printStackTrace();
    }
    return false;
    }
    /**
    * 同步日志。
    */
    public static void syncLog() {
    try {
    mCache.flush();
    } catch (IOException e) {
    e.printStackTrace();
    }
    }
    /**
    * 关闭DiskLruCache。
    */
    public static void closeCache() {
    syncLog();
    }
    }

    好了缓存代码都手把手的写完了,使用很简单

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    package com.losileeya.imageloader;
    import android.os.Bundle;
    import android.support.v7.app.AppCompatActivity;
    import android.widget.ImageView;
    import android.widget.LinearLayout;
    import android.widget.RelativeLayout;
    import com.losileeya.imageloader.ImageLoder.ImageLoder;
    public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    RelativeLayout rl= (RelativeLayout) findViewById(R.id.bg);
    ImageView iv=new ImageView(this);
    iv.setLayoutParams(new LinearLayout.LayoutParams(getWindow().getAttributes().width,getWindow().getAttributes().height));
    rl.addView(iv);
    new ImageLoder(this).disPlayImage(iv,"http://img.my.csdn.net/uploads/201309/01/1378037235_7476.jpg");
    }
    }

    就一句话ImageLoder(this).disPlayImage(iv,”http://img.my.csdn.net/uploads/201309/01/1378037235_7476.jpg“);

    好了,demo就不上传了,所有的代码附上面了,看下效果吧:

    当然别忘了添加所需权限:

    1
    2
    3
    4
    5
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>


未经许可不得转载,转载请注明zilianliuxue的blog,本人保留所有版权。

文章目录
  1. 1. 前言
  2. 2. 什么是三级缓存
  3. 3. 三级缓存原理
  4. 4.
    1. 4.0.1. 内存缓存(MemoryCache)
    2. 4.0.2. 本地缓存(保存到文件LocalCache)
    3. 4.0.3. md5
    4. 4.0.4. 网络缓存(NetCache)
    5. 4.0.5. 提供调用方法ImageLoder.disPlayImage
    6. 4.0.6. DiskLruCache硬盘缓存
    7. 4.0.7. DiskCache(DiskLruCache )