Android進(jìn)階之圖片加載框架搭建(二)代碼完全實(shí)現(xiàn)

引言

在上篇博客中Android進(jìn)階之圖片加載框架搭建(一)消息機(jī)制原理分析及在并發(fā)中的應(yīng)用,結(jié)合Android消息機(jī)制構(gòu)建了圖片請(qǐng)求的任務(wù)管理模型,在此基礎(chǔ)上极景,本篇博客將說明一個(gè)簡單的圖片加載器的完全實(shí)現(xiàn)机断。主要有:

  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:獲得實(shí)際寬度
         * 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;
    }

    /**
     * 根據(jù)需求的寬和高以及圖片實(shí)際的寬和高計(jì)算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;
    }
}

兩級(jí)緩存

磁盤緩存采用谷歌推薦的DiskLruCache,內(nèi)存緩存采用LruCache,在ImageLoader中的初始化代碼如下:

//緩存配置
    private LruCache<String, Bitmap> mLruCache;
    private DiskLruCache mDiskLruCache;
    private static final long DISK_CACHE_SIZE = 1024 * 1024 * 50;
    private boolean mIsDiskCacheEnable = true;//磁盤緩存開關(guān),默認(rèn)開啟

    /**
     * 內(nèi)存緩存
     */
    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/");
    }

內(nèi)存緩存的讀寫操作:

 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值,實(shí)際項(xiàng)目中同一個(gè)URL可能用到不同大小的ImageView上做祝,因此需要考慮寬高因素囱晴,然后再M(fèi)D5。這里作為示例舟奠,只做簡單處理。

圖片下載單元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.
 * 圖片下載器,圖片下載線程的執(zhí)行代碼
 */
public class ImageDownloader {
    /**
     * 無硬盤緩存,下載圖片到內(nèi)存
     * @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);
            //獲得網(wǎng)絡(luò)圖片的寬高
            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);
            //獲得網(wǎng)絡(luò)圖片的寬高
            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;
    }
}

這里提供了下載到內(nèi)存和磁盤兩種方法。

ImageLoader實(shí)現(xiàn)

ImageLoader結(jié)構(gòu):
1.LRU內(nèi)存緩存
2.后臺(tái)線程,用于調(diào)度下載圖片的線程,通過構(gòu)建后臺(tái)消息模型及信號(hào)量實(shí)現(xiàn)并發(fā)下載
3.下載線程池
4.磁盤緩存
5.更新ImageView的handler
有關(guān)任務(wù)管理的代碼已經(jīng)在上篇博客詳細(xì)說明了實(shí)現(xiàn)原理驮配,這里只說明圖片加載的核心代碼。

初始化代碼:

多了緩存的配置:

   initMemoryCache();
   initDiskCache(context); 

調(diào)用入口

    /**
     * 調(diào)用入口
     *
     * @param path      文件或者網(wǎng)絡(luò)地址
     * @param imageView 目標(biāo)控件
     * @param isFromNet true 本地圖片 false 加載本地圖片
     */
    public void loadImage(final String path, final ImageView imageView, final boolean isFromNet) {
        imageView.setTag(path);//避免錯(cuò)位
        if (mUIHandler == null) {
            mUIHandler = new Handler() {
                public void handleMessage(Message msg) {
                    // 獲取得到圖片着茸,為imageview回調(diào)設(shè)置圖片
                    ImgBeanHolder holder = (ImgBeanHolder) msg.obj;
                    Bitmap bm = holder.bitmap;
                    ImageView imageview = holder.imageView;
                    String path = holder.path;
                    // 將path與getTag存儲(chǔ)路徑進(jìn)行比較
                    if (imageview.getTag().toString().equals(path)) {
                        imageview.setImageBitmap(bm);
                    }
                }
            };
        }

        //內(nèi)存緩存檢測(cè)
        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.檢測(cè)內(nèi)存壮锻,如果有BP,則直接刷新UI涮阔;如果沒有則構(gòu)建任務(wù)猜绣,加入調(diào)度隊(duì)列
3.imageView.setTag(path)是為了避免在列表組件中顯示錯(cuò)位。
buildTask方法:

/**
     * 獲取bp的核心代碼,沒有內(nèi)存緩存時(shí),下載網(wǎng)絡(luò)圖片敬特,加入磁盤和內(nèi)存緩存
     *
     * @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) {//加載網(wǎng)絡(luò)圖片
                    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 {
                        //直接從網(wǎng)絡(luò)加載
                        bp = ImageDownloader.downloadImgFromUrl(path, imageView);
                    }
                } else {
                    //加載本地圖片
                    bp = loadImageFromLocal(path, imageView);
                }

                mBackLoopThreadSemaphore.release();//該任務(wù)執(zhí)行完成, 釋放并發(fā)加載圖片信號(hào)量
                if (bp != null) {
                    //更新UI
                    refreshBitmap(path, imageView, bp);
                    //加入內(nèi)存緩存
                    addBitmapToLruCache(BitmapUtils.toMD5(path), bp);
                }
            }
        };
    }

說明:如果磁盤緩存開關(guān)開啟掰邢,則查看磁盤是否有已下載的BP,否則走網(wǎng)絡(luò)下載伟阔。其他邏輯注釋已經(jīng)很明了尸变,不再贅述。

加載本地圖片:

    /**
     * 加載本地圖片
     *
     * @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() + "/";
    }

    /**
     * 將字符串轉(zhuǎn)成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);
    }

    /**
     * 最優(yōu)化String的構(gòu)建
     */
    public static String getString(Object... objects) {
        StringBuffer buffer = new StringBuffer();
        for (Object object : objects) {
            buffer.append(object);
        }
        return buffer.toString();
    }

    /**
     * 得到配置文件中的MetaData數(shù)據(jù)
     */
    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;
    }

