Android LruCache和DiskLruCache相结合打造图片加载框架(仿微信图片选择,照片墙)

来源:互联网 发布:mobi域名前加中文 编辑:程序博客网 时间:2024/04/29 18:12

LrcCache和DiskLruCache相结合打造图片加载框架


转载请标明出处:http://blog.csdn.net/luoshishou/article/details/51299169

源码下载链接:
http://download.csdn.net/detail/luoshishou/9508282

1概述


这几在研究图片加载的方面的知识,在网上看了一下前辈们写的文章,受到了一些启发,于是综合多方面的知识,将这些整合起来,自己边写了一个图片加载框架。说到图片加载最容易出问题的就是OOM就是内存溢出,所以一定要限制加载图片时使用的内存,这就使用到Android提供的缓存类LruCache,关于LruCache的知识这里不再赘述,大家自行学习。但是如果图片非常的多而且频繁操作的话,加上LruCache的缓存空间有限,缓存就不得不经常更新,效果会大打折扣,于是就想到使用LruCache和DiskLruCache结合起来,做一个二级缓存。DiskLruCache使用的手机的SD卡或者手机存储作为缓存,不占用手机App运行时占用的内存。


                                       



2缓存

主要思路,得到的图片Bitmap存入LruCache中,如果LruCache如果空间不够使用会按照最近最少使用的原则去把最近最少使用的Bitmap删除。将LruCache删除的Bitmap存入DiskLruCache缓存中,实现二级缓存。

2.1 改造LruCache

由于Android提供LruCache 在删除最近最少使用的对象时是直接删除对象,但是我要的是将删除的对象返回,并存入到DiskLruCache中,所有要对LruCache进行改造。由于LruCache的缓存方法public final V put(K key, V value)如下,不能重写,所有只能重新写一个与LruCache一样的类,复制LruCache的所有方法过来,但是单独改写public final V put(K key, V value)以及相关的private void trimToSize(int maxSize)。
 

public final Vput(Kkey,Vvalue) {
    if (key == null|| value ==null) {
        throw new NullPointerException("key == null || value == null");
    }

    V previous;
    synchronized (this) {
        putCount++;
        size += safeSizeOf(key, value);
       previous = map.put(key, value);
        if (previous != null) {
            size -= safeSizeOf(key, previous);
        }
    }

    if (previous != null) {
        entryRemoved(false, key, previous, value);
    }

    trimToSize(maxSize);
    return previous;
}

 

重新建立类BitmapLruCache,重新写public finalVput(Kkey,Vvalue)和private voidtrimToSize(intmaxSize)方法。

 

/** * 将对象加入缓存,如果缓存已经满,则删除最近最少使用的对象,并返回被删除的对象列表 * @param key * @param value * @return */public final LinkedHashMap<K, V> put(K key, V value) {    if (key == null || value == null) {        throw new NullPointerException("key == null || value == null");    }    V previous;    synchronized (this) {        putCount++;        size += safeSizeOf(key, value);        previous = map.put(key, value);        if (previous != null) {            size -= safeSizeOf(key, previous);        }    }    if (previous != null) {        entryRemoved(false, key, previous, value);    }        return trimToSize(maxSize);}

 

 

