您的位置:首页 > 理论基础 > 计算机网络

Android-缓存网络图片(MVP模式)学习笔记

2016-08-18 13:41 441 查看

Android之缓存网络图片到内存中学习笔记

访问网络图片是很普遍的事了,在前面的学习中,我也写过了几次异步网上请求网络图片,但是没有缓存图片,那么我们也都知道,有时候访问一些经常访问的网络图片,如果不采取缓存的形式,那么对流量的消耗会非常大,所以,有必要的时候我们可以采取缓存图片的方式来解决流量消耗问题,下面就通过一个MVP模式的简单设计来
这里写代码片
讲解一下缓存网络图片。

整体的结构如下:



首先对于缓存图片来说,我们需要掌握一下几个小点:

1. 当图片没有缓存的时候,我们就要去通过网络异步加载图片

2. 当存在缓存的时候,我们就不能去通过网络异步加载图片,直接获取缓存中的图片

3. 当保存缓存图片时如何确定唯一命名规则,由于网络下载图片地址是不同的,所以可根据地址命名

4. 考虑到有些模拟器保存到内存卡的缓存中时,出现在cache中找到不缓存的图片时,我们将图片地址

中的非单词字符(除了a-z A-Z 0 - 9) 以外,全部去掉

5. 考虑缓存的内存是否够用,当缓存不够用时我们就要删除部分(这里我的代码是删除全部)已缓存的图片

6. 考虑在GridView中复用控件时,出现图片的闪烁已经图片错乱问题

model层:

ModelInter.java

//所有的获取数据功能都抽取到接口中,让其子类实现
public interface ModelInter {
void getData(OnCompleteListener listener, String path);
public interface OnCompleteListener {
void onComplete(byte[] bytes, String path);
}
}


ModelImp.java

//实现获取数据
public class ModelImp implements ModelInter {
@Override
public void getData(OnCompleteListener listener, String path) {
new MyAsyncTask(listener).execute(path);
}
}


view层:

ViewInter.java

//所有更新界面,以及得到数据的功能
public interface ViewInter<T> {
void getAdapterData(List<T> data);
}


MainActivity.java

//主界面启动,展示数据等。
public class MainActivity extends AppCompatActivity implements ViewInter<Bean.NewslistBean> {

private String path = "http://apis.baidu.com/txapi/mvtp/meinv?num=10";
private GridView mGrid;
private MyAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
new Presenter(this).load(path);
}

private void init() {
mGrid = (GridView) findViewById(R.id.mGrid);
}

private void showListData(List<Bean.NewslistBean> data) {
if (adapter == null) {
adapter = new MyAdapter(data, R.layout.list_item);
mGrid.setAdapter(adapter);
} else {

}
}
//通过presenter代理类为我们提供数据
@Override
public void getAdapterData(List<Bean.NewslistBean> data) {
showListData(data);
}
}


presenter层:

presenter.java

//为视图层提供所需要的数据
public class Presenter {
private ViewInter viewInter;
private ModelInter modelInter;
public Presenter(ViewInter viewInter){
this.viewInter = viewInter;
modelInter  = new ModelImp();
}
//当view需要数据时,调用该方法就可以获取数据了
public void load(final String path){
//传递了匿名内部类,实现获取数据的方法
modelInter.getData(new ModelInter.OnCompleteListener() {
@Override
public void onComplete(byte[] bytes, String path) {
//通过gson解析数据
Bean bean = new Gson().fromJson(new String(bytes), Bean.class);
if(bean != null)
viewInter.getAdapterData(bean.getNewslist());
}
},path);
}
}


adapter:

MyAdapter.java