    /**
     * 對(duì)double數(shù)據(jù)進(jìn)行截?cái)?     */
    public static String cutDouble0(double value) {
        DecimalFormat format = new DecimalFormat("##0");
        return format.format(value);
    }

    /**
     * 對(duì)double數(shù)據(jù)進(jìn)行截?cái)?     */
    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請(qǐng)求中的參數(shù)
     */
    public static String getPostParams(String preString, Object object) {
        String result = getString(preString, "{");
        boolean isFirst = true;
        // 獲取object對(duì)象對(duì)應(yīng)類中的所有屬性域
        Field[] fields = object.getClass().getDeclaredFields();
        for (Field field : fields) {
            // 對(duì)于每個(gè)屬性碱工,獲取屬性名
            String varName = field.getName();
            try {
                // 獲取原來的訪問控制權(quán)限
                boolean accessFlag = field.isAccessible();
                // 修改訪問控制權(quán)限
                field.setAccessible(true);
                // 獲取在對(duì)象object中屬性field對(duì)應(yīng)的對(duì)象中的變量
                Object value = field.get(object);
                // 生成參數(shù),其實(shí)跟get的URL中'?'后的參數(shù)字符串一致
                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"));
                    }
                }
                // 恢復(fù)訪問控制權(quán)限
                field.setAccessible(accessFlag);
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        }
        result += "}";
        return result;
    }

    /**
     * 獲取post請(qǐng)求中的參數(shù)
     */
    public static String getSimplePostParams(Object object) {
        String result = "";
        boolean isFirst = true;
        // 獲取object對(duì)象對(duì)應(yīng)類中的所有屬性域
        Field[] fields = object.getClass().getDeclaredFields();
        for (Field field : fields) {
            // 對(duì)于每個(gè)屬性娃承,獲取屬性名
            String varName = field.getName();
            try {
                // 獲取原來的訪問控制權(quán)限
                boolean accessFlag = field.isAccessible();
                // 修改訪問控制權(quán)限
                field.setAccessible(true);
                // 獲取在對(duì)象object中屬性field對(duì)應(yīng)的對(duì)象中的變量
                Object value = field.get(object);
                // 生成參數(shù),其實(shí)跟get的URL中'?'后的參數(shù)字符串一致
                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"));
                    }
                }
                // 恢復(fù)訪問控制權(quán)限
                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);
    }

    /**
     * 手機(jī)號(hào)合法性校驗(yàn)
     */
    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();
    }

    /**
     * 正則檢測(cè)
     *
     * @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();
    }

    /**
     * 將字符串轉(zhuǎn)成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();
    }

    /**
     * 實(shí)現(xiàn)文本復(fù)制功能
     */
    public static void copy(Context context, String content) {
        ClipboardManager cmb = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
        cmb.setText(content.trim());
    }

    /**
     * 實(shí)現(xiàn)粘貼功能
     */
    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*_");

        //進(jìn)行編碼
        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字符串中第一次出現(xiàn)處的索引奏夫。
                 */
                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字符串中第一次出現(xiàn)處的索引。
                 */
                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;
    }
}

