Android进阶之图片加载框架搭建(二)代码完全实现

引言

在上篇博客中Android进阶之图片加载框架搭建(一)消息机制原理分析及在并发中的应用,结合Android消息机制构建了图片请求的任务管理模型,在此基础上,本篇博客将说明一个简单的图片加载器的完全实现。主要有:

  1. 图片的缩放
  2. 图片的缓存

图片的缩放

ImageSize:提供获得ImageView宽高和缩放比的工具方法

package com.qicode.imageloaderdr.util;

/**
 * Created by chenming on 16/9/26.
 * 图片宽高封装
 */
public class ImageSize {
   public int width;
   public int height;
}

图片缩放工具ImageSizeUtil:

package com.qicode.imageloaderdr.util;

import android.graphics.BitmapFactory;
import android.util.DisplayMetrics;
import android.view.ViewGroup;
import android.widget.ImageView;

import java.lang.reflect.Field;

/**
 * Created by chenming on 16/9/26.
 */
public class ImageSizeUtil {
    /**
     * 获得imageview的宽高
     * @param imageView
     * @return
     */
    public static ImageSize getImageViewSize(ImageView imageView){
        ImageSize result = new ImageSize();

        DisplayMetrics displayMetrics = imageView.getContext().getResources()
                .getDisplayMetrics();
        ViewGroup.LayoutParams lp = imageView.getLayoutParams();
        /**
         * step1:获得实际宽度
         * step2:获得布局指定宽度
         * step3:获得mMaxWidth
         * step4:获得屏幕宽度
         * 高度也做同样处理
         */
        //高度
        int width = imageView.getWidth();//step1
        if(width <= 0){
            width = lp.width;//step 2
        }

        if(width <= 0){
            width = getMaxWidth(imageView);//step 3
        }

        if(width <= 0){
            width = displayMetrics.widthPixels;//step 4
        }

        //高度
        int height = imageView.getHeight();//step1
        if(height <= 0){
            height = lp.height;//step 2
        }

        if(height <= 0){
            height = getMaxHeight(imageView);//step 3
        }

        if(height <= 0){
            height = displayMetrics.heightPixels;//step 4
        }

        result.width = width;
        result.height = height;
        return result;
    }