//自定义适配器类,不详细解说,需要了解的查看我相关知识点即可
public class MyAdapter extends MyBaseAdapter<Bean.NewslistBean> {

public MyAdapter(List<Bean.NewslistBean> data, int layout) {
super(data,layout);
}

@Override
public void setData(ViewHolder holder, Bean.NewslistBean t) {
//初始化上下文
if(MyUtils.context == null)
MyUtils.context = holder.getContext();
//如果不存在缓存,就加载网络图片
if(!MyUtils.isCacheImage(t.getPicUrl())){
if(MyUtils.isEnoughForCache())//判断缓存空间是否足够
//加载请求网络图片
holder.setImageURL(R.id.mImg,t.getPicUrl());
else{
Toast.makeText(MyUtils.context,"空间不足了,缓存不了图片了,正在请求删除图片...",Toast.LENGTH_SHORT).show();
//删除已经缓存的图片
MyUtils.deletePic(MyUtils.context.getExternalCacheDir());
}
}else {
//这里可以通过log日志判断数据是怎样获取的
Log.i("IT_Real", "setData: 直接拿数据了,没用通过缓存拿数据");
//得到缓存路径下的文件
String fileName = t.getPicUrl().replaceAll("\\W","");
fileName = fileName + t.getPicUrl().substring(t.getPicUrl().lastIndexOf("."),t.getPicUrl().length());
//通过文件位置获取一个Bitmap
Bitmap bm = BitmapFactory.decodeFile(holder.getContext().getExternalCacheDir() + "/" + fileName);
//设置图片
holder.setImageBitmap(R.id.mImg,bm);
}
}
}


MyBaseAdapter.java

public abstract class MyBaseAdapter<T> extends BaseAdapter {
protected List<T> data;
protected int layout;
public MyBaseAdapter(List<T> data,int layout){
this.data = data;
this.layout = layout;
}
@Override
public int getCount() {
return data == null ? 0 : data.size();
}

@Override
public Object getItem(int position) {
return data.get(position);
}

@Override
public long getItemId(int position) {
return position;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = ViewHolder.getHolder(convertView,parent,position, R.layout.list_item);
setData(holder,data.get(position));
return holder.getConvertView();
}
public abstract void setData(ViewHolder holder,T t);
}


ViewHolder.java

public class ViewHolder {
private int position;
private SparseArray<View> array;
private View convertView;
private Context context;

private ViewHolder(ViewGroup parent, int position, int layout) {
this.position = position;
this.context = parent.getContext();
convertView = LayoutInflater.from(parent.getContext()).inflate(layout, null);
convertView.setTag(this);
array = new SparseArray<>();
}

public static ViewHolder getHolder(View convertView, ViewGroup parent, int position, int layout) {
if (convertView == null) {
return new ViewHolder(parent, position, layout);
} else {
ViewHolder holder = (ViewHolder) convertView.getTag();
holder.position = position;
return holder;
}
}

public <T extends View> T getView(int viewId) {
View view = array.get(viewId);
if (view == null) {
view = convertView.findViewById(viewId);
array.put(viewId, view);
}
return (T) view;
}

public Context getContext() {
return context;
}

public View getConvertView() {
return convertView;
}

public ViewHolder setText(int viewId, String data) {
TextView tv = getView(viewId);
tv.setText(data);
return this;
}

public ViewHolder setImageResource(int viewId, int resId) {
ImageView img = getView(viewId);
img.setImageResource(resId);
return this;
}
public ViewHolder setImageURL(int viewId, String url){
final ImageView image = getView(viewId);
//防止图片闪烁,每次加载GridView时,设置为空白界面
image.setImageBitmap(null);
new MyAsyncTask(new ImageCallBack(image,url,this.context)).execute(url);
Log.i("IT_Real", "setImageURL: 网络请求了");
return this;
}
public ViewHolder setImageBitmap(int viewId, Bitmap bm) {
ImageView img = getView(viewId);
img.setImageBitmap(bm);
return this;
}
}


utils:

MyAsyncTask.java