/** * 删除最近最少使用对象,并返回被删除的对象列表 * @param maxSize * @return */public LinkedHashMap<K, V> trimToSize(int maxSize) {   LinkedHashMap<K, V> trimMap =  new LinkedHashMap<K, V>(0, 0.75f, true); //被删除的对象列表    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 || map.isEmpty()) {                break;            }            Map.Entry<K, V> toEvict = map.entrySet().iterator().next();            key = toEvict.getKey();            value = toEvict.getValue();            trimMap.put(key, value);     //添加被删除的对象            map.remove(key);            size -= safeSizeOf(key, value);            evictionCount++;        }        entryRemoved(true, key, value, null);    }        return trimMap;}

 

 

 

2.2 DiskLruCache

对应DiskLruCache大家可以学习这篇文章http://blog.csdn.net/guolin_blog/article/details/28863651,说的非常好,我就不赘述了。

2.3 LruCache和DiskLruCache相结合

建立BitmapCacheL2类,结合LrcCache和DiskLruCache,写缓存方法。
在构造方法初始化LrcCache和DiskLruCache
 
public BitmapCacheL2(Context context){   mLrcCache = new BitmapLruCache<String, Bitmap>(mCacheSize){      @Override      protected int sizeOf(String key, Bitmap value) {         // TODO Auto-generated method stub         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {            return value.getByteCount();         } else {            return value.getRowBytes() * value.getHeight();         }         // Pre HC-MR1      }   };   //start 初始化手机SD存储缓存   File cacheDir = getDiskCacheDir(context, "thumb");   if (!cacheDir.exists()) {      cacheDir.mkdirs();   }   // 创建DiskLruCache实例,初始化缓存数据   try {      mDiskLruCache = DiskLruCache            .open(cacheDir, getAppVersion(context), 1, MAX_FILE_SIZE);   } catch (IOException e) {      // TODO Auto-generated catch block      e.printStackTrace();   }   //end 初始化手机SD存储缓存}
 

BitmapCacheL2  最主要的的两个方法分别是添加缓存和取出缓存的方法如下

 

/** * 保存bitmap到缓存 * @param url * @param bitmap */public void putBitmap(String url, Bitmap bitmap) {   Log.i(tag, "putBitmap *** ");   //start 将bitmap存入内存缓存,如果已经存满,则删除最近最少使用的bitmap,并返回被删除的bitmap对象列表   LinkedHashMap<String, Bitmap> trimMap;   trimMap = mLrcCache.put(url, bitmap);   putBitmap2DiskLruCache(url,bitmap);   //end 将bitmap存入内存缓存,如果已经存满,则删除最近最少使用的bitmap,并返回被删除的bitmap对象列表   //start 将被LrucCache删除的bitmap存入DiskLruCache   if(null!=trimMap && !trimMap.isEmpty()){      Log.i(tag, " LruCache--->DiskCache");      Iterator<?> it = trimMap.entrySet().iterator();      while(it.hasNext()){         Entry<String, Bitmap> entry = (Entry<String, Bitmap>) it.next();         putBitmap2DiskLruCache(entry.getKey(), entry.getValue());//向DiskLruCache添加缓存
      }   }   ///end 将被LrucCache删除的bitmap存入DiskLruCache}

 

 

 

/** * 从缓存获取bitmap */public Bitmap getBitmap(String url) {   // TODO Auto-generated method stub   Log.i(tag, "getBitmap ***");   //首先从手机内存缓存中获取   Bitmap map = mLrcCache.get(url);   //手机内存缓存没有,再从手机SD存储缓存中获取   if(null==map){      map = getBitmapFromDiskLruCache(url);   }   return map;}

 

3图片加载类SDImageLoader

图片加载类的主要工作过程:首先查找缓存,找到相应Bitmap则返回Bitmap,如果没有则根据路径从本地加载或者从网络下载;将从本地加载或者从网络下载Bitmap加入到缓存中,并刷新UI。当然加载图片Bitmap的操作都是在线程中进行的,为了管理这些线程,我建立了线程池,和用于管理线程池的线程,和线程池执行的任务队列,线程的调度方式有两种FIFO或者LIFO

3.1图片加载类初始化

图片加载类使用单例模式:

 

/** * 获取实例对象 * @param context * @param mThreadCount  并行线程数量 * @param type          任务执行顺序 * @return */public static SDImageLoader getInstance (Context context,int mThreadCount,Type type) {    if(null==mInstace){        synchronized (ImageLoader.class) {            if(null == mInstace) {                mInstace = new SDImageLoader(mThreadCount,type,context);            }        }    }    return mInstace;}

 

初始化图片加载类:

/**     * 初始化     * @param mThreadCount  线程数量     * @param type           调度类型     * @param context     */    private void init(int mThreadCount,Type type,Context context) {        mSemaphonreThreadPool = new Semaphore(mThreadCount);  //任务执行信号量
                //start 初始化控制线程池任务执行的线程        mPoolThread = new Thread() {            @Override            public void run() {                super.run();                Looper.prepare();                mPoolThreadHandler = new Handler () {                    @Override                    public void handleMessage(Message msg) {                        super.handleMessage(msg);                                                try {                            mSemaphonreThreadPool.acquire();                          //线程池去取出一个任务执行                            mThreadPool.execute(getTask());                        } catch (InterruptedException e) {                            e.printStackTrace();                        }                    }                };                mSemaphorePoolThreadHandler.release();                Looper.loop();            }        };        mPoolThread.start();      //start 初始化控制线程池任务执行的线程        /*        int maxMemory = (int)Runtime.getRuntime().maxMemory();        int cacheMemory = maxMemory/8;*/      mLruCache = new BitmapCacheL2(context);    //缓存mTaskQueue = new LinkedList<Runnable>();  //任务队列mThreadPool = Executors.newFixedThreadPool(mThreadCount); //线程池
    }

 

 

3.2图片加载类的主要方法

图片加载类的主要方法:public void loadImage(final String path, final ImageView imageView,boolean isFromNetwork),首先通过isFromNetwork判断是加载本地图片或者网络图片,然后选择一种加载图片Runnable任务,将任务加入到线程池任务队列中,并用Handler发送消息,通知控制控制线程,控制线程按照Type的调度类型取出任务提交给线程池执行。Runnable任务得到Bitmap之后会根据ImageView的高宽和Bitmap的高宽做图片的采样压缩,节省内存;接着使用Handler发送消息更新UI。
 
public void loadImage(final String path, final ImageView imageView,boolean isFromNetwork){       if(null == imageView) {          return;       }        imageView.setTag(path);        //start 初始化更新UI方法        if(null == mUIHandler){            mUIHandler = new Handler(){                @Override                public void handleMessage(Message msg) {                    ImageViewBeanHolder holder = (ImageViewBeanHolder) msg.obj;                    ImageView img = holder.imageView;                    String tagPath = img.getTag().toString();                    if(tagPath.equals(holder.path)){  //判断是否对应的路径                        Log.i(TAG, " path = " + holder.path + "");                        img.setImageBitmap(holder.bitmap);                    }                }            };        }        //end 初始化更新UI方法                Bitmap bitmap = getBitmapFromLruCache(path);  //从缓存中获取Bitmap                if(null != bitmap ){            refreshImageView(bitmap, imageView, path); //刷新UI        }else {           if(isFromNetwork == true) {  //从网络加载图片//            addTask(new BitmapFromNetworkRunnable(path,imageView));                            addTask(new Runnable() {                              @Override               public void run() {                  Bitmap bm = null;                  if(Build.VERSION.SDK_INT < Build.VERSION_CODES.FROYO){     //Android 2.2 以前版本使用此方法                     downloadImgByUrlHttpClient(path,imageView);                  }else {                     bm = downloadImgByUrl(path,imageView);                  }//                bm = downloadImgByUrlHttpClient(path,imageView);                  if(null == bm) {                     mSemaphonreThreadPool.release();                     return;                  }                  mSemaphonreThreadPool.release();                  addBitmap2LruCache(path, bm);                  refreshImageView(bm, imageView, path);                                 }            });                       } else {   //加载手机本地的图片            addTask(new Runnable() {               @Override               public void run() {                  // 加载图片                  // 图片压缩                  // 1获取图片显示的大小                  ImageSize imageSize = getImageSize(imageView);                  // 2图片压缩                  Bitmap bm = decodeSampleBitmap(path, imageSize.width,                        imageSize.height);                  addBitmap2LruCache(path, bm);                  mSemaphonreThreadPool.release();                  refreshImageView(bm, imageView, path);               }            });         }                   }    }
 
 
 

3.2.1加载手机本地图片

addTask(new Runnable() {   @Override   public void run() {      // 加载图片      // 图片压缩      // 1获取图片显示的大小      ImageSize imageSize = getImageSize(imageView);      // 2图片压缩      Bitmap bm = decodeSampleBitmap(path, imageSize.width,            imageSize.height);      addBitmap2LruCache(path, bm);      mSemaphonreThreadPool.release();      refreshImageView(bm, imageView, path);   }});
 
 

3.2.2 加载网络图片

加载网络图片,考虑到Android版本问题,Android 2.2 以前使用HttpClient,Android 2.2以后使用HttpURLConnection
 
HttpClient 的下载图片方法:
/**    * 从网络下载图片    * @param urlStr    * @param imageview    * @return    */    @SuppressWarnings("deprecation")   public Bitmap downloadImgByUrlHttpClient(String urlStr, ImageView imageview) {       Log.i(TAG,"downloadImgByUrlHttpClient *** ");       HttpClient httpclient = new DefaultHttpClient();          HttpGet httpget = new HttpGet(urlStr);                        try {         HttpResponse response = httpclient.execute(httpget);         if(HttpStatus.SC_OK == response.getStatusLine().getStatusCode()) {            HttpEntity entity = response.getEntity();            InputStream is = null;            is = new BufferedInputStream(entity.getContent());            /*          is.mark(is.available());                 Options opts = new Options();                 opts.inJustDecodeBounds = true;                 bitmap = BitmapFactory.decodeStream(is, null, opts);                                  //获取imageview想要显示的宽和高                 ImageSize imageViewSize = getImageSize(imageview);                 opts.inSampleSize = getBitmapSampleSize(opts,                         imageViewSize.width, imageViewSize.height);                 opts.inJustDecodeBounds = false;                 is.reset();                bitmap = BitmapFactory.decodeStream(is, null, opts);*/                        //          is = new BufferedInputStream(conn.getInputStream());            Log.i(TAG, " befor available() = " + is.available());            Bitmap bitmap = BitmapFactory.decodeStream(is);            Log.i(TAG, "after available() = " + is.available());                        ByteArrayOutputStream baos = new ByteArrayOutputStream();            // start 按照图片格式将bitmap转为输出流            if (urlStr.endsWith("png")) {               bitmap.compress(CompressFormat.PNG, 50, baos);            } else if (urlStr.endsWith("webp")) {               bitmap.compress(CompressFormat.WEBP, 50, baos);            } else {               bitmap.compress(CompressFormat.JPEG, 50, baos);            }            // end 按照图片格式将bitmap转为输出流            InputStream isBm = new ByteArrayInputStream(baos.toByteArray());            Log.i(TAG, " befor available() isBm = " + isBm.available());            isBm.mark(isBm.available());                        // start 采样压缩图片            Options opts = new Options();            opts.inJustDecodeBounds = true;            bitmap = BitmapFactory.decodeStream(isBm, null, opts);            Log.i(TAG, "after available() isBm = " + isBm.available());            // 获取imageview想要显示的宽和高            ImageSize imageViewSize = getImageSize(imageview);            opts.inSampleSize = getBitmapSampleSize(opts, imageViewSize.width,                  imageViewSize.height); // 采样            opts.inJustDecodeBounds = false;            if (isBm.markSupported()) {               isBm.reset();            }            bitmap = BitmapFactory.decodeStream(isBm, null, opts);            // end 采样压缩图片                        isBm.close();            baos.close();                //            bitmap = BitmapFactory.decodeStream(is);               is.close();               return bitmap;                     }               } catch (ClientProtocolException e) {         // TODO Auto-generated catch block         e.printStackTrace();      } catch (IOException e) {         // TODO Auto-generated catch block         e.printStackTrace();      }finally {         httpclient.getConnectionManager().shutdown();        }               return null;    }
 
HttpURLConnection 下载图片的方法:
 
/**    * 从网络下载图片    * @param urlStr    图片地址    * @param imageview     * @return    */public Bitmap downloadImgByUrl(String urlStr, ImageView imageview) {   Log.i(TAG, "downloadImgByUrl *** ");   FileOutputStream fos = null;   BufferedInputStream is = null;   try {      URL url = new URL(urlStr);      HttpURLConnection conn = (HttpURLConnection) url.openConnection();      conn.connect();      Log.i(TAG, " ResponseCode = " + conn.getResponseCode());      is = new BufferedInputStream(conn.getInputStream());      Log.i(TAG, " befor available() = " + is.available());      Bitmap bitmap = BitmapFactory.decodeStream(is);      Log.i(TAG, "after available() = " + is.available());            ByteArrayOutputStream baos = new ByteArrayOutputStream();      // start 按照图片格式将bitmap转为输出流      if (urlStr.endsWith("png")) {         bitmap.compress(CompressFormat.PNG, 50, baos);      } else if (urlStr.endsWith("webp")) {         bitmap.compress(CompressFormat.WEBP, 50, baos);      } else {         bitmap.compress(CompressFormat.JPEG, 50, baos);      }      // end 按照图片格式将bitmap转为输出流      InputStream isBm = new ByteArrayInputStream(baos.toByteArray());      Log.i(TAG, " befor available() isBm = " + isBm.available());      isBm.mark(isBm.available());            // start 采样压缩图片      Options opts = new Options();      opts.inJustDecodeBounds = true;      bitmap = BitmapFactory.decodeStream(isBm, null, opts);      Log.i(TAG, "after available() isBm = " + isBm.available());      // 获取imageview想要显示的宽和高      ImageSize imageViewSize = getImageSize(imageview);      opts.inSampleSize = getBitmapSampleSize(opts, imageViewSize.width,            imageViewSize.height); // 采样      opts.inJustDecodeBounds = false;      if (isBm.markSupported()) {         isBm.reset();      }      bitmap = BitmapFactory.decodeStream(isBm, null, opts);      // end 采样压缩图片            isBm.close();      baos.close();      is.close();      conn.disconnect();      return bitmap;   } catch (Exception e) {      e.printStackTrace();   } finally {      try {         if (is != null)            is.close();      } catch (IOException e) {      }      try {         if (fos != null)            fos.close();      } catch (IOException e) {      }   }   return null;}
 
 
 

4仿微信图片选择MainActivity1

主要过程:使用ContentResolver搜索手机内所有的图片,得到所有包含图片的文件夹;在搜索的同时得到文件夹的第一张图片的路径,和包含图片最多的文件夹;Handler发送消息更新UI显示包含图片最多的文件夹内的所有图片;增加点击事件可以选择其他的文件夹。

4.1 搜索手机内的图片

/** * 利用contentPrivider扫描手机中的图片 */private void initDatas(){    if(!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {        Toast.makeText(this,"没有存储卡",Toast.LENGTH_LONG).show();        return;    }    mProgressDialog = ProgressDialog.show(this,null,"正在查找..");    new Thread(){        @Override        public void run() {            super.run();            Uri mImageUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;            ContentResolver cr = MainActivity1.this.getContentResolver();            Cursor cursor = cr.query(mImageUri, null, MediaStore.Images.Media.MIME_TYPE + " = ? or " +                    MediaStore.Images.Media.MIME_TYPE + " = ?",                    new String[]{"image/jpeg", "image/png"},                    MediaStore.Images.Media.DATE_MODIFIED);            Set<String> mDirPath = new HashSet<String>(); //已经扫描过的包含图片文件的文件夹路径            String firstImage = null;            while (cursor.moveToNext()) {                String path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));                //第一张图片的路径                if(null == firstImage){                    firstImage = path;                }                //start 获取该图片的父路径                File parentFile = new File(path).getParentFile();                if(parentFile==null){                    continue;                }                //end 获取该图片的父路径                String dirPath = parentFile.getAbsolutePath();                FolderBean folderBean = null;                if(mDirPath.contains(dirPath)){                    continue;                }else {                    mDirPath.add(dirPath);                    folderBean = new FolderBean();                    folderBean.setDir(dirPath);                    folderBean.setFirstImgPath(path);                }                if(parentFile.list() == null) {                    continue;                }                //start 获取该文件夹下的图片文件数量                int picsSize = parentFile.list(new FilenameFilter() {                    @Override                    public boolean accept(File dir, String filename) {                        if(filename.endsWith(".jpg")                                ||filename.endsWith(".jpeg")                                ||filename.endsWith(".png"))                        {                            return true;                        }                        return false;                    }                }).length;                //end 获取该文件夹下的图片文件数量                folderBean.setCount(picsSize);                mFolderBeans.add(folderBean);                //start 设置图片文件最多的文件夹为当前文件夹                if(picsSize > mMaxCount){                    mMaxCount = picsSize;                    mCurrentDir = parentFile;                }            }            //end 设置图片文件最多的文件夹为当前文件夹            cursor.close();            mDirPath = null;            mHandler.sendEmptyMessage(0x110);        }    }.start();}
 

4.2 更新UI

private void data2View() {    if(mCurrentDir == null){        Toast.makeText(this,"没有扫描到图片",Toast.LENGTH_LONG).show();        return;    }    //start 当前文件夹的图片文件    mImgs = Arrays.asList(mCurrentDir.list(new FilenameFilter() {        @Override        public boolean accept(File dir, String filename) {            if(filename.endsWith(".jpg")                    ||filename.endsWith(".jpeg")                    ||filename.endsWith(".png"))            {                return true;            }            return false;        }    }));    //end 当前文件夹的图片文件    if(null==mAdapter){        mAdapter = new ImageAdapterAdapter(this,mImgs,mCurrentDir.getAbsolutePath());    }    mGridView.setAdapter(mAdapter);    mDirCount.setText(mMaxCount + "");    mDirName.setText(mCurrentDir.getName());}
 

4.3 弹出选择文件夹的窗口的初始化

/** * 初始化popupwindow */private void initPop() {    Log.i(TAG,"mFolderBeans = "+mFolderBeans);    mPop = new ListImageDirPopupWindow(this,mFolderBeans);    mPop.setAnimationStyle(R.style.dir_popupwindow_anim);    mPop.setOnDismissListener(new PopupWindow.OnDismissListener() {        @Override        public void onDismiss() {            lightOn();        }    });    //start设置弹出窗口图片路径选择回调监听    mPop.setOnDirSelectedListener(new ListImageDirPopupWindow.OnDirSelectedListener() {        @Override        public void onDirSelected(FolderBean folderBean) {            if(null != folderBean) {                mCurrentDir = new File(folderBean.getDir()); //选中文件路径                mImgs = Arrays.asList(mCurrentDir.list(new FilenameFilter() { //文件路径中图片路径                    @Override                    public boolean accept(File dir, String filename) {                        if(filename.endsWith(".jpg")                                ||filename.endsWith(".jpeg")                                ||filename.endsWith(".png"))                        {                            return true;                        }                        return false;                    }                }));                //start 刷新图片gridView                if(null==mAdapter){                    mAdapter = new ImageAdapterAdapter(MainActivity1.this,mImgs,mCurrentDir.getAbsolutePath());                    mGridView.setAdapter(mAdapter);                }else {                    mAdapter.setDirPath(mCurrentDir.getAbsolutePath());                    mAdapter.setSourceData(mImgs);                    mAdapter.notifyDataSetChanged();                }                //end 刷新图片gridView                mDirCount.setText(mImgs.size() + "");    //文件中图片的数量                mDirName.setText(mCurrentDir.getName()); //文件名            }            mPop.dismiss();        }    });    //end 设置弹出窗口图片路径选择回调监听}
 

5加载网络图片MainActivity2

首先活动网络图片的链接再更新UI显示图片

5.1 获取网络加载的Url

所有的网络图片的Url都在Images类中,所有的链接约有3000多张,有些链接可能已经失效,当然你也可以自己抓取百度的图片里面的图片,我是用Chrome浏览器的一个插件“小乐图客”来抓取的。
/** * 获取图片链接 * @param num * @return */private List<String> getUrlList(int num) {   List<String> urlList = null;   if(num < 0) {  //所有链接      urlList = Arrays.asList(imageThumbUrls);   }   if(num == 0) { //无链接      urlList = new LinkedList<String>();         }   if(num >0) {  //根据数量      if(num < imageThumbUrls.length) {         urlList = new LinkedList<String>();         for(int i=0 ;i<num; i++) {            urlList.add(imageThumbUrls[i]);         }      } else {         urlList = Arrays.asList(imageThumbUrls);      }            }      return urlList;}
 
 

5.2 更新UI

private void bindGvData2(){   mAdapter = new ImageAdapter2(this, mGv,getUrlList(-1));   mGv.setAdapter(mAdapter);}
 
本文结束,谢谢各位阅读,如有错误请指出,谢谢!
参考资料:
http://blog.csdn.net/lmj623565791/article/details/41874561
http://blog.csdn.net/guolin_blog/article/details/28863651
http://blog.csdn.net/xiaanming/article/details/9825113
http://my.oschina.net/jeffzhao/blog/80900
源码下载链接:
http://download.csdn.net/detail/luoshishou/9508282
 
 

 

1 0
原创粉丝点击