總結(jié):這里只是實(shí)現(xiàn)了一個(gè)輕量型的圖片加載框架历筝,沒有考慮到可擴(kuò)展性(如Cache的設(shè)計(jì)酗昼、圖片的加載監(jiān)聽、BP處理接口等等),但足以讓你了解它的核心實(shí)現(xiàn)原理和方法梳猪。希望對(duì)讀者有幫助麻削!GitHub地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市春弥,隨后出現(xiàn)的幾起案子呛哟,更是在濱河造成了極大的恐慌,老刑警劉巖匿沛,帶你破解...
    沈念sama閱讀 206,602評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件扫责,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡逃呼,警方通過查閱死者的電腦和手機(jī)鳖孤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來抡笼,“玉大人苏揣,你說我怎么就攤上這事⊥埔觯” “怎么了平匈?”我有些...
    開封第一講書人閱讀 152,878評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長拾碌。 經(jīng)常有香客問我吐葱,道長,這世上最難降的妖魔是什么校翔? 我笑而不...
    開封第一講書人閱讀 55,306評(píng)論 1 279
  • 正文 為了忘掉前任弟跑,我火速辦了婚禮,結(jié)果婚禮上防症,老公的妹妹穿的比我還像新娘孟辑。我一直安慰自己,他們只是感情好蔫敲,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,330評(píng)論 5 373
  • 文/花漫 我一把揭開白布饲嗽。 她就那樣靜靜地躺著,像睡著了一般奈嘿。 火紅的嫁衣襯著肌膚如雪貌虾。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,071評(píng)論 1 285
  • 那天裙犹,我揣著相機(jī)與錄音尽狠,去河邊找鬼衔憨。 笑死,一個(gè)胖子當(dāng)著我的面吹牛袄膏,可吹牛的內(nèi)容都是我干的践图。 我是一名探鬼主播,決...
    沈念sama閱讀 38,382評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼沉馆,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼码党!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起斥黑,我...
    開封第一講書人閱讀 37,006評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤揖盘,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后心赶,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體扣讼,經(jīng)...
    沈念sama閱讀 43,512評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,965評(píng)論 2 325
  • 正文 我和宋清朗相戀三年缨叫,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了椭符。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,094評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡耻姥,死狀恐怖销钝,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情琐簇,我是刑警寧澤蒸健,帶...
    沈念sama閱讀 33,732評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站婉商,受9級(jí)特大地震影響似忧,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜丈秩,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,283評(píng)論 3 307
  • 文/蒙蒙 一盯捌、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蘑秽,春花似錦饺著、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至缀雳,卻和暖如春渡嚣,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評(píng)論 1 262
  • 我被黑心中介騙來泰國打工严拒, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留扬绪,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,536評(píng)論 2 354
  • 正文 我出身青樓裤唠,卻偏偏與公主長得像,于是被迫代替她去往敵國和親莹痢。 傳聞我的和親對(duì)象是個(gè)殘疾皇子种蘸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,828評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容