    /**
     * 反射获得最大宽度
     * @param imageView
     * @return
     */
    private static int getMaxWidth(ImageView imageView) {
        try {
            Class clazz = Class.forName("android.widget.ImageView");
            Field field = clazz.getDeclaredField("mMaxWidth");
            field.setAccessible(true);
            return field.getInt(imageView);
        } catch (ClassNotFoundException e) {

        } catch (NoSuchFieldException e) {
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return 0;
    }

    /**
     * 反射获得最大高度
     * @param imageView
     * @return
     */
    private static int getMaxHeight(ImageView imageView) {
        try {
            Class clazz = Class.forName("android.widget.ImageView");
            Field field = clazz.getDeclaredField("mMaxHeight");
            field.setAccessible(true);
            return field.getInt(imageView);
        } catch (ClassNotFoundException e) {

        } catch (NoSuchFieldException e) {
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return 0;
    }

    /**
     * 根据需求的宽和高以及图片实际的宽和高计算SampleSize
     * @param options
     * @param reqWidth
     * @param reqHeight
     * @return
     */
    public static int caculateInSampleSize(BitmapFactory.Options options, int reqWidth,
                                           int reqHeight){

        int rawWidth = options.outWidth;
        int rawHeigh = options.outHeight;

        int sampleRatio = 1;

        if (rawWidth > reqWidth || rawHeigh > reqHeight){
            int widthRatio = Math.round(rawWidth * 1.0f / reqWidth);
            int heightRatio = Math.round(rawHeigh * 1.0f / reqHeight);
            sampleRatio = Math.max(widthRatio, heightRatio);
        }
        return sampleRatio;
    }
}

两级缓存

磁盘缓存采用谷歌推荐的DiskLruCache,内存缓存采用LruCache,在ImageLoader中的初始化代码如下:

//缓存配置
    private LruCache<String, Bitmap> mLruCache;
    private DiskLruCache mDiskLruCache;
    private static final long DISK_CACHE_SIZE = 1024 * 1024 * 50;
    private boolean mIsDiskCacheEnable = true;//磁盘缓存开关,默认开启

    /**
     * 内存缓存
     */
    private void initMemoryCache() {
        int maxMemory = (int) Runtime.getRuntime().maxMemory();
        int cacheMemory = maxMemory / 8;
        mLruCache = new LruCache<String, Bitmap>(cacheMemory) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getRowBytes() * value.getHeight();
            }
        };
    }

    /**
     * 磁盘缓存初始化
     *
     * @param context
     */
    private void initDiskCache(Context context) {
        //磁盘缓存初始化
        String dir = getImageCacheFile(context);
        File file = new File(dir);
        if (!file.exists()) {
            file.mkdirs();
        }
        File diskCacheDir = new File(dir);
        try {
            mDiskLruCache = DiskLruCache.open(diskCacheDir, 1, 1, DISK_CACHE_SIZE);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * DiskLru缓存目录
     *
     * @param context
     * @return
     */
    private String getImageCacheFile(Context context) {
        return StringUtils
                .getString(context.getCacheDir(), "/cacheImage/");
    }

内存缓存的读写操作:

 private Bitmap getBitmapFromLruCache(String path) {
        Bitmap bp = mLruCache.get(path);
        return bp;
    }

    /**
     * 将图片加入LruCache
     *
     * @param path
     * @param bm
     */
    protected void addBitmapToLruCache(String path, Bitmap bm) {
        if (getBitmapFromLruCache(path) == null) {
            if (bm != null)
                mLruCache.put(path, bm);
        }
    }

磁盘缓存的读写操作:

    /**
     * 写入bitmap到磁盘
     *
     * @param path
     * @param bp
     */
    private void saveBitmapToDisk(String path, Bitmap bp) throws IOException {
        boolean isFinish = false;
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        bp.compress(Bitmap.CompressFormat.PNG, 100, baos);
        InputStream in = new ByteArrayInputStream(baos.toByteArray());
        OutputStream os = null;
        DiskLruCache.Editor editor = null;
        String key = StringUtils.toMD5(path);
        editor = mDiskLruCache.edit(key);
        if (editor != null) {
            os = editor.newOutputStream(0);
        }

        int rbyte;
        if (os != null) {
            while ((rbyte = in.read()) != -1) {
                os.write(rbyte);
            }
            isFinish = true;
        }
        //提交
        if (editor != null) {
            if (isFinish) {
                editor.commit();

            } else {
                editor.abort();
            }
        }
        mDiskLruCache.flush();

    }

    /**
     * 从磁盘缓存中取bp
     *
     * @param path
     * @return
     * @throws IOException
     */
    private Bitmap getBitmapFromDisk(String path) throws IOException {
        Bitmap bp = null;
        String key = StringUtils.toMD5(path);
        DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);
        if (snapshot != null) {
            FileInputStream fis = (FileInputStream) snapshot.getInputStream(0);
            bp = BitmapFactory.decodeStream(fis);
        }
        return bp;
    }

注:磁盘缓存文件名为图片URL地址的MD5值,实际项目中同一个URL可能用到不同大小的ImageView上,因此需要考虑宽高因素,然后再MD5。这里作为示例,只做简单处理。

图片下载单元ImageDownloader

package com.qicode.imageloaderdr.imageloader;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.widget.ImageView;

import com.qicode.imageloaderdr.util.BitmapUtils;
import com.qicode.imageloaderdr.util.ImageSize;
import com.qicode.imageloaderdr.util.ImageSizeUtil;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

/**
 * Created by chenming on 16/9/26.
 * 图片下载器,图片下载线程的执行代码
 */
public class ImageDownloader {
    /**
     * 无硬盘缓存,下载图片到内存
     * @param urlStr
     * @param imageView
     * @return
     * @throws IOException
     */
    public static Bitmap downloadImgFromUrl(String urlStr, ImageView imageView) {
        InputStream is = null;
        try {
            URL url = new URL(urlStr);
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            is = new BufferedInputStream(connection.getInputStream());
            is.mark(1024*1024);
            //获得网络图片的宽高
            BitmapFactory.Options opts = new BitmapFactory.Options();
            opts.inJustDecodeBounds = true;
            Bitmap bitmap = BitmapFactory.decodeStream(is, null, opts);

            ImageSize imageSize = ImageSizeUtil.getImageViewSize(imageView);
            opts.inSampleSize = ImageSizeUtil.caculateInSampleSize(opts, imageSize.width, imageSize.height);

            opts.inJustDecodeBounds = false;
            is.reset();
            bitmap = BitmapFactory.decodeStream(is, null, opts);
            connection.disconnect();
            return bitmap;
        } catch (MalformedURLException e) {
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (is != null)
                    is.close();
            } catch (IOException e) {
            }

        }
        return null;
    }

    /**
     * 下载图片到硬盘
     * @param urlStr
     * @param imageView
     * @return
     * @throws IOException
     */
    public static Bitmap downloadImgFromUrlToFile(String urlStr, ImageView imageView, String fileName) {
        InputStream is = null;
        try {
            URL url = new URL(urlStr);
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            is = new BufferedInputStream(connection.getInputStream());
            is.mark(1024*1024);
            //获得网络图片的宽高
            BitmapFactory.Options opts = new BitmapFactory.Options();
            opts.inJustDecodeBounds = true;
            Bitmap bitmap = BitmapFactory.decodeStream(is, null, opts);

            ImageSize imageSize = ImageSizeUtil.getImageViewSize(imageView);
            opts.inSampleSize = ImageSizeUtil.caculateInSampleSize(opts, imageSize.width, imageSize.height);

            opts.inJustDecodeBounds = false;
            is.reset();
            bitmap = BitmapFactory.decodeStream(is, null, opts);
            BitmapUtils.saveBitmap(imageView.getContext(), bitmap, fileName);
            connection.disconnect();
            return bitmap;
        } catch (MalformedURLException e) {
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (is != null)
                    is.close();
            } catch (IOException e) {
            }

        }
        return null;
    }
}