//异步网络请求数据
public class MyAsyncTask extends AsyncTask<String,Void,byte[]> {
private String path;
private ModelInter.OnCompleteListener listener;
public MyAsyncTask(ModelInter.OnCompleteListener listener){
this.listener = listener;
}

/**
* 异步请求网络数据
*/
@Override
protected byte[] doInBackground(String... params) {
path = params[0];
return getBytes(path);
}
/**
* 获取到数据,然后接口回调传递数据
*/
@Override
protected void onPostExecute(byte[] bytes) {
super.onPostExecute(bytes);
if(listener != null) {
listener.onComplete(bytes,path);
}
}

/**
* 网络请求的数据
*/
private byte[] getBytes(String path){
try {
URL url = new URL(path);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setRequestProperty("apikey","百度APIStore的个人apikey,没有的可以自己去注册一个,然后获取美女图片json数据");
connection.setConnectTimeout(5000);
connection.setReadTimeout(5000);
connection.connect();
if(connection.getResponseCode() == HttpURLConnection.HTTP_OK){
BufferedInputStream bis = new BufferedInputStream(connection.getInputStream());
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int len = -1;
byte[] bytes = new byte[1024 * 8];
while(-1 != (len = bis.read(bytes))){
baos.write(bytes,0,len);
}
byte[] result = baos.toByteArray();
bis.close();
baos.close();
return result;
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}


MyUtils.java

//所有自己需要实现的功能工具类

public class MyUtils {
public static  Context context;
private static StatFs statFs;

/**
*  判断是否存在缓存图片
* @param path 图片路径
* @return
*/
public static boolean isCacheImage(String path){
String fileName = path.replaceAll("\\W","");
fileName = fileName + path.substring(path.lastIndexOf("."),path.length());
File file = new File(context.getExternalCacheDir(),fileName);
Log.i("IT_Real", "isCacheImage: " + "file is exists" + file.exists() + "  ->" + isMount());
return isMount() && file.exists();
}

/**
* 判断内存卡是否具有读写权限
* @return true有,false没有
*/
public static boolean isMount(){
return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
}

/**
* 判断缓存空间是否足够
* @return
*/
public static boolean isEnoughForCache(){
if(statFs == null)
statFs = new StatFs(Environment.getExternalStorageDirectory().getPath());
int availableBlocks = statFs.getAvailableBlocks();
int blockSize = statFs.getBlockSize();
//如果可用空间小于10兆,就提示用户内存不足
return availableBlocks  * blockSize>= 10 * 1024 * 1024;
}

/**
* 递归删除缓存的文件
* @param file 文件路径
*/
public static void deletePic(File file){
if(file == null) return;
if(!file.isFile()){//如果不是文件就遍历
File[] files = file.listFiles();
if(file != null){
for (File file1 : files) {
deletePic(file1);
}
}
}else {
file.delete();
Log.i("IT_Real", "deletePic: 删除了..");
}

}
}


callback:

ImageCallBack.java

//图片回调接口实现类

public class ImageCallBack implements ModelInter.OnCompleteListener {
private ImageView image;
private String path;
private Context context;

public ImageCallBack(ImageView image, String path, Context context) {
this.image = image;
this.path = path;
this.context = context;
}

/**
* 得到网络请求图片后的图片数据
* @param bytes
* @param path
*/
@Override
public void onComplete(byte[] bytes, String path) {
//判断每次加载的网络数据是否和当前图片地址的加载数据,不相同则不设置此图片,防止图片数据错乱
if (bytes != null && this.path.equals(path)) {
Bitmap bm = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
image.setImageBitmap(bm);
//开始保存图片
saveCache(context.getExternalCacheDir(), bm, path);

}
}

/**
* 保存缓存的图片
* @param cacheDir 缓存的路径
* @param bm
* @param path 文件的名字
*/
private void saveCache(File cacheDir, Bitmap bm, String path) {
//去掉所有非单词的字符
String fileName = path.replaceAll("\\W","");
fileName = fileName + path.substring(path.lastIndexOf("."),path.length());
try {
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(new File(cacheDir, fileName)));
bm.compress(Bitmap.CompressFormat.JPEG, 100, bos);
bos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}


整体的功能已经实现了,这个小案例中,我使用了MVP模式、万能通用适配器,关于MVP模式和万能适配器,这里都不做介绍了,不了解的可用查看我博客中的相关知识点,所有涉及到的主要问题都已经注释了,如果有不懂的可用留言下方,我会在第一时间回复。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