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