这里提供了下载到内存和磁盘两种方法。

ImageLoader实现

ImageLoader结构:
1.LRU内存缓存
2.后台线程,用于调度下载图片的线程,通过构建后台消息模型及信号量实现并发下载
3.下载线程池
4.磁盘缓存
5.更新ImageView的handler
有关任务管理的代码已经在上篇博客详细说明了实现原理,这里只说明图片加载的核心代码。

初始化代码:

多了缓存的配置:

   initMemoryCache();
   initDiskCache(context); 

调用入口

    /**
     * 调用入口
     *
     * @param path      文件或者网络地址
     * @param imageView 目标控件
     * @param isFromNet true 本地图片 false 加载本地图片
     */
    public void loadImage(final String path, final ImageView imageView, final boolean isFromNet) {
        imageView.setTag(path);//避免错位
        if (mUIHandler == null) {
            mUIHandler = new Handler() {
                public void handleMessage(Message msg) {
                    // 获取得到图片,为imageview回调设置图片
                    ImgBeanHolder holder = (ImgBeanHolder) msg.obj;
                    Bitmap bm = holder.bitmap;
                    ImageView imageview = holder.imageView;
                    String path = holder.path;
                    // 将path与getTag存储路径进行比较
                    if (imageview.getTag().toString().equals(path)) {
                        imageview.setImageBitmap(bm);
                    }
                }
            };
        }

        //内存缓存检测
        Bitmap bp = getBitmapFromLruCache(BitmapUtils.toMD5(path));
        if (bp == null) {
            addTask(buildTask(path, imageView, isFromNet));
        } else {
            refreshBitmap(path, imageView, bp);
        }
    }

private class ImgBeanHolder {
        private Bitmap bitmap;
        private ImageView imageView;
        private String path;
    }

说明:
1.UIHandler初始化
2.检测内存,如果有BP,则直接刷新UI;如果没有则构建任务,加入调度队列
3.imageView.setTag(path)是为了避免在列表组件中显示错位。
buildTask方法:

/**
     * 获取bp的核心代码,没有内存缓存时,下载网络图片,加入磁盘和内存缓存
     *
     * @return
     */
    private Runnable buildTask(final String path, final ImageView imageView, final boolean isFromNet) {
        return new Runnable() {
            @Override
            public void run() {
                Bitmap bp = null;
                if (isFromNet) {//加载网络图片
                    if (mIsDiskCacheEnable) {
                        //本地缓存处理
                        //disklrucache读缓存
                        try {
                            bp = getBitmapFromDisk(path);
                            if (bp == null) {
                                //下载图片
                                bp = ImageDownloader.downloadImgFromUrl(path, imageView);
                                if (bp != null) {
                                    saveBitmapToDisk(path, bp);
                                }
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    } else {
                        //直接从网络加载
                        bp = ImageDownloader.downloadImgFromUrl(path, imageView);
                    }
                } else {
                    //加载本地图片
                    bp = loadImageFromLocal(path, imageView);
                }

                mBackLoopThreadSemaphore.release();//该任务执行完成, 释放并发加载图片信号量
                if (bp != null) {
                    //更新UI
                    refreshBitmap(path, imageView, bp);
                    //加入内存缓存
                    addBitmapToLruCache(BitmapUtils.toMD5(path), bp);
                }
            }
        };
    }

说明:如果磁盘缓存开关开启,则查看磁盘是否有已下载的BP,否则走网络下载。其他逻辑注释已经很明了,不再赘述。

加载本地图片:

    /**
     * 加载本地图片
     *
     * @param path
     * @param imageView
     * @return
     */
    private Bitmap loadImageFromLocal(final String path, final ImageView imageView) {
        Bitmap bm;
        // 加载图片
        // 图片的压缩
        // 1、获得图片需要显示的大小
        ImageSize imageSize = ImageSizeUtil.getImageViewSize(imageView);
        // 2、压缩图片
        bm = decodeSampledBitmapFromPath(path, imageSize.width,
                imageSize.height);
        return bm;
    }

    /**
     * 压缩本地图片
     *
     * @param path
     * @param width
     * @param height
     * @return
     */
    private Bitmap decodeSampledBitmapFromPath(String path, int width, int height) {
        BitmapFactory.Options opts = new BitmapFactory.Options();
        opts.inJustDecodeBounds = true;
        Bitmap bitmap = BitmapFactory.decodeFile(path, opts);
        opts.inSampleSize = ImageSizeUtil.caculateInSampleSize(opts, width, height);
        opts.inJustDecodeBounds = false;
        bitmap = BitmapFactory.decodeFile(path, opts);
        return bitmap;
    }

最后用到的工具类代码

 package com.qicode.imageloaderdr.util;

import android.content.Context;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Environment;
import android.text.TextUtils;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * Created by chenming on 16/10/27.
 */

public class BitmapUtils {
    /**
     * 将bitmap存成文件
     */
    public static String saveBitmap(Context context, Bitmap bitmap, String fileName) {
        if (context == null || bitmap == null || TextUtils.isEmpty(fileName)) {
            return null;
        }
        String ret = null;
        OutputStream fos = null;
        try {
            Uri uri = Uri.fromFile(new File(BitmapUtils.getTempSaveDir(context) + fileName));
            File file = new File(uri.getPath());
            if (!file.exists()) {
                try {
                    file.createNewFile();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            fos = context.getContentResolver().openOutputStream(uri);
            if (fos != null) {
                bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
                fos.flush();
            }
            ret = getTempSaveDir(context) + fileName;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (fos != null) {
                    fos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return ret;
    }

    /**
     * 获得缓存目录路径
     *
     * @param context
     * @return
     */
    public static String getTempSaveDir(Context context) {
        String userDir;
        userDir = context.getCacheDir().getAbsolutePath();
        File file = new File(userDir);
        if (!file.exists()) {
            file.mkdirs();
        }
        return file.getAbsolutePath() + "/";
    }

    /**
     * 将字符串转成MD5值
     */
    public static String toMD5(String string) {
        byte[] hash;

        try {
            hash = MessageDigest.getInstance("MD5").digest(string.getBytes("UTF-8"));
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            return null;
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            return null;
        }

        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();
    }

}
    
package com.qicode.imageloaderdr.util;

import android.app.Activity;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;
import android.support.v4.content.ContextCompat;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.style.AbsoluteSizeSpan;
import android.text.style.CharacterStyle;
import android.text.style.ForegroundColorSpan;
import android.util.Base64;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Created by huyongsheng on 2014/7/18.
 */
public class StringUtils {

    /**
     * 生成google play连接地址
     */
    public static String getGooglePlayString(Activity activity, String packageName) {
        return getGooglePlayString(packageName, "flip", activity.getPackageName());
    }

    /**
     * 生成google play连接地址
     */
    public static String getGooglePlayString(String packageName, String source, String medium) {
        return StringUtils
                .getString("market://details?id=", packageName, "&referrer=", "utm_source%3D", source, "%26utm_medium%3D",
                        medium);
    }

    /**
     * 最优化String的构建
     */
    public static String getString(Object... objects) {
        StringBuffer buffer = new StringBuffer();
        for (Object object : objects) {
            buffer.append(object);
        }
        return buffer.toString();
    }

    /**
     * 得到配置文件中的MetaData数据
     */
    public static String getMetaData(Context context, String keyName) {
        try {
            ApplicationInfo info = context.getPackageManager().getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA);
            Bundle bundle = info.metaData;
            Object value = bundle.get(keyName);
            if (value != null) {
                return value.toString();
            } else {
                return null;
            }
        } catch (NameNotFoundException e) {
            return null;
        }
    }

    /**
     * 获取package信息
     */
    public static PackageInfo getPackageInfo(Context context) throws NameNotFoundException {
        return context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
    }

    /**
     * 生成base64编码
     */
    public static String encodeBase64(String string) {
        return Base64.encodeToString(string.getBytes(), Base64.NO_WRAP);
    }

    /**
     * base64解码
     */
    public static String decodeBase64(String string) {
        String result = null;
        if (!StringUtils.isNullOrEmpty(string)) {
            try {
                result = new String(Base64.decode(string, Base64.NO_WRAP), "utf-8");
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            }
        }
        return result;
    }

    /**
     * 对double数据进行截断
     */
    public static String cutDouble0(double value) {
        DecimalFormat format = new DecimalFormat("##0");
        return format.format(value);
    }

    /**
     * 对double数据进行截断
     */
    public static String cutFloat0(float value) {
        DecimalFormat format = new DecimalFormat("##0");
        return format.format(value);
    }

    /**
     * 判断String是否为空
     */
    public static boolean isNullOrEmpty(String inputString) {
        return null == inputString || inputString.trim().equals("");
    }

    /**
     * 判断bytes是否为空
     */
    public static boolean isNullOrEmpty(byte[] bytes) {
        return null == bytes || bytes.length == 0;
    }

    /**
     * 获取post请求中的参数
     */
    public static String getPostParams(String preString, Object object) {
        String result = getString(preString, "{");
        boolean isFirst = true;
        // 获取object对象对应类中的所有属性域
        Field[] fields = object.getClass().getDeclaredFields();
        for (Field field : fields) {
            // 对于每个属性,获取属性名
            String varName = field.getName();
            try {
                // 获取原来的访问控制权限
                boolean accessFlag = field.isAccessible();
                // 修改访问控制权限
                field.setAccessible(true);
                // 获取在对象object中属性field对应的对象中的变量
                Object value = field.get(object);
                // 生成参数,其实跟get的URL中'?'后的参数字符串一致
                if (isFirst) {
                    if (value instanceof String) {
                        result += getString("\"", URLEncoder.encode(varName, "utf-8"), "\":\"",
                                URLEncoder.encode(String.valueOf(value), "utf-8"), "\"");
                    } else {
                        result += getString("\"", URLEncoder.encode(varName, "utf-8"), "\":",
                                URLEncoder.encode(String.valueOf(value), "utf-8"));
                    }
                    isFirst = false;
                } else {
                    if (value instanceof String) {
                        result += getString(",\"", URLEncoder.encode(varName, "utf-8"), "\":\"",
                                URLEncoder.encode(String.valueOf(value), "utf-8"), "\"");
                    } else {
                        result += getString(",\"", URLEncoder.encode(varName, "utf-8"), "\":",
                                URLEncoder.encode(String.valueOf(value), "utf-8"));
                    }
                }
                // 恢复访问控制权限
                field.setAccessible(accessFlag);
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        }
        result += "}";
        return result;
    }

    /**
     * 获取post请求中的参数
     */
    public static String getSimplePostParams(Object object) {
        String result = "";
        boolean isFirst = true;
        // 获取object对象对应类中的所有属性域
        Field[] fields = object.getClass().getDeclaredFields();
        for (Field field : fields) {
            // 对于每个属性,获取属性名
            String varName = field.getName();
            try {
                // 获取原来的访问控制权限
                boolean accessFlag = field.isAccessible();
                // 修改访问控制权限
                field.setAccessible(true);
                // 获取在对象object中属性field对应的对象中的变量
                Object value = field.get(object);
                // 生成参数,其实跟get的URL中'?'后的参数字符串一致
                if (value != null) {
                    if (isFirst) {
                        result += getString(URLEncoder.encode(varName, "utf-8"), "=", URLEncoder.encode(String.valueOf(value), "utf-8"));
                        isFirst = false;
                    } else {
                        result +=
                                getString("&", URLEncoder.encode(varName, "utf-8"), "=", URLEncoder.encode(String.valueOf(value), "utf-8"));
                    }
                }
                // 恢复访问控制权限
                field.setAccessible(accessFlag);
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        }
        return result;
    }

    /**
     * 使用sha加密
     */
    public static String getSHA(String val) {
        MessageDigest md5 = null;
        try {
            md5 = MessageDigest.getInstance("SHA-1");
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        md5.update(val.getBytes());
        byte[] m = md5.digest();//加密  
        return getString(m);
    }

    /**
     * 手机号合法性校验
     */
    public static boolean checkPhoneNumber(String value) {
//        String regExp = "^((13[0-9])|(15[^4,\\D])|(18[0-9])|(147))\\d{8}$";
        String regExp = "^1\\d{10}$";
        Pattern p = Pattern.compile(regExp);
        Matcher m = p.matcher(value);
        return m.find();
    }

    /**
     * 正则检测
     *
     * @param content
     * @param format
     * @return
     */
    public static boolean checkStringFormat(String content, String format) {
        Pattern p = Pattern.compile(format);
        Matcher m = p.matcher(content);
        return m.find();
    }

    public static boolean isNum(String str) {
        Pattern pattern = Pattern.compile("[0-9]*");
        return pattern.matcher(str).matches();
    }

    /**
     * 将字符串转成MD5值
     */
    public static String toMD5(String string) {
        byte[] hash;

        try {
            hash = MessageDigest.getInstance("MD5").digest(string.getBytes("UTF-8"));
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            return null;
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            return null;
        }

        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();
    }

    /**
     * 获取该输入流的MD5值
     *
     * @throws NoSuchAlgorithmException
     * @throws IOException
     */
    public static String getMD5(InputStream is) throws NoSuchAlgorithmException, IOException {
        StringBuffer md5 = new StringBuffer();
        MessageDigest md = MessageDigest.getInstance("MD5");
        byte[] dataBytes = new byte[1024];

        int read;
        while ((read = is.read(dataBytes)) != -1) {
            md.update(dataBytes, 0, read);
        }
        byte[] bytes = md.digest();

        // convert the byte to hex format
        for (byte b : bytes) {
            md5.append(Integer.toString((b & 0xff) + 0x100, 16).substring(1));
        }
        return md5.toString();
    }

    public static String getPrice(int price) {
        DecimalFormat format = new DecimalFormat("###0.00");
        float money = price / 100.0f;
        return format.format(money);
    }

    public static boolean isValidEnglish(String name) {
        String noSpaceName = name.replace(" ", "");
        String reg = "^[A-Za-z]+$";
        Matcher m = Pattern.compile(reg).matcher(noSpaceName);
        return m.find();
    }

    /**
     * 实现文本复制功能
     */
    public static void copy(Context context, String content) {
        ClipboardManager cmb = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
        cmb.setText(content.trim());
    }

    /**
     * 实现粘贴功能
     */
    public static String paste(Context context) {
        ClipboardManager cmb = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
        return cmb.getText().toString().trim();
    }


    public static String dateFormat(long timeStamp, String dateFormat) {
        SimpleDateFormat sdf = new SimpleDateFormat(dateFormat, Locale.CHINA);
        Date date = new Date(timeStamp * 1000);
        return sdf.format(date);
    }

    /**
     * 部分编码后的url
     * @param url
     * @return
     */
    public static String restoreEncodeUrl(String url) {
        //修正反斜杠为斜杠
        url = url.replace("\\", "/");
        //使用长文本代替要保留字符串
        url = url.replace(":", "_*colon*_")
                .replace("/", "_*slash*_")
                .replace("\\", "_*backslash*_")
                .replace(" ", "_*blank*_")
                .replace("?", "_*question*_")
                .replace("=", "_*equal*_")
                .replace(";", "_*semicolon*_");

        //进行编码
        try {
            url = URLEncoder.encode(url, "utf-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

        url = url.replace("_*colon*_", ":")
                .replace("_*slash*_", "/")
                .replace("_*backslash*_", "\\")
                .replace("_*blank*_", "%20")
                .replace("_*question*_", "?")
                .replace("_*equal*_", "=")
                .replace("_*semicolon*_", ";");

        return url;
    }

    /**
     * spannable str
     *
     * @param context
     * @param fullStr
     * @param highLightStr
     * @param colorId
     * @return
     */
    public static SpannableStringBuilder getSpannable(Context context, String fullStr, String highLightStr, int colorId, int textSizeId) {
        int mStart;
        int mEnd;
        if (!TextUtils.isEmpty(fullStr) && !TextUtils.isEmpty(highLightStr)) {
            if (fullStr.contains(highLightStr)) {
                /*
                 *  返回highlightStr字符串wholeStr字符串中第一次出现处的索引。
                 */
                mStart = fullStr.indexOf(highLightStr);
                mEnd = mStart + highLightStr.length();
            } else {
                return new SpannableStringBuilder(fullStr);
            }
        } else {
            return new SpannableStringBuilder(fullStr);
        }
        SpannableStringBuilder spBuilder = new SpannableStringBuilder(fullStr);
        int color = ContextCompat.getColor(context, colorId);
        CharacterStyle charaStyle = new ForegroundColorSpan(color);//颜色
        spBuilder.setSpan(charaStyle, mStart, mEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        spBuilder.setSpan(new AbsoluteSizeSpan(textSizeId), mStart, mEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        return spBuilder;
    }

    /**
     * 大小不一样的String
     * @param context
     * @param fullStr
     * @param highLightStr
     * @return
     */
    public static SpannableStringBuilder getTextSizeSpannable(Context context, String fullStr, String highLightStr, int textSizeId, int colorId) {
        int mStart;
        int mEnd;
        if (!TextUtils.isEmpty(fullStr) && !TextUtils.isEmpty(highLightStr)) {
            if (fullStr.contains(highLightStr)) {
                /*
                 *  返回highlightStr字符串wholeStr字符串中第一次出现处的索引。
                 */
                mStart = fullStr.indexOf(highLightStr);
                mEnd = mStart + highLightStr.length();
            } else {
                return new SpannableStringBuilder(fullStr);
            }
        } else {
            return new SpannableStringBuilder(fullStr);
        }
        SpannableStringBuilder spBuilder = new SpannableStringBuilder(fullStr);
        spBuilder.setSpan(new AbsoluteSizeSpan(textSizeId), mStart, mEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        int color = ContextCompat.getColor(context, colorId);
        CharacterStyle charaStyle = new ForegroundColorSpan(color);//颜色
        spBuilder.setSpan(charaStyle, 0, fullStr.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        return spBuilder;
    }
}

总结:这里只是实现了一个轻量型的图片加载框架,没有考虑到可扩展性(如Cache的设计、图片的加载监听、BP处理接口等等),但足以让你了解它的核心实现原理和方法。希望对读者有帮助!GitHub地址

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,837评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,551评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,417评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,448评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,524评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,554评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,569评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,316评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,766评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,077评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,240评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,912评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,560评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,176评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,425评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,114评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,114评论 2 352

推荐阅读更多精彩内容