从今天开始我们来学习框架思维,努力成为一个架构师,学习的开始先从别人的代码中获取这种思维,看看大牛们是怎么实现的。在分析代码的过程的同时也是对知识的巩固和对自己分析能力和知识层面的提高。本篇参考于网络众多博文,在这十分感谢,用到的知识点索引博客还请博主谅解......等等巴拉巴拉......
我们先从afinal框架说起(下载传送门:https://github.com/yangfuhai/afinal),然后再说xUtils框架,由于xUtils框架是在afinal框架的基础上做了优化,对于我们以后对xUtils框架的分析就会变得得心应手。限于技术水平有限,仅仅作为学习笔记,大神勿喷。
说明:该源码是0.5v版本的。
一、源码目录结构
可以看到对外开放的四大模块:
1.##FinalActivity:
* 完全注解方式就可以进行UI绑定和事件绑定
* 无需findViewById和setClickListener等
2.##FinalDb:
* 简化数据库的增删改查操作(支持一对多,多对一)
3.##FinalBitmap:
* 强大的图像处理(加载网络图片,可以设置线程数、缓存、是否开启动画)
* 采用lru(近期最少使用)算法进行内存管理防止oom
4.##FinalHttp:
* 网络数据传递,支持ajax方式加载。(上传、下载文件或数据)。
其它的文件包下分别是:
- annotation 注释包 —— UI 绑定和数据库注解
- bitmap 图像处理包 —— 对图像的配置
- core 核心代码包 —— 数据算法队列处理的核心处理包
- db 数据库包 —— 数据库与数据表的操作
- exception 异常捕获包 —— 捕获异常操作
- http 网络访问包 —— 对网络数据的处理
- utils 工具包 —— 通用操作的封装
PS:它们的使用将会在第三部分一一说明。
二、权限设置
按照官方说明需要在AndroidManifest.xml文件中设置以下两个权限:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
分别是访问网络权限和访问SDcard
三、How & Why
- FinalActivity
How:
你需要将你的activity继承自FinalActivity,在UI布局的xml文件中设置所需的id,使用ViewInject对该控件进行注解,其对应的点击事件就是你所设定的方法名称,接下来你只需要实现这个方法名的具体操作就行了,另外afinal提供了两种方式进行事件绑定,一种是上述,另外一种,afinal提供了两个公有方法进行调用注解。PS:注解相关知识可以参考相关博客,这里感谢 Android通过注解初始化View_roykfw的博客-继承自FinalActivity
package com.example.demo;
import java.util.ArrayList;
import android.graphics.Color;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import net.tsz.afinal.FinalActivity;
import net.tsz.afinal.annotation.view.Select;
import net.tsz.afinal.annotation.view.ViewInject;
public class YouActivity extends FinalActivity {
ArrayList<String> dataList = new ArrayList<String>();
// 注解实现事件关联
@ViewInject(id = R.id.text_view) private TextView textView;
@ViewInject(id = R.id.item_text_view) private TextView itemTextView;
@ViewInject(id = R.id.button, click = "buttonClick") private Button button;
@ViewInject(id = R.id.list_view, itemClick = "itemClick",
select = @Select(selected = "select", noSelected = "noSelect")) private ListView listView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.you_activity_layout);
for (int i = 0; i < 3; i++) {
dataList.add(String.valueOf(i));
}
listView.setAdapter(new BaseAdapter() {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = LayoutInflater.from(YouActivity.this).inflate(R.layout.listview_item_layout, null);
itemTextView = (TextView) convertView.findViewById(R.id.item_text_view);
}
itemTextView.setText(dataList.get(position));
return convertView;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public Object getItem(int position) {
return dataList.get(position);
}
@Override
public int getCount() {
return dataList.size();
}
});
}
/ 直接写事件,省去findViewById和setListener
public void buttonClick(View view) {
textView.setText("按钮被点击了");
}
public void itemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
Toast.makeText(YouActivity.this, dataList.get(arg2) + "被点击", Toast.LENGTH_SHORT).show();
}
public void select(AdapterView<?> arg0, View arg1, int arg2,long arg3) {
button.setBackgroundColor(Color.BLUE);
}
public void noSelect(AdapterView<?> arg0) {
button.setBackgroundColor(0x5d5d5d);
}
}
2.使用第三方框架时注入
如:ActionBarShelock
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.you_activity_layout);
FinalActivity.initInjectedView(this);
}
3.在fragment中使用
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View viewRoot = inflater.inflate(R.layout.map_frame, container, false);
FinalActivity.initInjectedView(this,viewRoot);
}
Why:
我们发现了@ViewInject和@Select 索引进去发现
package net.tsz.afinal.annotation.view;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewInject {//自定义注解属性
public int id();
public String click() default "";
public String longClick() default "";
public String itemClick() default "";
public String itemLongClick() default "";
public Select select() default @Select(selected="") ;
} 这是自定义注解,UI绑定是通过反射的机制找到开发者设定id并与事件绑定,绑定方法也是通过反射获取的,我们在开发的时候经常用到@Override 、@Deprecated,这是内部注解,我们通过 @interface “你自定义的注解名称”可以自定义你的注解,关于自定义注解的问题参见上面的飞机票或者点击:
这里
注解的问题就先聊到这,我们继续:看看具体的反射实现,看注释:
package net.tsz.afinal;
import java.lang.reflect.Field;
import net.tsz.afinal.annotation.view.EventListener;
import net.tsz.afinal.annotation.view.Select;
import net.tsz.afinal.annotation.view.ViewInject;
import android.app.Activity;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.widget.AbsListView;
public abstract class FinalActivity extends Activity {
public void setContentView(int layoutResID) {
super.setContentView(layoutResID);
initInjectedView(this);
}
public void setContentView(View view, LayoutParams params) {
super.setContentView(view, params);
initInjectedView(this);
}
public void setContentView(View view) {
super.setContentView(view);
initInjectedView(this);
}
public static void initInjectedView(Activity activity){
initInjectedView(activity, activity.getWindow().getDecorView());//获取window最顶层View
}
public static void initInjectedView(Object injectedSource,View sourceView){
Field[] fields = injectedSource.getClass().getDeclaredFields();//获取injectedSource里面所有的字段属性(public protect private)
if(fields!=null && fields.length>0){
// 遍历所有字段并根据设置,设置相应的点击监听
for(Field field : fields){
try {
field.setAccessible(true);//在类的外面获取此类的私有成员变量的value时要设置此
if(field.get(injectedSource)!= null )
continue;
ViewInject viewInject = field.getAnnotation(ViewInject.class);//过滤获取有ViewInject注解的所有属性
if(viewInject!=null){
int viewId = viewInject.id();
field.set(injectedSource,sourceView.findViewById(viewId));//获取注解id并绑定
设置监听
setListener(injectedSource,field,viewInject.click(),Method.Click);
setListener(injectedSource,field,viewInject.longClick(),Method.LongClick);
setListener(injectedSource,field,viewInject.itemClick(),Method.ItemClick);
setListener(injectedSource,field,viewInject.itemLongClick(),Method.itemLongClick);
Select select = viewInject.select();
if(!TextUtils.isEmpty(select.selected())){
setViewSelectListener(injectedSource,field,select.selected(),select.noSelected());
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
private static void setViewSelectListener(Object injectedSource,Field field,String select,String noSelect)throws Exception{
Object obj = field.get(injectedSource);
if(obj instanceof View){
((AbsListView)obj).setOnItemSelectedListener(new EventListener(injectedSource).select(select).noSelect(noSelect));
}
}
private static void setListener(Object injectedSource,Field field,String methodName,Method method)throws Exception{
if(methodName == null || methodName.trim().length() == 0)
return;
Object obj = field.get(injectedSource);
switch (method) {
case Click:
if(obj instanceof View){
((View)obj).setOnClickListener(new EventListener(injectedSource).click(methodName));
}
break;
case ItemClick:
if(obj instanceof AbsListView){
((AbsListView)obj).setOnItemClickListener(new EventListener(injectedSource).itemClick(methodName));
}
break;
case LongClick:
if(obj instanceof View){
((View)obj).setOnLongClickListener(new EventListener(injectedSource).longClick(methodName));
}
break;
case itemLongClick:
if(obj instanceof AbsListView){
((AbsListView)obj).setOnItemLongClickListener(new EventListener(injectedSource).itemLongClick(methodName));
}
break;
default:
break;
}
}
public enum Method{
Click,LongClick,ItemClick,itemLongClick
}
} 通过三个构造函数我们发现当我们继承FinalActivity时在setContentView(R.layout.you_activity_layout);之后就进行了注入,它另外还提供了两个静态的公有方法支持第三方框架的注入,这个在上面已经说明。
- FinalBitmap
How:
该类的使用非常简单,基本就一行
// mFinalBitmap.configBitmapLoadThreadSize(4);
// mFinalBitmap.configDiskCacheSize(50);
// mFinalBitmap.configMemoryCachePercent(0.6f);
// .
// .
// .
// . 等等其他设置
FinalBitmap mFinalBitmap = FinalBitmap.create(YouActivity.this);
mFinalBitmap.display(yourImageView, "https://ss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superplus/img/logo_white.png");
在加载网络图片之前你可以通过设置相关参数来自定义显示图像的配置
Why:
我们看下源码,里面包含的方法有对应的注释说明
package net.tsz.afinal;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import net.tsz.afinal.bitmap.core.BitmapCache;
import net.tsz.afinal.bitmap.core.BitmapDisplayConfig;
import net.tsz.afinal.bitmap.core.BitmapProcess;
import net.tsz.afinal.bitmap.display.Displayer;
import net.tsz.afinal.bitmap.display.SimpleDisplayer;
import net.tsz.afinal.bitmap.download.Downloader;
import net.tsz.afinal.bitmap.download.SimpleDownloader;
import net.tsz.afinal.core.AsyncTask;
import net.tsz.afinal.utils.Utils;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.view.View;
import android.widget.ImageView;
public class FinalBitmap {
private FinalBitmapConfig mConfig;
private BitmapCache mImageCache;
private BitmapProcess mBitmapProcess;
private boolean mExitTasksEarly = false;
private boolean mPauseWork = false;
private final Object mPauseWorkLock = new Object();
private Context mContext;
private boolean mInit = false ;
private ExecutorService bitmapLoadAndDisplayExecutor;
private static FinalBitmap mFinalBitmap;
// config method start
private FinalBitmap(Context context) {
mContext = context;
mConfig = new FinalBitmapConfig(context);
configDiskCachePath(Utils.getDiskCacheDir(context, "afinalCache").getAbsolutePath());//配置缓存路径
configDisplayer(new SimpleDisplayer());//配置显示器
configDownlader(new SimpleDownloader());//配置下载器
}
/**
* 创建finalbitmap
* @param ctx
* @return
* 单例模式
*/
public static synchronized FinalBitmap create(Context ctx){
if(mFinalBitmap == null){
mFinalBitmap = new FinalBitmap(ctx.getApplicationContext());
}
return mFinalBitmap;
}
/**
* 设置图片正在加载的时候显示的图片
* @param bitmap - Bitmap
*/
public FinalBitmap configLoadingImage(Bitmap bitmap) {
mConfig.defaultDisplayConfig.setLoadingBitmap(bitmap);
return this;
}
/**
* 设置图片正在加载的时候显示的图片
* @param resId - int
*/
public FinalBitmap configLoadingImage(int resId) {
mConfig.defaultDisplayConfig.setLoadingBitmap(BitmapFactory.decodeResource(mContext.getResources(), resId));
return this;
}
/**
* 设置图片加载失败时候显示的图片
* @param bitmap - Bitmap
*/
public FinalBitmap configLoadfailImage(Bitmap bitmap) {
mConfig.defaultDisplayConfig.setLoadfailBitmap(bitmap);
return this;
}
/**
* 设置图片加载失败时候显示的图片
* @param resId - int
*/
public FinalBitmap configLoadfailImage(int resId) {
mConfig.defaultDisplayConfig.setLoadfailBitmap(BitmapFactory.decodeResource(mContext.getResources(), resId));
return this;
}
/**
* 配置默认图片的小的高度
* @param bitmapHeight
*/
public FinalBitmap configBitmapMaxHeight(int bitmapHeight){
mConfig.defaultDisplayConfig.setBitmapHeight(bitmapHeight);
return this;
}
/**
* 配置默认图片的小的宽度
* @param bitmapHeight
*/
public FinalBitmap configBitmapMaxWidth(int bitmapWidth){
mConfig.defaultDisplayConfig.setBitmapWidth(bitmapWidth);
return this;
}
/**
* 设置下载器,比如通过ftp或者其他协议去网络读取图片的时候可以设置这项
* @param downlader
* @return
*/
public FinalBitmap configDownlader(Downloader downlader){
mConfig.downloader = downlader;
return this;
}
/**
* 设置显示器,比如在显示的过程中显示动画等
* @param displayer
* @return
*/
public FinalBitmap configDisplayer(Displayer displayer){
mConfig.displayer = displayer;
return this;
}
/**
* 配置磁盘缓存路径
* @param strPath
* @return
*/
public FinalBitmap configDiskCachePath(String strPath){
if(!TextUtils.isEmpty(strPath)){
mConfig.cachePath = strPath;
}
return this;
}
/**
* 配置内存缓存大小 大于2MB以上有效
* @param size 缓存大小
*/
public FinalBitmap configMemoryCacheSize(int size){
mConfig.memCacheSize = size;
return this;
}
/**
* 设置应缓存的在APK总内存的百分比,优先级大于configMemoryCacheSize
* @param percent 百分比,值的范围是在 0.05 到 0.8之间
*/
public FinalBitmap configMemoryCachePercent(float percent){
mConfig.memCacheSizePercent = percent;
return this;
}
/**
* 设置磁盘缓存大小 5MB 以上有效
* @param size
*/
public FinalBitmap configDiskCacheSize(int size){
mConfig.diskCacheSize = size;
return this;
}
/**
* 设置加载图片的线程并发数量
* @param size
*/
public FinalBitmap configBitmapLoadThreadSize(int size){
if(size >= 1)
mConfig.poolSize = size;
return this;
}
/**
* 配置是否立即回收图片资源
* @param recycleImmediately
* @return
*/
public FinalBitmap configRecycleImmediately(boolean recycleImmediately){
mConfig.recycleImmediately = recycleImmediately;
return this;
}
/**
* 初始化finalBitmap
* @return
*/
private FinalBitmap init(){
if(!mInit){
BitmapCache.ImageCacheParams imageCacheParams = new BitmapCache.ImageCacheParams(mConfig.cachePath);
if(mConfig.memCacheSizePercent>0.05 && mConfig.memCacheSizePercent<0.8){
imageCacheParams.setMemCacheSizePercent(mContext, mConfig.memCacheSizePercent);
}else{
if(mConfig.memCacheSize > 1024 * 1024 * 2){
imageCacheParams.setMemCacheSize(mConfig.memCacheSize);
}else{
//设置默认的内存缓存大小
imageCacheParams.setMemCacheSizePercent(mContext, 0.3f);
}
}
if(mConfig.diskCacheSize > 1024 * 1024 * 5)
imageCacheParams.setDiskCacheSize(mConfig.diskCacheSize);
imageCacheParams.setRecycleImmediately(mConfig.recycleImmediately);
//init Cache
mImageCache = new BitmapCache(imageCacheParams);
//init Executors
bitmapLoadAndDisplayExecutor = Executors.newFixedThreadPool(mConfig.poolSize,new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
// 设置线程的优先级别,让线程先后顺序执行(级别越高,抢到cpu执行的时间越多)
t.setPriority(Thread.NORM_PRIORITY - 1);
return t;
}
});
//init BitmapProcess
mBitmapProcess = new BitmapProcess(mConfig.downloader,mImageCache);
mInit = true ;
}
return this;
}
// config method end
public void display(View imageView,String uri){
doDisplay(imageView,uri,null);
}
public void display(View imageView,String uri,int imageWidth,int imageHeight){
BitmapDisplayConfig displayConfig = configMap.get(imageWidth+"_"+imageHeight);
if(displayConfig==null){
displayConfig = getDisplayConfig();
displayConfig.setBitmapHeight(imageHeight);
displayConfig.setBitmapWidth(imageWidth);
configMap.put(imageWidth+"_"+imageHeight, displayConfig);
}
doDisplay(imageView,uri,displayConfig);
}
public void display(View imageView,String uri,Bitmap loadingBitmap){
BitmapDisplayConfig displayConfig = configMap.get(String.valueOf(loadingBitmap));
if(displayConfig==null){
displayConfig = getDisplayConfig();
displayConfig.setLoadingBitmap(loadingBitmap);
configMap.put(String.valueOf(loadingBitmap), displayConfig);
}
doDisplay(imageView,uri,displayConfig);
}
public void display(View imageView,String uri,Bitmap loadingBitmap,Bitmap laodfailBitmap){
BitmapDisplayConfig displayConfig = configMap.get(String.valueOf(loadingBitmap)+"_"+String.valueOf(laodfailBitmap));
if(displayConfig==null){
displayConfig = getDisplayConfig();
displayConfig.setLoadingBitmap(loadingBitmap);
displayConfig.setLoadfailBitmap(laodfailBitmap);
configMap.put(String.valueOf(loadingBitmap)+"_"+String.valueOf(laodfailBitmap), displayConfig);
}
doDisplay(imageView,uri,displayConfig);
}
public void display(View imageView,String uri,int imageWidth,int imageHeight,Bitmap loadingBitmap,Bitmap laodfailBitmap){
BitmapDisplayConfig displayConfig = configMap.get(imageWidth+"_"+imageHeight+"_"+String.valueOf(loadingBitmap)+"_"+String.valueOf(laodfailBitmap));
if(displayConfig==null){
displayConfig = getDisplayConfig();
displayConfig.setBitmapHeight(imageHeight);
displayConfig.setBitmapWidth(imageWidth);
displayConfig.setLoadingBitmap(loadingBitmap);
displayConfig.setLoadfailBitmap(laodfailBitmap);
configMap.put(imageWidth+"_"+imageHeight+"_"+String.valueOf(loadingBitmap)+"_"+String.valueOf(laodfailBitmap), displayConfig);
}
doDisplay(imageView,uri,displayConfig);
}
public void display(View imageView,String uri,BitmapDisplayConfig config){
doDisplay(imageView,uri,config);
}
private void doDisplay(View imageView, String uri, BitmapDisplayConfig displayConfig) {
if(!mInit ){
init();
}
if (TextUtils.isEmpty(uri) || imageView == null) {
return;
}
if(displayConfig == null)
displayConfig = mConfig.defaultDisplayConfig;
Bitmap bitmap = null;
if (mImageCache != null) {
bitmap = mImageCache.getBitmapFromMemoryCache(uri);
}
if (bitmap != null) {
if(imageView instanceof ImageView){
((ImageView)imageView).setImageBitmap(bitmap);
}else{
imageView.setBackgroundDrawable(new BitmapDrawable(bitmap));
}
}else if (checkImageTask(uri, imageView)) {
final BitmapLoadAndDisplayTask task = new BitmapLoadAndDisplayTask(imageView, displayConfig );
//设置默认图片
final AsyncDrawable asyncDrawable = new AsyncDrawable(mContext.getResources(), displayConfig.getLoadingBitmap(), task);
if(imageView instanceof ImageView){
((ImageView)imageView).setImageDrawable(asyncDrawable);
}else{
imageView.setBackgroundDrawable(asyncDrawable);
}
task.executeOnExecutor(bitmapLoadAndDisplayExecutor, uri);
}
}
private HashMap<String, BitmapDisplayConfig> configMap = new HashMap<String, BitmapDisplayConfig>();
private BitmapDisplayConfig getDisplayConfig(){
BitmapDisplayConfig config = new BitmapDisplayConfig();
config.setAnimation(mConfig.defaultDisplayConfig.getAnimation());
config.setAnimationType(mConfig.defaultDisplayConfig.getAnimationType());
config.setBitmapHeight(mConfig.defaultDisplayConfig.getBitmapHeight());
config.setBitmapWidth(mConfig.defaultDisplayConfig.getBitmapWidth());
config.setLoadfailBitmap(mConfig.defaultDisplayConfig.getLoadfailBitmap());
config.setLoadingBitmap(mConfig.defaultDisplayConfig.getLoadingBitmap());
return config;
}
private void clearCacheInternalInBackgroud() {
if (mImageCache != null) {
mImageCache.clearCache();
}
}
private void clearDiskCacheInBackgroud(){
if (mImageCache != null) {
mImageCache.clearDiskCache();
}
}
private void clearCacheInBackgroud(String key){
if (mImageCache != null) {
mImageCache.clearCache(key);
}
}
private void clearDiskCacheInBackgroud(String key){
if (mImageCache != null) {
mImageCache.clearDiskCache(key);
}
}
/**
* 执行过此方法后,FinalBitmap的缓存已经失效,建议通过FinalBitmap.create()获取新的实例
* @author fantouch
*/
private void closeCacheInternalInBackgroud() {
if (mImageCache != null) {
mImageCache.close();
mImageCache = null;
mFinalBitmap = null;
}
}
/**
* 网络加载bitmap
* @param data
* @return
*/
private Bitmap processBitmap(String uri,BitmapDisplayConfig config) {
if (mBitmapProcess != null) {
return mBitmapProcess.getBitmap(uri,config);
}
return null;
}
/**
* 从缓存(内存缓存和磁盘缓存)中直接获取bitmap,注意这里有io操作,最好不要放在ui线程执行
* @param key
* @return
*/
public Bitmap getBitmapFromCache(String key){
Bitmap bitmap = getBitmapFromMemoryCache(key);
if(bitmap == null)
bitmap = getBitmapFromDiskCache(key);
return bitmap;
}
/**
* 从内存缓存中获取bitmap
* @param key
* @return
*/
public Bitmap getBitmapFromMemoryCache(String key){
return mImageCache.getBitmapFromMemoryCache(key);
}
/**
* 从磁盘缓存中获取bitmap,,注意这里有io操作,最好不要放在ui线程执行
* @param key
* @return
*/
public Bitmap getBitmapFromDiskCache(String key){
return getBitmapFromDiskCache(key,null);
}
public Bitmap getBitmapFromDiskCache(String key,BitmapDisplayConfig config){
return mBitmapProcess.getFromDisk(key, config);
}
public void setExitTasksEarly(boolean exitTasksEarly) {
mExitTasksEarly = exitTasksEarly;
}
/**
* activity onResume的时候调用这个方法,让加载图片线程继续
*/
public void onResume(){
setExitTasksEarly(false);
}
/**
* activity onPause的时候调用这个方法,让线程暂停
*/
public void onPause() {
setExitTasksEarly(true);
}
/**
* activity onDestroy的时候调用这个方法,释放缓存
* 执行过此方法后,FinalBitmap的缓存已经失效,建议通过FinalBitmap.create()获取新的实例
*
* @author fantouch
*/
public void onDestroy() {
closeCache();
}
/**
* 清除所有缓存(磁盘和内存)
*/
public void clearCache() {
new CacheExecutecTask().execute(CacheExecutecTask.MESSAGE_CLEAR);
}
/**
* 根据key清除指定的内存缓存
* @param key
*/
public void clearCache(String key) {
new CacheExecutecTask().execute(CacheExecutecTask.MESSAGE_CLEAR_KEY,key);
}
/**
* 清除缓存
*/
public void clearMemoryCache() {
if(mImageCache!=null)
mImageCache.clearMemoryCache();
}
/**
* 根据key清除指定的内存缓存
* @param key
*/
public void clearMemoryCache(String key) {
if(mImageCache!=null)
mImageCache.clearMemoryCache(key);
}
/**
* 清除磁盘缓存
*/
public void clearDiskCache() {
new CacheExecutecTask().execute(CacheExecutecTask.MESSAGE_CLEAR_DISK);
}
/**
* 根据key清除指定的内存缓存
* @param key
*/
public void clearDiskCache(String key) {
new CacheExecutecTask().execute(CacheExecutecTask.MESSAGE_CLEAR_KEY_IN_DISK,key);
}
/**
* 关闭缓存
* 执行过此方法后,FinalBitmap的缓存已经失效,建议通过FinalBitmap.create()获取新的实例
* @author fantouch
*/
public void closeCache() {
new CacheExecutecTask().execute(CacheExecutecTask.MESSAGE_CLOSE);
}
/**
* 退出正在加载的线程,程序退出的时候调用词方法
* @param exitTasksEarly
*/
public void exitTasksEarly(boolean exitTasksEarly) {
mExitTasksEarly = exitTasksEarly;
if(exitTasksEarly)
pauseWork(false);//让暂停的线程结束
}
/**
* 暂停正在加载的线程,监听listview或者gridview正在滑动的时候条用词方法
* @param pauseWork true停止暂停线程,false继续线程
*/
public void pauseWork(boolean pauseWork) {
synchronized (mPauseWorkLock) {
mPauseWork = pauseWork;
if (!mPauseWork) {
mPauseWorkLock.notifyAll();
}
}
}
private static BitmapLoadAndDisplayTask getBitmapTaskFromImageView(View imageView) {
if (imageView != null) {
Drawable drawable = null;
if(imageView instanceof ImageView){
drawable = ((ImageView)imageView).getDrawable();
}else{
drawable = imageView.getBackground();
}
if (drawable instanceof AsyncDrawable) {
final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
return asyncDrawable.getBitmapWorkerTask();
}
}
return null;
}
/**
* 检测 imageView中是否已经有线程在运行
* @param data
* @param imageView
* @return true 没有 false 有线程在运行了
*/
public static boolean checkImageTask(Object data, View imageView) {
final BitmapLoadAndDisplayTask bitmapWorkerTask = getBitmapTaskFromImageView(imageView);
if (bitmapWorkerTask != null) {
final Object bitmapData = bitmapWorkerTask.data;
if (bitmapData == null || !bitmapData.equals(data)) {
bitmapWorkerTask.cancel(true);
} else {
// 同一个线程已经在执行
return false;
}
}
return true;
}
private static class AsyncDrawable extends BitmapDrawable {
private final WeakReference<BitmapLoadAndDisplayTask> bitmapWorkerTaskReference;
public AsyncDrawable(Resources res, Bitmap bitmap,BitmapLoadAndDisplayTask bitmapWorkerTask) {
super(res, bitmap);
bitmapWorkerTaskReference = new WeakReference<BitmapLoadAndDisplayTask>(
bitmapWorkerTask);
}
public BitmapLoadAndDisplayTask getBitmapWorkerTask() {
return bitmapWorkerTaskReference.get();
}
}
private class CacheExecutecTask extends AsyncTask<Object, Void, Void> {
public static final int MESSAGE_CLEAR = 1;
public static final int MESSAGE_CLOSE = 2;
public static final int MESSAGE_CLEAR_DISK = 3;
public static final int MESSAGE_CLEAR_KEY = 4;
public static final int MESSAGE_CLEAR_KEY_IN_DISK = 5;
@Override
protected Void doInBackground(Object... params) {
switch ((Integer) params[0]) {
case MESSAGE_CLEAR:
clearCacheInternalInBackgroud();
break;
case MESSAGE_CLOSE:
closeCacheInternalInBackgroud();
break;
case MESSAGE_CLEAR_DISK:
clearDiskCacheInBackgroud();
break;
case MESSAGE_CLEAR_KEY:
clearCacheInBackgroud(String.valueOf(params[1]));
break;
case MESSAGE_CLEAR_KEY_IN_DISK:
clearDiskCacheInBackgroud(String.valueOf(params[1]));
break;
}
return null;
}
}
/**
* bitmap下载显示的线程
* @author michael yang
*/
private class BitmapLoadAndDisplayTask extends AsyncTask<Object, Void, Bitmap> {
private Object data;
private final WeakReference<View> imageViewReference;
private final BitmapDisplayConfig displayConfig;
public BitmapLoadAndDisplayTask(View imageView,BitmapDisplayConfig config) {
imageViewReference = new WeakReference<View>(imageView);
displayConfig = config;
}
@Override
protected Bitmap doInBackground(Object... params) {
data = params[0];
final String dataString = String.valueOf(data);
Bitmap bitmap = null;
synchronized (mPauseWorkLock) {
while (mPauseWork && !isCancelled()) {
try {
mPauseWorkLock.wait();
} catch (InterruptedException e) {
}
}
}
if (bitmap == null && !isCancelled()&& getAttachedImageView() != null && !mExitTasksEarly) {
bitmap = processBitmap(dataString,displayConfig);
}
if(bitmap!=null){
mImageCache.addToMemoryCache(dataString, bitmap);
}
return bitmap;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
if (isCancelled() || mExitTasksEarly) {
bitmap = null;
}
// 判断线程和当前的imageview是否是匹配
final View imageView = getAttachedImageView();
if (bitmap != null && imageView != null) {
mConfig.displayer.loadCompletedisplay(imageView,bitmap,displayConfig);
}else if(bitmap == null && imageView!=null ){
mConfig.displayer.loadFailDisplay(imageView, displayConfig.getLoadfailBitmap());
}
}
@Override
protected void onCancelled(Bitmap bitmap) {
super.onCancelled(bitmap);
synchronized (mPauseWorkLock) {
mPauseWorkLock.notifyAll();
}
}
/**
* 获取线程匹配的imageView,防止出现闪动的现象
* @return
*/
private View getAttachedImageView() {
final View imageView = imageViewReference.get();
final BitmapLoadAndDisplayTask bitmapWorkerTask = getBitmapTaskFromImageView(imageView);
if (this == bitmapWorkerTask) {
return imageView;
}
return null;
}
}
/**
* @title 配置信息
* @description FinalBitmap的配置信息
* @company 探索者网络工作室(www.tsz.net)
* @author michael Young (www.YangFuhai.com)
* @version 1.0
* @created 2012-10-28
*/
private class FinalBitmapConfig {
public String cachePath;
public Displayer displayer;//interface 图片加载成功、失败的回调
public Downloader downloader;//interface 下载网络流
public BitmapDisplayConfig defaultDisplayConfig;//图像配置
public float memCacheSizePercent;//缓存百分比,android系统分配给每个apk内存的大小
public int memCacheSize;//内存缓存百分比
public int diskCacheSize;//磁盘百分比
public int poolSize = 3;//默认的线程池线程并发数量
public boolean recycleImmediately = true;//是否立即回收内存
public FinalBitmapConfig(Context context) {
defaultDisplayConfig = new BitmapDisplayConfig();
defaultDisplayConfig.setAnimation(null);
defaultDisplayConfig.setAnimationType(BitmapDisplayConfig.AnimationType.fadeIn);
//设置图片的显示最大尺寸(为屏幕的大小,默认为屏幕宽度的1/2)
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
int defaultWidth = (int)Math.floor(displayMetrics.widthPixels/2);
defaultDisplayConfig.setBitmapHeight(defaultWidth);
defaultDisplayConfig.setBitmapWidth(defaultWidth);
}
}
}
1.图像的配置使用了FinalBitmapConfig类实现的,具体是通过BitmapDisplayConfig类实现的,该类就是一个普通的实体类,定义了一些列get 、 set方法。
2.图像缓存使用BitmapCache实现,关于缓存的操作FinalBitmap都是通过该类实现的,它是通过DiskCache、IMemoryCache、ImageCacheParams辅助核心类实现。相关实现都在net.tsz.afinal.bitmap.core包下。
BitmapCache首先通过init(cacheParams);进行了图像的初始化工作,cacheParams是ImageCacheParams类,它来设置图片的缓存大小
,文件来源等等属性。
然后通过是否设置缓存做不同的处理分别是SoftMemoryCacheImpl,BaseMemoryCacheImpl,DiskCache
===我们先看BaseMemoryCacheImpl:
public class BaseMemoryCacheImpl implements IMemoryCache{...}
它先使用了一个接口IMemoryCache,这里面根据传入的size使用lru算法,通过这个LruMemoryCache类实现的,补充一下LinkedHashMap是Map接口的哈希表和链接列表
相关参考:Android高效加载大图、多图解决方案_LruCache,有效避免程序OOM_luohai859的博客
===我们再看SoftMemoryCacheImpl:
public class SoftMemoryCacheImpl implements IMemoryCache{...}
采用的是HashMap算法实现的
===最后是DiskCache:
// 是否启用内存缓存
if (mCacheParams.memoryCacheEnabled) {
//是否立即回收内存
if(mCacheParams.recycleImmediately)
mMemoryCache = new SoftMemoryCacheImpl(mCacheParams.memCacheSize);
else
mMemoryCache = new BaseMemoryCacheImpl(mCacheParams.memCacheSize);
}
// 是否启用sdcard缓存
if (cacheParams.diskCacheEnabled) {
try {
String path = mCacheParams.diskCacheDir.getAbsolutePath();
mDiskCache = new DiskCache(path, mCacheParams.diskCacheCount, mCacheParams.diskCacheSize, false);
} catch (IOException e) {
//ignore.
}
}
DiskCache类见下:
package net.tsz.afinal.bitmap.core;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteOrder;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.util.zip.Adler32;
import android.util.Log;
public class DiskCache implements Closeable {
private static final String TAG = DiskCache.class.getSimpleName();
private static final int MAGIC_INDEX_FILE = 0xB3273030;
private static final int MAGIC_DATA_FILE = 0xBD248510;
// index header offset
private static final int IH_MAGIC = 0;
private static final int IH_MAX_ENTRIES = 4;
private static final int IH_MAX_BYTES = 8;
private static final int IH_ACTIVE_REGION = 12;
private static final int IH_ACTIVE_ENTRIES = 16;
private static final int IH_ACTIVE_BYTES = 20;
private static final int IH_VERSION = 24;
private static final int IH_CHECKSUM = 28;
private static final int INDEX_HEADER_SIZE = 32;
private static final int DATA_HEADER_SIZE = 4;
// blob header offset
private static final int BH_KEY = 0;
private static final int BH_CHECKSUM = 8;
private static final int BH_OFFSET = 12;
private static final int BH_LENGTH = 16;
private static final int BLOB_HEADER_SIZE = 20;
private RandomAccessFile mIndexFile;
private RandomAccessFile mDataFile0;
private RandomAccessFile mDataFile1;
private FileChannel mIndexChannel;
private MappedByteBuffer mIndexBuffer;
private int mMaxEntries;
private int mMaxBytes;
private int mActiveRegion;
private int mActiveEntries;
private int mActiveBytes;
private int mVersion;
private RandomAccessFile mActiveDataFile;
private RandomAccessFile mInactiveDataFile;
private int mActiveHashStart;
private int mInactiveHashStart;
private byte[] mIndexHeader = new byte[INDEX_HEADER_SIZE];
private byte[] mBlobHeader = new byte[BLOB_HEADER_SIZE];
private Adler32 mAdler32 = new Adler32();
private String mPath;
/**
* 这里会创建 ".idx"和".0"和".1"文件,".0"和".1"是数据缓存,他们只有一个“激活”状态的,当缓存数据达到用户配置量的时候会清空一个数据,然后切换另一个为“激活”状态
* @param path 缓存路径
* @param maxEntries 存放的最大item 容量
* @param maxBytes 存放的最大数据容量
* @param reset 是否重置,如果true,则先清空所有数据才能使用
* @throws IOException
*/
public DiskCache(String path, int maxEntries, int maxBytes, boolean reset) throws IOException {
this(path, maxEntries, maxBytes, reset, 0);
}
public DiskCache(String path, int maxEntries, int maxBytes, boolean reset, int version) throws IOException {
File dir = new File(path);
if(!dir.exists()){
if(!dir.mkdirs()){
throw new IOException("unable to make dirs");
}
}
mPath = path;
mIndexFile = new RandomAccessFile(path + ".idx", "rw");
mDataFile0 = new RandomAccessFile(path + ".0", "rw");
mDataFile1 = new RandomAccessFile(path + ".1", "rw");
mVersion = version;
if (!reset && loadIndex()) {
return;
}
resetCache(maxEntries, maxBytes);
if (!loadIndex()) {
closeAll();
throw new IOException("unable to load index");
}
}
// Delete the files associated with the given path previously created
// by the BlobCache constructor.
public void delete(){
deleteFileSilently(mPath + ".idx");
deleteFileSilently(mPath + ".0");
deleteFileSilently(mPath + ".1");
}
private static void deleteFileSilently(String path) {
try {
new File(path).delete();
} catch (Throwable t) {
// ignore;
}
}
// Close the cache. All resources are released. No other method should be
// called after this is called.
@Override
public void close() {
syncAll();
closeAll();
}
private void closeAll() {
closeSilently(mIndexChannel);
closeSilently(mIndexFile);
closeSilently(mDataFile0);
closeSilently(mDataFile1);
}
// Returns true if loading index is successful. After this method is called,
// mIndexHeader and index header in file should be kept sync.
private boolean loadIndex() {
try {
mIndexFile.seek(0);
mDataFile0.seek(0);
mDataFile1.seek(0);
byte[] buf = mIndexHeader;
if (mIndexFile.read(buf) != INDEX_HEADER_SIZE) {
Log.w(TAG, "cannot read header");
return false;
}
if (readInt(buf, IH_MAGIC) != MAGIC_INDEX_FILE) {
Log.w(TAG, "cannot read header magic");
return false;
}
if (readInt(buf, IH_VERSION) != mVersion) {
Log.w(TAG, "version mismatch");
return false;
}
mMaxEntries = readInt(buf, IH_MAX_ENTRIES);
mMaxBytes = readInt(buf, IH_MAX_BYTES);
mActiveRegion = readInt(buf, IH_ACTIVE_REGION);
mActiveEntries = readInt(buf, IH_ACTIVE_ENTRIES);
mActiveBytes = readInt(buf, IH_ACTIVE_BYTES);
int sum = readInt(buf, IH_CHECKSUM);
if (checkSum(buf, 0, IH_CHECKSUM) != sum) {
Log.w(TAG, "header checksum does not match");
return false;
}
// Sanity check
if (mMaxEntries <= 0) {
Log.w(TAG, "invalid max entries");
return false;
}
if (mMaxBytes <= 0) {
Log.w(TAG, "invalid max bytes");
return false;
}
if (mActiveRegion != 0 && mActiveRegion != 1) {
Log.w(TAG, "invalid active region");
return false;
}
if (mActiveEntries < 0 || mActiveEntries > mMaxEntries) {
Log.w(TAG, "invalid active entries");
return false;
}
if (mActiveBytes < DATA_HEADER_SIZE || mActiveBytes > mMaxBytes) {
Log.w(TAG, "invalid active bytes");
return false;
}
if (mIndexFile.length() !=
INDEX_HEADER_SIZE + mMaxEntries * 12 * 2) {
Log.w(TAG, "invalid index file length");
return false;
}
// Make sure data file has magic
byte[] magic = new byte[4];
if (mDataFile0.read(magic) != 4) {
Log.w(TAG, "cannot read data file magic");
return false;
}
if (readInt(magic, 0) != MAGIC_DATA_FILE) {
Log.w(TAG, "invalid data file magic");
return false;
}
if (mDataFile1.read(magic) != 4) {
Log.w(TAG, "cannot read data file magic");
return false;
}
if (readInt(magic, 0) != MAGIC_DATA_FILE) {
Log.w(TAG, "invalid data file magic");
return false;
}
// Map index file to memory
mIndexChannel = mIndexFile.getChannel();
mIndexBuffer = mIndexChannel.map(FileChannel.MapMode.READ_WRITE,
0, mIndexFile.length());
mIndexBuffer.order(ByteOrder.LITTLE_ENDIAN);
setActiveVariables();
return true;
} catch (IOException ex) {
Log.e(TAG, "loadIndex failed.", ex);
return false;
}
}
private void setActiveVariables() throws IOException {
mActiveDataFile = (mActiveRegion == 0) ? mDataFile0 : mDataFile1;
mInactiveDataFile = (mActiveRegion == 1) ? mDataFile0 : mDataFile1;
mActiveDataFile.setLength(mActiveBytes);
mActiveDataFile.seek(mActiveBytes);
mActiveHashStart = INDEX_HEADER_SIZE;
mInactiveHashStart = INDEX_HEADER_SIZE;
if (mActiveRegion == 0) {
mInactiveHashStart += mMaxEntries * 12;
} else {
mActiveHashStart += mMaxEntries * 12;
}
}
private void resetCache(int maxEntries, int maxBytes) throws IOException {
mIndexFile.setLength(0); // truncate to zero the index
mIndexFile.setLength(INDEX_HEADER_SIZE + maxEntries * 12 * 2);
mIndexFile.seek(0);
byte[] buf = mIndexHeader;
writeInt(buf, IH_MAGIC, MAGIC_INDEX_FILE);
writeInt(buf, IH_MAX_ENTRIES, maxEntries);
writeInt(buf, IH_MAX_BYTES, maxBytes);
writeInt(buf, IH_ACTIVE_REGION, 0);
writeInt(buf, IH_ACTIVE_ENTRIES, 0);
writeInt(buf, IH_ACTIVE_BYTES, DATA_HEADER_SIZE);
writeInt(buf, IH_VERSION, mVersion);
writeInt(buf, IH_CHECKSUM, checkSum(buf, 0, IH_CHECKSUM));
mIndexFile.write(buf);
// This is only needed if setLength does not zero the extended part.
// writeZero(mIndexFile, maxEntries * 12 * 2);
mDataFile0.setLength(0);
mDataFile1.setLength(0);
mDataFile0.seek(0);
mDataFile1.seek(0);
writeInt(buf, 0, MAGIC_DATA_FILE);
mDataFile0.write(buf, 0, 4);
mDataFile1.write(buf, 0, 4);
}
// Flip the active region and the inactive region.
private void flipRegion() throws IOException {
mActiveRegion = 1 - mActiveRegion;
mActiveEntries = 0;
mActiveBytes = DATA_HEADER_SIZE;
writeInt(mIndexHeader, IH_ACTIVE_REGION, mActiveRegion);
writeInt(mIndexHeader, IH_ACTIVE_ENTRIES, mActiveEntries);
writeInt(mIndexHeader, IH_ACTIVE_BYTES, mActiveBytes);
updateIndexHeader();
setActiveVariables();
clearHash(mActiveHashStart);
syncIndex();
}
// Sync mIndexHeader to the index file.
private void updateIndexHeader() {
writeInt(mIndexHeader, IH_CHECKSUM,
checkSum(mIndexHeader, 0, IH_CHECKSUM));
mIndexBuffer.position(0);
mIndexBuffer.put(mIndexHeader);
}
// Clear the hash table starting from the specified offset.
private void clearHash(int hashStart) {
byte[] zero = new byte[1024];
mIndexBuffer.position(hashStart);
for (int count = mMaxEntries * 12; count > 0;) {
int todo = Math.min(count, 1024);
mIndexBuffer.put(zero, 0, todo);
count -= todo;
}
}
// Inserts a (key, data) pair into the cache.
public void insert(long key, byte[] data) throws IOException {
if (DATA_HEADER_SIZE + BLOB_HEADER_SIZE + data.length > mMaxBytes) {
throw new RuntimeException("blob is too large!");
}
if (mActiveBytes + BLOB_HEADER_SIZE + data.length > mMaxBytes
|| mActiveEntries * 2 >= mMaxEntries) {
flipRegion();
}
if (!lookupInternal(key, mActiveHashStart)) {
// If we don't have an existing entry with the same key, increase
// the entry count.
mActiveEntries++;
writeInt(mIndexHeader, IH_ACTIVE_ENTRIES, mActiveEntries);
}
Log.i("info", "当前图片已经写入到了本地++++++++++++++"+key);
insertInternal(key, data, data.length);
updateIndexHeader();
}
// Appends the data to the active file. It also updates the hash entry.
// The proper hash entry (suitable for insertion or replacement) must be
// pointed by mSlotOffset.
private void insertInternal(long key, byte[] data, int length)
throws IOException {
byte[] header = mBlobHeader;
int sum = checkSum(data);
writeLong(header, BH_KEY, key);
writeInt(header, BH_CHECKSUM, sum);
writeInt(header, BH_OFFSET, mActiveBytes);
writeInt(header, BH_LENGTH, length);
mActiveDataFile.write(header);
mActiveDataFile.write(data, 0, length);
mIndexBuffer.putLong(mSlotOffset, key);
mIndexBuffer.putInt(mSlotOffset + 8, mActiveBytes);
mActiveBytes += BLOB_HEADER_SIZE + length;
writeInt(mIndexHeader, IH_ACTIVE_BYTES, mActiveBytes);
}
public static class LookupRequest {
public long key; // input: the key to find
public byte[] buffer; // input/output: the buffer to store the blob
public int length; // output: the length of the blob
}
// This method is for one-off lookup. For repeated lookup, use the version
// accepting LookupRequest to avoid repeated memory allocation.
private LookupRequest mLookupRequest = new LookupRequest();
public byte[] lookup(long key) throws IOException {
mLookupRequest.key = key;
mLookupRequest.buffer = null;
if (lookup(mLookupRequest)) {
return mLookupRequest.buffer;
} else {
return null;
}
}
// Returns true if the associated blob for the given key is available.
// The blob is stored in the buffer pointed by req.buffer, and the length
// is in stored in the req.length variable.
//
// The user can input a non-null value in req.buffer, and this method will
// try to use that buffer. If that buffer is not large enough, this method
// will allocate a new buffer and assign it to req.buffer.
//
// This method tries not to throw IOException even if the data file is
// corrupted, but it can still throw IOException if things get strange.
public boolean lookup(LookupRequest req) throws IOException {
// Look up in the active region first.
if (lookupInternal(req.key, mActiveHashStart)) {
if (getBlob(mActiveDataFile, mFileOffset, req)) {
return true;
}
}
// We want to copy the data from the inactive file to the active file
// if it's available. So we keep the offset of the hash entry so we can
// avoid looking it up again.
int insertOffset = mSlotOffset;
// Look up in the inactive region.
if (lookupInternal(req.key, mInactiveHashStart)) {
if (getBlob(mInactiveDataFile, mFileOffset, req)) {
// If we don't have enough space to insert this blob into
// the active file, just return it.
if (mActiveBytes + BLOB_HEADER_SIZE + req.length > mMaxBytes
|| mActiveEntries * 2 >= mMaxEntries) {
return true;
}
// Otherwise copy it over.
mSlotOffset = insertOffset;
try {
insertInternal(req.key, req.buffer, req.length);
mActiveEntries++;
writeInt(mIndexHeader, IH_ACTIVE_ENTRIES, mActiveEntries);
updateIndexHeader();
} catch (Throwable t) {
Log.e(TAG, "cannot copy over");
}
return true;
}
}
return false;
}
// Copies the blob for the specified offset in the specified file to
// req.buffer. If req.buffer is null or too small, allocate a buffer and
// assign it to req.buffer.
// Returns false if the blob is not available (either the index file is
// not sync with the data file, or one of them is corrupted). The length
// of the blob is stored in the req.length variable.
private boolean getBlob(RandomAccessFile file, int offset,
LookupRequest req) throws IOException {
byte[] header = mBlobHeader;
long oldPosition = file.getFilePointer();
try {
file.seek(offset);
if (file.read(header) != BLOB_HEADER_SIZE) {
Log.w(TAG, "cannot read blob header");
return false;
}
long blobKey = readLong(header, BH_KEY);
if (blobKey != req.key) {
Log.w(TAG, "blob key does not match: " + blobKey);
return false;
}
int sum = readInt(header, BH_CHECKSUM);
int blobOffset = readInt(header, BH_OFFSET);
if (blobOffset != offset) {
Log.w(TAG, "blob offset does not match: " + blobOffset);
return false;
}
int length = readInt(header, BH_LENGTH);
if (length < 0 || length > mMaxBytes - offset - BLOB_HEADER_SIZE) {
Log.w(TAG, "invalid blob length: " + length);
return false;
}
if (req.buffer == null || req.buffer.length < length) {
req.buffer = new byte[length];
}
byte[] blob = req.buffer;
req.length = length;
if (file.read(blob, 0, length) != length) {
Log.w(TAG, "cannot read blob data");
return false;
}
if (checkSum(blob, 0, length) != sum) {
Log.w(TAG, "blob checksum does not match: " + sum);
return false;
}
return true;
} catch (Throwable t) {
Log.e(TAG, "getBlob failed.", t);
return false;
} finally {
file.seek(oldPosition);
}
}
// Tries to look up a key in the specified hash region.
// Returns true if the lookup is successful.
// The slot offset in the index file is saved in mSlotOffset. If the lookup
// is successful, it's the slot found. Otherwise it's the slot suitable for
// insertion.
// If the lookup is successful, the file offset is also saved in
// mFileOffset.
private int mSlotOffset;
private int mFileOffset;
private boolean lookupInternal(long key, int hashStart) {
int slot = (int) (key % mMaxEntries);
if (slot < 0) slot += mMaxEntries;
int slotBegin = slot;
while (true) {
int offset = hashStart + slot * 12;
long candidateKey = mIndexBuffer.getLong(offset);
int candidateOffset = mIndexBuffer.getInt(offset + 8);
if (candidateOffset == 0) {
mSlotOffset = offset;
return false;
} else if (candidateKey == key) {
mSlotOffset = offset;
mFileOffset = candidateOffset;
return true;
} else {
if (++slot >= mMaxEntries) {
slot = 0;
}
if (slot == slotBegin) {
Log.w(TAG, "corrupted index: clear the slot.");
mIndexBuffer.putInt(hashStart + slot * 12 + 8, 0);
}
}
}
}
public void syncIndex() {
try {
mIndexBuffer.force();
} catch (Throwable t) {
Log.w(TAG, "sync index failed", t);
}
}
public void syncAll() {
syncIndex();
try {
mDataFile0.getFD().sync();
} catch (Throwable t) {
Log.w(TAG, "sync data file 0 failed", t);
}
try {
mDataFile1.getFD().sync();
} catch (Throwable t) {
Log.w(TAG, "sync data file 1 failed", t);
}
}
// This is for testing only.
//
// Returns the active count (mActiveEntries). This also verifies that
// the active count matches matches what's inside the hash region.
int getActiveCount() {
int count = 0;
for (int i = 0; i < mMaxEntries; i++) {
int offset = mActiveHashStart + i * 12;
// long candidateKey = mIndexBuffer.getLong(offset);
int candidateOffset = mIndexBuffer.getInt(offset + 8);
if (candidateOffset != 0) ++count;
}
if (count == mActiveEntries) {
return count;
} else {
Log.e(TAG, "wrong active count: " + mActiveEntries + " vs " + count);
return -1; // signal failure.
}
}
int checkSum(byte[] data) {
mAdler32.reset();
mAdler32.update(data);
return (int) mAdler32.getValue();
}
int checkSum(byte[] data, int offset, int nbytes) {
mAdler32.reset();
mAdler32.update(data, offset, nbytes);
return (int) mAdler32.getValue();
}
static void closeSilently(Closeable c) {
if (c == null) return;
try {
c.close();
} catch (Throwable t) {
// do nothing
}
}
static int readInt(byte[] buf, int offset) {
return (buf[offset] & 0xff)
| ((buf[offset + 1] & 0xff) << 8)
| ((buf[offset + 2] & 0xff) << 16)
| ((buf[offset + 3] & 0xff) << 24);
}
static long readLong(byte[] buf, int offset) {
long result = buf[offset + 7] & 0xff;
for (int i = 6; i >= 0; i--) {
result = (result << 8) | (buf[offset + i] & 0xff);
}
return result;
}
static void writeInt(byte[] buf, int offset, int value) {
for (int i = 0; i < 4; i++) {
buf[offset + i] = (byte) (value & 0xff);
value >>= 8;
}
}
static void writeLong(byte[] buf, int offset, long value) {
for (int i = 0; i < 8; i++) {
buf[offset + i] = (byte) (value & 0xff);
value >>= 8;
}
}
}
里面核心在于写入文件,他们的实现说白了就是先建立缓存文件,然后逐个字节读写,采用位操作执行效率得到大量提升。
与此同时为了防止图片数据被恶意篡改使用了Adler-32校验算法,关于Adler-32算法参见博客:Adler-32校验算法_wdkirchhoff的博客_adler32,为了防止oom在采用缓存机制的同时使用BitmapFactory.Options进行图片的缩放(BitmapDecoder类中)
好了,以上都是对获取的图像进行处理,那么文件是怎么来的呢
那就是BitmapProcess类了,里面实现了从网络下载和从内存获取图片getBitmap(url.....)和getFromDisk(....)
具体实现是通过流,见
bitmap = BitmapDecoder.decodeSampledBitmapFromByteArray(data,0,data.length,config.getBitmapWidth(),config.getBitmapHeight());
和
b = BitmapFactory.decodeByteArray(buffer.data, buffer.offset, buffer.length);
发现decodeSampledBitmapFromByteArray(...)方法的实现也是 BitmapFactory.decodeByteArray(...)的方式,
public static Bitmap decodeSampledBitmapFromByteArray(byte[] data, int offset, int length, int reqWidth, int reqHeight) {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
options.inPurgeable = true;
BitmapFactory.decodeByteArray(data, offset, length, options);
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
options.inJustDecodeBounds = false;
return BitmapFactory.decodeByteArray(data, offset, length, options);
}
BitmapFactory.decodeByteArray(...)见下:
public static Bitmap decodeByteArray(byte[] data, int offset, int length, Options opts) {
if ((offset | length) < 0 || data.length < offset + length) {
throw new ArrayIndexOutOfBoundsException();
}
Bitmap bm;
Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "decodeBitmap");
try {
bm = nativeDecodeByteArray(data, offset, length, opts);
if (bm == null && opts != null && opts.inBitmap != null) {
throw new IllegalArgumentException("Problem decoding into existing bitmap");
}
setDensityFromOptions(bm, opts);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
}
return bm;
}
简而言之就是,从数据流中通过BitmapFactory转换为Bitmap对象,在此之前可能要根据开发选择是否缓存,如果缓存就是采用lru算法,然后将该Bitmap加载到ImageView等。。上
- FinalHttp
How:先建立对象FinalHttp mFinalHttp = new FinalHttp() 然后你懂得...
获取方式---------get
FinalHttp fh = new FinalHttp();
fh.get("http://www.yangfuhai.com", new AjaxCallBack(){
@Override
public void onLoading(long count, long current) { //每1秒钟自动被回调一次
textView.setText(current+"/"+count);
}
@Override
public void onSuccess(String t) {
textView.setText(t==null?"null":t);
}
@Override
public void onStart() {
//开始http请求的时候回调
}
@Override
public void onFailure(Throwable t, String strMsg) {
//加载失败的时候回调
}
});
上传方式---------post
AjaxParams params = new AjaxParams();
params.put("username", "michael yang");
params.put("password", "123456");
params.put("email", "test@tsz.net");
params.put("profile_picture", new File("/mnt/sdcard/pic.jpg")); // 上传文件
params.put("profile_picture2", inputStream); // 上传数据流
params.put("profile_picture3", new ByteArrayInputStream(bytes)); // 提交字节流
FinalHttp fh = new FinalHttp();
fh.post("http://www.yangfuhai.com", params, new AjaxCallBack(){
@Override
public void onLoading(long count, long current) {
textView.setText(current+"/"+count);
}
@Override
public void onSuccess(String t) {
textView.setText(t==null?"null":t);
}
});
下载文件---------download
FinalHttp fh = new FinalHttp();
fh.download("http://www.xxx.com/下载路径/xxx.apk", "/mnt/sdcard/testapk.apk", new AjaxCallBack() {
@Override
public void onLoading(long count, long current) {
textView.setText("下载进度:"+current+"/"+count);
}
@Override
public void onSuccess(File t) {
textView.setText(t==null?"null":t.getAbsoluteFile().toString());
}
});
看样子都很简单。下面我们来看:
Why:
通过源码我们知道了它是在 org. apache. http. impl. client. DefaultHttpClient.DefaultHttpClient的基础上进行了二次封装,用到的底层当然是webkit,关于webkit参见: 【转】WebKit 分析–for android - Braincol
我们看到FinalHttp在无参的构造中进行了一些列的网络连接初始化Schemeregistry参见: 网络连接知识,并定义了相关网络协议 ,在执行线程池使用AtomicInteger进行线程安全同步,可以看这个博客: AtomicInteger的用法_zz198808的博客-_atomicinteger方法 或者移步: Java之美[从菜鸟到高手演练]之atomic包的原理及分析_终点的博客-atomic包原理
<span style="white-space:pre"> </span>class HttpHandler <T> extends AsyncTask<Object, Object, Object> implements EntityCallBack
package net.tsz.afinal;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.zip.GZIPInputStream;
import net.tsz.afinal.http.AjaxCallBack;
import net.tsz.afinal.http.AjaxParams;
import net.tsz.afinal.http.HttpHandler;
import net.tsz.afinal.http.RetryHandler;
import net.tsz.afinal.http.SyncRequestHandler;
import org.apache.http.Header;
import org.apache.http.HeaderElement;
import org.apache.http.HttpEntity;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.HttpResponse;
import org.apache.http.HttpResponseInterceptor;
import org.apache.http.HttpVersion;
import org.apache.http.client.CookieStore;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.protocol.ClientContext;
import org.apache.http.conn.params.ConnManagerParams;
import org.apache.http.conn.params.ConnPerRouteBean;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.entity.HttpEntityWrapper;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.SyncBasicHttpContext;
public class FinalHttp {
private static final int DEFAULT_SOCKET_BUFFER_SIZE = 8 * 1024; //8KB
private static final String HEADER_ACCEPT_ENCODING = "Accept-Encoding";
private static final String ENCODING_GZIP = "gzip";
private static int maxConnections = 10; //http请求最大并发连接数
private static int socketTimeout = 10 * 1000; //超时时间,默认10秒
private static int maxRetries = 5;//错误尝试次数,错误异常表请在RetryHandler添加
private static int httpThreadCount = 3;//http线程池数量
private final DefaultHttpClient httpClient;
private final HttpContext httpContext;
private String charset = "utf-8";
private final Map<String, String> clientHeaderMap;
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);
public Thread newThread(Runnable r) {
Thread tread = new Thread(r, "FinalHttp #" + mCount.getAndIncrement());
tread.setPriority(Thread.NORM_PRIORITY - 1);
return tread;
}
};
private static final Executor executor =Executors.newFixedThreadPool(httpThreadCount, sThreadFactory);
public FinalHttp() {
BasicHttpParams httpParams = new BasicHttpParams();
ConnManagerParams.setTimeout(httpParams, socketTimeout);
ConnManagerParams.setMaxConnectionsPerRoute(httpParams, new ConnPerRouteBean(maxConnections));
ConnManagerParams.setMaxTotalConnections(httpParams, 10);
HttpConnectionParams.setSoTimeout(httpParams, socketTimeout);
HttpConnectionParams.setConnectionTimeout(httpParams, socketTimeout);
HttpConnectionParams.setTcpNoDelay(httpParams, true);
HttpConnectionParams.setSocketBufferSize(httpParams, DEFAULT_SOCKET_BUFFER_SIZE);
HttpProtocolParams.setVersion(httpParams, HttpVersion.HTTP_1_1);
SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
schemeRegistry.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));
ThreadSafeClientConnManager cm = new ThreadSafeClientConnManager(httpParams, schemeRegistry);
httpContext = new SyncBasicHttpContext(new BasicHttpContext());
httpClient = new DefaultHttpClient(cm, httpParams);
httpClient.addRequestInterceptor(new HttpRequestInterceptor() {
public void process(HttpRequest request, HttpContext context) {
if (!request.containsHeader(HEADER_ACCEPT_ENCODING)) {
request.addHeader(HEADER_ACCEPT_ENCODING, ENCODING_GZIP);
}
for (String header : clientHeaderMap.keySet()) {
request.addHeader(header, clientHeaderMap.get(header));
}
}
});
httpClient.addResponseInterceptor(new HttpResponseInterceptor() {
public void process(HttpResponse response, HttpContext context) {
final HttpEntity entity = response.getEntity();
if (entity == null) {
return;
}
final Header encoding = entity.getContentEncoding();
if (encoding != null) {
for (HeaderElement element : encoding.getElements()) {
if (element.getName().equalsIgnoreCase(ENCODING_GZIP)) {
response.setEntity(new InflatingEntity(response.getEntity()));
break;
}
}
}
}
});
httpClient.setHttpRequestRetryHandler(new RetryHandler(maxRetries));
clientHeaderMap = new HashMap<String, String>();
}
public HttpClient getHttpClient() {
return this.httpClient;
}
public HttpContext getHttpContext() {
return this.httpContext;
}
public void configCharset(String charSet){
if(charSet!=null && charSet.trim().length()!=0)
this.charset = charSet;
}
public void configCookieStore(CookieStore cookieStore) {
httpContext.setAttribute(ClientContext.COOKIE_STORE, cookieStore);
}
public void configUserAgent(String userAgent) {
HttpProtocolParams.setUserAgent(this.httpClient.getParams(), userAgent);
}
/**
* 设置网络连接超时时间,默认为10秒钟
* @param timeout
*/
public void configTimeout(int timeout){
final HttpParams httpParams = this.httpClient.getParams();
ConnManagerParams.setTimeout(httpParams, timeout);
HttpConnectionParams.setSoTimeout(httpParams, timeout);
HttpConnectionParams.setConnectionTimeout(httpParams, timeout);
}
/**
* 设置https请求时 的 SSLSocketFactory
* @param sslSocketFactory
*/
public void configSSLSocketFactory(SSLSocketFactory sslSocketFactory) {
Scheme scheme = new Scheme("https", sslSocketFactory, 443);
this.httpClient.getConnectionManager().getSchemeRegistry().register(scheme);
}
/**
* 配置错误重试次数
* @param retry
*/
public void configRequestExecutionRetryCount(int count){
this.httpClient.setHttpRequestRetryHandler(new RetryHandler(count));
}
/**
* 添加http请求头
* @param header
* @param value
*/
public void addHeader(String header, String value) {
clientHeaderMap.put(header, value);
}
//------------------get 请求-----------------------
public void get( String url, AjaxCallBack<? extends Object> callBack) {
get( url, null, callBack);
}
public void get( String url, AjaxParams params, AjaxCallBack<? extends Object> callBack) {
sendRequest(httpClient, httpContext, new HttpGet(getUrlWithQueryString(url, params)), null, callBack);
}
public void get( String url, Header[] headers, AjaxParams params, AjaxCallBack<? extends Object> callBack) {
HttpUriRequest request = new HttpGet(getUrlWithQueryString(url, params));
if(headers != null) request.setHeaders(headers);
sendRequest(httpClient, httpContext, request, null, callBack);
}
public Object getSync( String url) {
return getSync( url, null);
}
public Object getSync( String url, AjaxParams params) {
HttpUriRequest request = new HttpGet(getUrlWithQueryString(url, params));
return sendSyncRequest(httpClient, httpContext, request, null);
}
public Object getSync( String url, Header[] headers, AjaxParams params) {
HttpUriRequest request = new HttpGet(getUrlWithQueryString(url, params));
if(headers != null) request.setHeaders(headers);
return sendSyncRequest(httpClient, httpContext, request, null);
}
//------------------post 请求-----------------------
public void post(String url, AjaxCallBack<? extends Object> callBack) {
post(url, null, callBack);
}
public void post(String url, AjaxParams params, AjaxCallBack<? extends Object> callBack) {
post(url, paramsToEntity(params), null, callBack);
}
public void post( String url, HttpEntity entity, String contentType, AjaxCallBack<? extends Object> callBack) {
sendRequest(httpClient, httpContext, addEntityToRequestBase(new HttpPost(url), entity), contentType, callBack);
}
public <T> void post( String url, Header[] headers, AjaxParams params, String contentType,AjaxCallBack<T> callBack) {
HttpEntityEnclosingRequestBase request = new HttpPost(url);
if(params != null) request.setEntity(paramsToEntity(params));
if(headers != null) request.setHeaders(headers);
sendRequest(httpClient, httpContext, request, contentType, callBack);
}
public void post( String url, Header[] headers, HttpEntity entity, String contentType,AjaxCallBack<? extends Object> callBack) {
HttpEntityEnclosingRequestBase request = addEntityToRequestBase(new HttpPost(url), entity);
if(headers != null) request.setHeaders(headers);
sendRequest(httpClient, httpContext, request, contentType, callBack);
}
public Object postSync(String url) {
return postSync(url, null);
}
public Object postSync(String url, AjaxParams params) {
return postSync(url, paramsToEntity(params), null);
}
public Object postSync( String url, HttpEntity entity, String contentType) {
return sendSyncRequest(httpClient, httpContext, addEntityToRequestBase(new HttpPost(url), entity), contentType);
}
public Object postSync( String url, Header[] headers, AjaxParams params, String contentType) {
HttpEntityEnclosingRequestBase request = new HttpPost(url);
if(params != null) request.setEntity(paramsToEntity(params));
if(headers != null) request.setHeaders(headers);
return sendSyncRequest(httpClient, httpContext, request, contentType);
}
public Object postSync( String url, Header[] headers, HttpEntity entity, String contentType) {
HttpEntityEnclosingRequestBase request = addEntityToRequestBase(new HttpPost(url), entity);
if(headers != null) request.setHeaders(headers);
return sendSyncRequest(httpClient, httpContext, request, contentType);
}
//------------------put 请求-----------------------
public void put(String url, AjaxCallBack<? extends Object> callBack) {
put(url, null, callBack);
}
public void put( String url, AjaxParams params, AjaxCallBack<? extends Object> callBack) {
put(url, paramsToEntity(params), null, callBack);
}
public void put( String url, HttpEntity entity, String contentType, AjaxCallBack<? extends Object> callBack) {
sendRequest(httpClient, httpContext, addEntityToRequestBase(new HttpPut(url), entity), contentType, callBack);
}
public void put(String url,Header[] headers, HttpEntity entity, String contentType, AjaxCallBack<? extends Object> callBack) {
HttpEntityEnclosingRequestBase request = addEntityToRequestBase(new HttpPut(url), entity);
if(headers != null) request.setHeaders(headers);
sendRequest(httpClient, httpContext, request, contentType, callBack);
}
public Object putSync(String url) {
return putSync(url, null);
}
public Object putSync( String url, AjaxParams params) {
return putSync(url, paramsToEntity(params),null);
}
public Object putSync(String url, HttpEntity entity, String contentType) {
return putSync(url,null, entity, contentType);
}
public Object putSync(String url,Header[] headers, HttpEntity entity, String contentType) {
HttpEntityEnclosingRequestBase request = addEntityToRequestBase(new HttpPut(url), entity);
if(headers != null) request.setHeaders(headers);
return sendSyncRequest(httpClient, httpContext, request, contentType);
}
//------------------delete 请求-----------------------
public void delete( String url, AjaxCallBack<? extends Object> callBack) {
final HttpDelete delete = new HttpDelete(url);
sendRequest(httpClient, httpContext, delete, null, callBack);
}
public void delete( String url, Header[] headers, AjaxCallBack<? extends Object> callBack) {
final HttpDelete delete = new HttpDelete(url);
if(headers != null) delete.setHeaders(headers);
sendRequest(httpClient, httpContext, delete, null, callBack);
}
public Object deleteSync(String url) {
return deleteSync(url,null);
}
public Object deleteSync( String url, Header[] headers) {
final HttpDelete delete = new HttpDelete(url);
if(headers != null) delete.setHeaders(headers);
return sendSyncRequest(httpClient, httpContext, delete, null);
}
//---------------------下载---------------------------------------
public HttpHandler<File> download(String url,String target,AjaxCallBack<File> callback){
return download(url, null, target, false, callback);
}
public HttpHandler<File> download(String url,String target,boolean isResume,AjaxCallBack<File> callback){
return download(url, null, target, isResume, callback);
}
public HttpHandler<File> download( String url,AjaxParams params, String target, AjaxCallBack<File> callback) {
return download(url, params, target, false, callback);
}
public HttpHandler<File> download( String url,AjaxParams params, String target,boolean isResume, AjaxCallBack<File> callback) {
final HttpGet get = new HttpGet(getUrlWithQueryString(url, params));
HttpHandler<File> handler = new HttpHandler<File>(httpClient, httpContext, callback,charset);
handler.executeOnExecutor(executor,get,target,isResume);
return handler;
}
protected <T> void sendRequest(DefaultHttpClient client, HttpContext httpContext, HttpUriRequest uriRequest, String contentType, AjaxCallBack<T> ajaxCallBack) {
if(contentType != null) {
uriRequest.addHeader("Content-Type", contentType);
}
new HttpHandler<T>(client, httpContext, ajaxCallBack,charset)
.executeOnExecutor(executor, uriRequest);
}
protected Object sendSyncRequest(DefaultHttpClient client, HttpContext httpContext, HttpUriRequest uriRequest, String contentType) {
if(contentType != null) {
uriRequest.addHeader("Content-Type", contentType);
}
return new SyncRequestHandler(client, httpContext,charset).sendRequest(uriRequest);
}
public static String getUrlWithQueryString(String url, AjaxParams params) {
if(params != null) {
String paramString = params.getParamString();
url += "?" + paramString;
}
return url;
}
private HttpEntity paramsToEntity(AjaxParams params) {
HttpEntity entity = null;
if(params != null) {
entity = params.getEntity();
}
return entity;
}
private HttpEntityEnclosingRequestBase addEntityToRequestBase(HttpEntityEnclosingRequestBase requestBase, HttpEntity entity) {
if(entity != null){
requestBase.setEntity(entity);
}
return requestBase;
}
private static class InflatingEntity extends HttpEntityWrapper {
public InflatingEntity(HttpEntity wrapped) {
super(wrapped);
}
@Override
public InputStream getContent() throws IOException {
return new GZIPInputStream(wrappedEntity.getContent());
}
@Override
public long getContentLength() {
return -1;
}
}
}
- FinalDb
How:
对于数据库的操作非常简单,在上面的程序基础上我添加了一个fun()方法来演示使用:
/ 直接写事件,省去findViewById和setListener
public void buttonClick(View view) {
textView.setText("建立 user_table表");
fun();
}
private void fun() {
FinalDb finalDb = FinalDb.create(YouActivity.this);//单例模式创建表
finalDb.deleteAll(User.class);
User user = new User();//表实体
user.setAge(22);
user.setId(1);
user.setName("hy");
user.setSex("女");
User user2 = new User();
user2.setAge(23);
user2.setId(2);
user2.setName("dcm");
user2.setSex("女");
finalDb.save(user);//创建表
finalDb.save(user2);//创建表
List<User> list = finalDb.findAll(User.class);//查询表
textView.setText("用户1:" + list.get(0).getName()
+ " 用户2:" + list.get(1).getName());
}
还有其他的操作参见框架作者给的说明文档:
使用android快速开发框架afinal的FinalDb操作android数据库
Why:
核心当然是SQLiteDatabase,关于这方面的知识请点击:android数据库储方式(一)----SQLite的基本操作 - 文酱 和 Android数据库 之 SQLite数据库 - Poborsky
我们来看,Finaldb是一个单例模式,他通过对外开放的create方法创建表,关于其源码见下:
另外注意,afinal的数据表实体也是通过注解的方式管理的。
package net.tsz.afinal;
import java.io.File;
import java.io.IOException;
import java.lang.String;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import net.tsz.afinal.db.sqlite.CursorUtils;
import net.tsz.afinal.db.sqlite.DbModel;
import net.tsz.afinal.db.sqlite.ManyToOneLazyLoader;
import net.tsz.afinal.db.sqlite.OneToManyLazyLoader;
import net.tsz.afinal.db.sqlite.SqlBuilder;
import net.tsz.afinal.db.sqlite.SqlInfo;
import net.tsz.afinal.db.table.KeyValue;
import net.tsz.afinal.db.table.ManyToOne;
import net.tsz.afinal.db.table.OneToMany;
import net.tsz.afinal.db.table.TableInfo;
import net.tsz.afinal.exception.DbException;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
public class FinalDb {
private static final String TAG = "FinalDb";
private static HashMap<String, FinalDb> daoMap = new HashMap<String, FinalDb>();
private SQLiteDatabase db;
private DaoConfig config;
private FinalDb(DaoConfig config) {
if (config == null)
throw new DbException("daoConfig is null");
if (config.getContext() == null)
throw new DbException("android context is null");
if (config.getTargetDirectory() != null
&& config.getTargetDirectory().trim().length() > 0) {
this.db = createDbFileOnSDCard(config.getTargetDirectory(),
config.getDbName());
} else {
this.db = new SqliteDbHelper(config.getContext()
.getApplicationContext(), config.getDbName(),
config.getDbVersion(), config.getDbUpdateListener())
.getWritableDatabase();
}
this.config = config;
}
private synchronized static FinalDb getInstance(DaoConfig daoConfig) {
FinalDb dao = daoMap.get(daoConfig.getDbName());
if (dao == null) {
dao = new FinalDb(daoConfig);
daoMap.put(daoConfig.getDbName(), dao);
}
return dao;
}
/**
* 创建FinalDb
*
* @param context
*/
public static FinalDb create(Context context) {
DaoConfig config = new DaoConfig();
config.setContext(context);
return create(config);
}
/**
* 创建FinalDb
*
* @param context
* @param isDebug
* 是否是debug模式(debug模式进行数据库操作的时候将会打印sql语句)
*/
public static FinalDb create(Context context, boolean isDebug) {
DaoConfig config = new DaoConfig();
config.setContext(context);
config.setDebug(isDebug);
return create(config);
}
/**
* 创建FinalDb
*
* @param context
* @param dbName
* 数据库名称
*/
public static FinalDb create(Context context, String dbName) {
DaoConfig config = new DaoConfig();
config.setContext(context);
config.setDbName(dbName);
return create(config);
}
/**
* 创建 FinalDb
*
* @param context
* @param dbName
* 数据库名称
* @param isDebug
* 是否为debug模式(debug模式进行数据库操作的时候将会打印sql语句)
*/
public static FinalDb create(Context context, String dbName, boolean isDebug) {
DaoConfig config = new DaoConfig();
config.setContext(context);
config.setDbName(dbName);
config.setDebug(isDebug);
return create(config);
}
/**
* 创建FinalDb
*
* @param context
* @param dbName
* 数据库名称
*/
public static FinalDb create(Context context, String targetDirectory,
String dbName) {
DaoConfig config = new DaoConfig();
config.setContext(context);
config.setDbName(dbName);
config.setTargetDirectory(targetDirectory);
return create(config);
}
/**
* 创建 FinalDb
*
* @param context
* @param dbName
* 数据库名称
* @param isDebug
* 是否为debug模式(debug模式进行数据库操作的时候将会打印sql语句)
*/
public static FinalDb create(Context context, String targetDirectory,
String dbName, boolean isDebug) {
DaoConfig config = new DaoConfig();
config.setContext(context);
config.setTargetDirectory(targetDirectory);
config.setDbName(dbName);
config.setDebug(isDebug);
return create(config);
}
/**
* 创建 FinalDb
*
* @param context
* 上下文
* @param dbName
* 数据库名字
* @param isDebug
* 是否是调试模式:调试模式会log出sql信息
* @param dbVersion
* 数据库版本信息
* @param dbUpdateListener
* 数据库升级监听器:如果监听器为null,升级的时候将会清空所所有的数据
* @return
*/
public static FinalDb create(Context context, String dbName,
boolean isDebug, int dbVersion, DbUpdateListener dbUpdateListener) {
DaoConfig config = new DaoConfig();
config.setContext(context);
config.setDbName(dbName);
config.setDebug(isDebug);
config.setDbVersion(dbVersion);
config.setDbUpdateListener(dbUpdateListener);
return create(config);
}
/**
*
* @param context
* 上下文
* @param targetDirectory
* db文件路径,可以配置为sdcard的路径
* @param dbName
* 数据库名字
* @param isDebug
* 是否是调试模式:调试模式会log出sql信息
* @param dbVersion
* 数据库版本信息
* @param dbUpdateListener数据库升级监听器
* :如果监听器为null,升级的时候将会清空所所有的数据
* @return
*/
public static FinalDb create(Context context, String targetDirectory,
String dbName, boolean isDebug, int dbVersion,
DbUpdateListener dbUpdateListener) {
DaoConfig config = new DaoConfig();
config.setContext(context);
config.setTargetDirectory(targetDirectory);
config.setDbName(dbName);
config.setDebug(isDebug);
config.setDbVersion(dbVersion);
config.setDbUpdateListener(dbUpdateListener);
return create(config);
}
/**
* 创建FinalDb
*
* @param daoConfig
* @return
*/
public static FinalDb create(DaoConfig daoConfig) {
return getInstance(daoConfig);
}
/**
* 保存数据库,速度要比save快
*
* @param entity
*/
public void save(Object entity) {
checkTableExist(entity.getClass());
exeSqlInfo(SqlBuilder.buildInsertSql(entity));
}
/**
* 保存数据到数据库<br />
* <b>注意:</b><br />
* 保存成功后,entity的主键将被赋值(或更新)为数据库的主键, 只针对自增长的id有效
*
* @param entity
* 要保存的数据
* @return ture: 保存成功 false:保存失败
*/
public boolean saveBindId(Object entity) {
checkTableExist(entity.getClass());
List<KeyValue> entityKvList = SqlBuilder
.getSaveKeyValueListByEntity(entity);
if (entityKvList != null && entityKvList.size() > 0) {
TableInfo tf = TableInfo.get(entity.getClass());
ContentValues cv = new ContentValues();
insertContentValues(entityKvList, cv);
Long id = db.insert(tf.getTableName(), null, cv);
if (id == -1)
return false;
tf.getId().setValue(entity, id);
return true;
}
return false;
}
/**
* 把List<KeyValue>数据存储到ContentValues
*
* @param list
* @param cv
*/
private void insertContentValues(List<KeyValue> list, ContentValues cv) {
if (list != null && cv != null) {
for (KeyValue kv : list) {
cv.put(kv.getKey(), kv.getValue().toString());
}
} else {
Log.w(TAG,
"insertContentValues: List<KeyValue> is empty or ContentValues is empty!");
}
}
/**
* 更新数据 (主键ID必须不能为空)
*
* @param entity
*/
public void update(Object entity) {
checkTableExist(entity.getClass());
exeSqlInfo(SqlBuilder.getUpdateSqlAsSqlInfo(entity));
}
/**
* 根据条件更新数据
*
* @param entity
* @param strWhere
* 条件为空的时候,将会更新所有的数据
*/
public void update(Object entity, String strWhere) {
checkTableExist(entity.getClass());
exeSqlInfo(SqlBuilder.getUpdateSqlAsSqlInfo(entity, strWhere));
}
/**
* 删除数据
*
* @param entity
* entity的主键不能为空
*/
public void delete(Object entity) {
checkTableExist(entity.getClass());
exeSqlInfo(SqlBuilder.buildDeleteSql(entity));
}
/**
* 根据主键删除数据
*
* @param clazz
* 要删除的实体类
* @param id
* 主键值
*/
public void deleteById(Class<?> clazz, Object id) {
checkTableExist(clazz);
exeSqlInfo(SqlBuilder.buildDeleteSql(clazz, id));
}
/**
* 根据条件删除数据
*
* @param clazz
* @param strWhere
* 条件为空的时候 将会删除所有的数据
*/
public void deleteByWhere(Class<?> clazz, String strWhere) {
checkTableExist(clazz);
String sql = SqlBuilder.buildDeleteSql(clazz, strWhere);
debugSql(sql);
db.execSQL(sql);
}
/**
* 删除表的所有数据
*
* @param clazz
*/
public void deleteAll(Class<?> clazz) {
checkTableExist(clazz);
String sql = SqlBuilder.buildDeleteSql(clazz, null);
debugSql(sql);
db.execSQL(sql);
}
/**
* 删除指定的表
*
* @param clazz
*/
public void dropTable(Class<?> clazz) {
checkTableExist(clazz);
TableInfo table = TableInfo.get(clazz);
String sql = "DROP TABLE " + table.getTableName();
debugSql(sql);
db.execSQL(sql);
}
/**
* 删除所有数据表
*/
public void dropDb() {
Cursor cursor = db.rawQuery(
"SELECT name FROM sqlite_master WHERE type ='table' AND name != 'sqlite_sequence'", null);
if (cursor != null) {
while (cursor.moveToNext()) {
db.execSQL("DROP TABLE " + cursor.getString(0));
}
}
if (cursor != null) {
cursor.close();
cursor = null;
}
}
private void exeSqlInfo(SqlInfo sqlInfo) {
if (sqlInfo != null) {
debugSql(sqlInfo.getSql());
db.execSQL(sqlInfo.getSql(), sqlInfo.getBindArgsAsArray());
} else {
Log.e(TAG, "sava error:sqlInfo is null");
}
}
/**
* 根据主键查找数据(默认不查询多对一或者一对多的关联数据)
*
* @param id
* @param clazz
*/
public <T> T findById(Object id, Class<T> clazz) {
checkTableExist(clazz);
SqlInfo sqlInfo = SqlBuilder.getSelectSqlAsSqlInfo(clazz, id);
if (sqlInfo != null) {
debugSql(sqlInfo.getSql());
Cursor cursor = db.rawQuery(sqlInfo.getSql(),
sqlInfo.getBindArgsAsStringArray());
try {
if (cursor.moveToNext()) {
return CursorUtils.getEntity(cursor, clazz, this);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
cursor.close();
}
}
return null;
}
/**
* 根据主键查找,同时查找“多对一”的数据(如果有多个“多对一”属性,则查找所有的“多对一”属性)
*
* @param id
* @param clazz
*/
public <T> T findWithManyToOneById(Object id, Class<T> clazz) {
checkTableExist(clazz);
String sql = SqlBuilder.getSelectSQL(clazz, id);
debugSql(sql);
DbModel dbModel = findDbModelBySQL(sql);
if (dbModel != null) {
T entity = CursorUtils.dbModel2Entity(dbModel, clazz);
return loadManyToOne(dbModel, entity, clazz);
}
return null;
}
/**
* 根据条件查找,同时查找“多对一”的数据(只查找findClass中的类的数据)
*
* @param id
* @param clazz
* @param findClass
* 要查找的类
*/
public <T> T findWithManyToOneById(Object id, Class<T> clazz,
Class<?>... findClass) {
checkTableExist(clazz);
String sql = SqlBuilder.getSelectSQL(clazz, id);
debugSql(sql);
DbModel dbModel = findDbModelBySQL(sql);
if (dbModel != null) {
T entity = CursorUtils.dbModel2Entity(dbModel, clazz);
return loadManyToOne(dbModel, entity, clazz, findClass);
}
return null;
}
/**
* 将entity中的“多对一”的数据填充满 如果是懒加载填充,则dbModel参数可为null
*
* @param clazz
* @param entity
* @param <T>
* @return
*/
public <T> T loadManyToOne(DbModel dbModel, T entity, Class<T> clazz,
Class<?>... findClass) {
if (entity != null) {
try {
Collection<ManyToOne> manys = TableInfo.get(clazz).manyToOneMap
.values();
for (ManyToOne many : manys) {
Object id = null;
if (dbModel != null) {
id = dbModel.get(many.getColumn());
} else if (many.getValue(entity).getClass() == ManyToOneLazyLoader.class
&& many.getValue(entity) != null) {
id = ((ManyToOneLazyLoader) many.getValue(entity))
.getFieldValue();
}
if (id != null) {
boolean isFind = false;
if (findClass == null || findClass.length == 0) {
isFind = true;
}
for (Class<?> mClass : findClass) {
if (many.getManyClass() == mClass) {
isFind = true;
break;
}
}
if (isFind) {
@SuppressWarnings("unchecked")
T manyEntity = (T) findById(
Integer.valueOf(id.toString()),
many.getManyClass());
if (manyEntity != null) {
if (many.getValue(entity).getClass() == ManyToOneLazyLoader.class) {
if (many.getValue(entity) == null) {
many.setValue(
entity,
new ManyToOneLazyLoader(entity,
clazz,
many.getManyClass(),
this));
}
((ManyToOneLazyLoader) many
.getValue(entity)).set(manyEntity);
} else {
many.setValue(entity, manyEntity);
}
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
return entity;
}
/**
* 根据主键查找,同时查找“一对多”的数据(如果有多个“一对多”属性,则查找所有的一对多”属性)
*
* @param id
* @param clazz
*/
public <T> T findWithOneToManyById(Object id, Class<T> clazz) {
checkTableExist(clazz);
String sql = SqlBuilder.getSelectSQL(clazz, id);
debugSql(sql);
DbModel dbModel = findDbModelBySQL(sql);
if (dbModel != null) {
T entity = CursorUtils.dbModel2Entity(dbModel, clazz);
return loadOneToMany(entity, clazz);
}
return null;
}
/**
* 根据主键查找,同时查找“一对多”的数据(只查找findClass中的“一对多”)
*
* @param id
* @param clazz
* @param findClass
*/
public <T> T findWithOneToManyById(Object id, Class<T> clazz,
Class<?>... findClass) {
checkTableExist(clazz);
String sql = SqlBuilder.getSelectSQL(clazz, id);
debugSql(sql);
DbModel dbModel = findDbModelBySQL(sql);
if (dbModel != null) {
T entity = CursorUtils.dbModel2Entity(dbModel, clazz);
return loadOneToMany(entity, clazz, findClass);
}
return null;
}
/**
* 将entity中的“一对多”的数据填充满
*
* @param entity
* @param clazz
* @param <T>
* @return
*/
public <T> T loadOneToMany(T entity, Class<T> clazz, Class<?>... findClass) {
if (entity != null) {
try {
Collection<OneToMany> ones = TableInfo.get(clazz).oneToManyMap
.values();
Object id = TableInfo.get(clazz).getId().getValue(entity);
for (OneToMany one : ones) {
boolean isFind = false;
if (findClass == null || findClass.length == 0) {
isFind = true;
}
for (Class<?> mClass : findClass) {
if (one.getOneClass() == mClass) {
isFind = true;
break;
}
}
if (isFind) {
List<?> list = findAllByWhere(one.getOneClass(),
one.getColumn() + "=" + id);
if (list != null) {
/* 如果是OneToManyLazyLoader泛型,则执行灌入懒加载数据 */
if (one.getDataType() == OneToManyLazyLoader.class) {
OneToManyLazyLoader oneToManyLazyLoader = one
.getValue(entity);
oneToManyLazyLoader.setList(list);
} else {
one.setValue(entity, list);
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
return entity;
}
/**
* 查找所有的数据
*
* @param clazz
*/
public <T> List<T> findAll(Class<T> clazz) {
checkTableExist(clazz);
return findAllBySql(clazz, SqlBuilder.getSelectSQL(clazz));
}
/**
* 查找所有数据
*
* @param clazz
* @param orderBy
* 排序的字段
*/
public <T> List<T> findAll(Class<T> clazz, String orderBy) {
checkTableExist(clazz);
return findAllBySql(clazz, SqlBuilder.getSelectSQL(clazz)
+ " ORDER BY " + orderBy);
}
/**
* 根据条件查找所有数据
*
* @param clazz
* @param strWhere
* 条件为空的时候查找所有数据
*/
public <T> List<T> findAllByWhere(Class<T> clazz, String strWhere) {
checkTableExist(clazz);
return findAllBySql(clazz,
SqlBuilder.getSelectSQLByWhere(clazz, strWhere));
}
/**
* 根据条件查找所有数据
*
* @param clazz
* @param strWhere
* 条件为空的时候查找所有数据
* @param orderBy
* 排序字段
*/
public <T> List<T> findAllByWhere(Class<T> clazz, String strWhere,
String orderBy) {
checkTableExist(clazz);
return findAllBySql(clazz,
SqlBuilder.getSelectSQLByWhere(clazz, strWhere) + " ORDER BY "
+ orderBy);
}
/**
* 根据条件查找所有数据
*
* @param clazz
* @param strSQL
*/
private <T> List<T> findAllBySql(Class<T> clazz, String strSQL) {
checkTableExist(clazz);
debugSql(strSQL);
Cursor cursor = db.rawQuery(strSQL, null);
try {
List<T> list = new ArrayList<T>();
while (cursor.moveToNext()) {
T t = CursorUtils.getEntity(cursor, clazz, this);
list.add(t);
}
return list;
} catch (Exception e) {
e.printStackTrace();
} finally {
if (cursor != null)
cursor.close();
cursor = null;
}
return null;
}
/**
* 根据sql语句查找数据,这个一般用于数据统计
*
* @param strSQL
*/
public DbModel findDbModelBySQL(String strSQL) {
debugSql(strSQL);
Cursor cursor = db.rawQuery(strSQL, null);
try {
if (cursor.moveToNext()) {
return CursorUtils.getDbModel(cursor);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
cursor.close();
}
return null;
}
public List<DbModel> findDbModelListBySQL(String strSQL) {
debugSql(strSQL);
Cursor cursor = db.rawQuery(strSQL, null);
List<DbModel> dbModelList = new ArrayList<DbModel>();
try {
while (cursor.moveToNext()) {
dbModelList.add(CursorUtils.getDbModel(cursor));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
cursor.close();
}
return dbModelList;
}
private void checkTableExist(Class<?> clazz) {
if (!tableIsExist(TableInfo.get(clazz))) {
String sql = SqlBuilder.getCreatTableSQL(clazz);
debugSql(sql);
db.execSQL(sql);
}
}
private boolean tableIsExist(TableInfo table) {
if (table.isCheckDatabese())
return true;
Cursor cursor = null;
try {
String sql = "SELECT COUNT(*) AS c FROM sqlite_master WHERE type ='table' AND name ='"
+ table.getTableName() + "' ";
debugSql(sql);
cursor = db.rawQuery(sql, null);
if (cursor != null && cursor.moveToNext()) {
int count = cursor.getInt(0);
if (count > 0) {
table.setCheckDatabese(true);
return true;
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (cursor != null)
cursor.close();
cursor = null;
}
return false;
}
private void debugSql(String sql) {
if (config != null && config.isDebug())
android.util.Log.d("Debug SQL", ">>>>>> " + sql);
}
public static class DaoConfig {
private Context mContext = null; // android上下文
private String mDbName = "afinal.db"; // 数据库名字
private int dbVersion = 1; // 数据库版本
private boolean debug = true; // 是否是调试模式(调试模式 增删改查的时候显示SQL语句)
private DbUpdateListener dbUpdateListener;
// private boolean saveOnSDCard = false;//是否保存到SD卡
private String targetDirectory;// 数据库文件在sd卡中的目录
public Context getContext() {
return mContext;
}
public void setContext(Context context) {
this.mContext = context;
}
public String getDbName() {
return mDbName;
}
public void setDbName(String dbName) {
this.mDbName = dbName;
}
public int getDbVersion() {
return dbVersion;
}
public void setDbVersion(int dbVersion) {
this.dbVersion = dbVersion;
}
public boolean isDebug() {
return debug;
}
public void setDebug(boolean debug) {
this.debug = debug;
}
public DbUpdateListener getDbUpdateListener() {
return dbUpdateListener;
}
public void setDbUpdateListener(DbUpdateListener dbUpdateListener) {
this.dbUpdateListener = dbUpdateListener;
}
// public boolean isSaveOnSDCard() {
// return saveOnSDCard;
// }
//
// public void setSaveOnSDCard(boolean saveOnSDCard) {
// this.saveOnSDCard = saveOnSDCard;
// }
public String getTargetDirectory() {
return targetDirectory;
}
public void setTargetDirectory(String targetDirectory) {
this.targetDirectory = targetDirectory;
}
}
/**
* 在SD卡的指定目录上创建文件
*
* @param sdcardPath
* @param dbfilename
* @return
*/
private SQLiteDatabase createDbFileOnSDCard(String sdcardPath,
String dbfilename) {
File dbf = new File(sdcardPath, dbfilename);
if (!dbf.exists()) {
try {
if (dbf.createNewFile()) {
return SQLiteDatabase.openOrCreateDatabase(dbf, null);
}
} catch (IOException ioex) {
throw new DbException("数据库文件创建失败", ioex);
}
} else {
return SQLiteDatabase.openOrCreateDatabase(dbf, null);
}
return null;
}
class SqliteDbHelper extends SQLiteOpenHelper {
private DbUpdateListener mDbUpdateListener;
public SqliteDbHelper(Context context, String name, int version,
DbUpdateListener dbUpdateListener) {
super(context, name, null, version);
this.mDbUpdateListener = dbUpdateListener;
}
@Override
public void onCreate(SQLiteDatabase db) {
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (mDbUpdateListener != null) {
mDbUpdateListener.onUpgrade(db, oldVersion, newVersion);
} else { // 清空所有的数据信息
dropDb();
}
}
}
public interface DbUpdateListener {
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion);
}
}
注释了方法的意义,数据库的配置由DaoConfig类进行管理,数据表的存储是通过HashMap键值对体现的最终调用如下构造:
private FinalDb(DaoConfig config) {
if (config == null)
throw new DbException("daoConfig is null");
if (config.getContext() == null)
throw new DbException("android context is null");
if (config.getTargetDirectory() != null
&& config.getTargetDirectory().trim().length() > 0) {
this.db = createDbFileOnSDCard(config.getTargetDirectory(),
config.getDbName());
} else {
this.db = new SqliteDbHelper(config.getContext()
.getApplicationContext(), config.getDbName(),
config.getDbVersion(), config.getDbUpdateListener())
.getWritableDatabase();
}
this.config = config;
}
先来看createDbFileOnSDCard方法,发现就是在SD卡上一个filepath下面创建一张表,底层仍然是通过File dbf = new File(sdcardPath, dbfilename); ..... return SQLiteDatabase.openOrCreateDatabase(dbf, null);实现的。
再看如果表存在,则通过SqliteDbHelper封装了一层SQLiteOpenHelper调用父类的方法。
class SqliteDbHelper extends SQLiteOpenHelper {
private DbUpdateListener mDbUpdateListener;
public SqliteDbHelper(Context context, String name, int version,
DbUpdateListener dbUpdateListener) {
super(context, name, null, version);
this.mDbUpdateListener = dbUpdateListener;
}
@Override
public void onCreate(SQLiteDatabase db) {
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (mDbUpdateListener != null) {
mDbUpdateListener.onUpgrade(db, oldVersion, newVersion);
} else { // 清空所有的数据信息
dropDb();
}
}
}
public interface DbUpdateListener {
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion);
}
数据库的操作封装在了SqlBuilder类里面
package net.tsz.afinal.db.sqlite;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import android.text.TextUtils;
import net.tsz.afinal.db.table.Id;
import net.tsz.afinal.db.table.KeyValue;
import net.tsz.afinal.db.table.ManyToOne;
import net.tsz.afinal.db.table.Property;
import net.tsz.afinal.db.table.TableInfo;
import net.tsz.afinal.exception.DbException;
public class SqlBuilder {
/**
* 获取插入的sql语句
* @return
*/
public static SqlInfo buildInsertSql(Object entity){
List<KeyValue> keyValueList = getSaveKeyValueListByEntity(entity);
StringBuffer strSQL=new StringBuffer();
SqlInfo sqlInfo = null;
if(keyValueList!=null && keyValueList.size()>0){
sqlInfo = new SqlInfo();
strSQL.append("INSERT INTO ");
strSQL.append(TableInfo.get(entity.getClass()).getTableName());
strSQL.append(" (");
for(KeyValue kv : keyValueList){
strSQL.append(kv.getKey()).append(",");
sqlInfo.addValue(kv.getValue());
}
strSQL.deleteCharAt(strSQL.length() - 1);
strSQL.append(") VALUES ( ");
int length = keyValueList.size();
for(int i =0 ; i < length;i++){
strSQL.append("?,");
}
strSQL.deleteCharAt(strSQL.length() - 1);
strSQL.append(")");
sqlInfo.setSql(strSQL.toString());
}
return sqlInfo;
}
public static List<KeyValue> getSaveKeyValueListByEntity(Object entity){
List<KeyValue> keyValueList = new ArrayList<KeyValue>();
TableInfo table=TableInfo.get(entity.getClass());
Object idvalue = table.getId().getValue(entity);
if(!(idvalue instanceof Integer)){ //用了非自增长,添加id , 采用自增长就不需要添加id了
if(idvalue instanceof String && idvalue != null){
KeyValue kv = new KeyValue(table.getId().getColumn(),idvalue);
keyValueList.add(kv);
}
}
//添加属性
Collection<Property> propertys = table.propertyMap.values();
for(Property property : propertys){
KeyValue kv = property2KeyValue(property,entity) ;
if(kv!=null)
keyValueList.add(kv);
}
//添加外键(多对一)
Collection<ManyToOne> manyToOnes = table.manyToOneMap.values();
for(ManyToOne many:manyToOnes){
KeyValue kv = manyToOne2KeyValue(many,entity);
if(kv!=null) keyValueList.add(kv);
}
return keyValueList;
}
private static String getDeleteSqlBytableName(String tableName){
return "DELETE FROM "+ tableName;
}
public static SqlInfo buildDeleteSql(Object entity){
TableInfo table=TableInfo.get(entity.getClass());
Id id = table.getId();
Object idvalue = id.getValue(entity);
if(idvalue == null ){
throw new DbException("getDeleteSQL:"+entity.getClass()+" id value is null");
}
StringBuffer strSQL = new StringBuffer(getDeleteSqlBytableName(table.getTableName()));
strSQL.append(" WHERE ").append(id.getColumn()).append("=?");
SqlInfo sqlInfo = new SqlInfo();
sqlInfo.setSql(strSQL.toString());
sqlInfo.addValue(idvalue);
return sqlInfo;
}
public static SqlInfo buildDeleteSql(Class<?> clazz , Object idValue){
TableInfo table=TableInfo.get(clazz);
Id id=table.getId();
if(null == idValue) {
throw new DbException("getDeleteSQL:idValue is null");
}
StringBuffer strSQL = new StringBuffer(getDeleteSqlBytableName(table.getTableName()));
strSQL.append(" WHERE ").append(id.getColumn()).append("=?");
SqlInfo sqlInfo = new SqlInfo();
sqlInfo.setSql(strSQL.toString());
sqlInfo.addValue(idValue);
return sqlInfo;
}
/**
* 根据条件删除数据 ,条件为空的时候将会删除所有的数据
* @param clazz
* @param strWhere
* @return
*/
public static String buildDeleteSql(Class<?> clazz , String strWhere){
TableInfo table=TableInfo.get(clazz);
StringBuffer strSQL = new StringBuffer(getDeleteSqlBytableName(table.getTableName()));
if(!TextUtils.isEmpty(strWhere)){
strSQL.append(" WHERE ");
strSQL.append(strWhere);
}
return strSQL.toString();
}
select sql start///
private static String getSelectSqlByTableName(String tableName){
return new StringBuffer("SELECT * FROM ").append(tableName).toString();
}
public static String getSelectSQL(Class<?> clazz,Object idValue){
TableInfo table=TableInfo.get(clazz);
StringBuffer strSQL = new StringBuffer(getSelectSqlByTableName(table.getTableName()));
strSQL.append(" WHERE ");
strSQL.append(getPropertyStrSql(table.getId().getColumn(), idValue));
return strSQL.toString();
}
public static SqlInfo getSelectSqlAsSqlInfo(Class<?> clazz,Object idValue){
TableInfo table=TableInfo.get(clazz);
StringBuffer strSQL = new StringBuffer(getSelectSqlByTableName(table.getTableName()));
strSQL.append(" WHERE ").append(table.getId().getColumn()).append("=?");
SqlInfo sqlInfo = new SqlInfo();
sqlInfo.setSql(strSQL.toString());
sqlInfo.addValue(idValue);
return sqlInfo;
}
public static String getSelectSQL(Class<?> clazz){
return getSelectSqlByTableName(TableInfo.get(clazz).getTableName());
}
public static String getSelectSQLByWhere(Class<?> clazz,String strWhere){
TableInfo table=TableInfo.get(clazz);
StringBuffer strSQL = new StringBuffer(getSelectSqlByTableName(table.getTableName()));
if(!TextUtils.isEmpty(strWhere)){
strSQL.append(" WHERE ").append(strWhere);
}
return strSQL.toString();
}
//update sql start/
public static SqlInfo getUpdateSqlAsSqlInfo(Object entity){
TableInfo table=TableInfo.get(entity.getClass());
Object idvalue=table.getId().getValue(entity);
if(null == idvalue ) {//主键值不能为null,否则不能更新
throw new DbException("this entity["+entity.getClass()+"]'s id value is null");
}
List<KeyValue> keyValueList = new ArrayList<KeyValue>();
//添加属性
Collection<Property> propertys = table.propertyMap.values();
for(Property property : propertys){
KeyValue kv = property2KeyValue(property,entity) ;
if(kv!=null)
keyValueList.add(kv);
}
//添加外键(多对一)
Collection<ManyToOne> manyToOnes = table.manyToOneMap.values();
for(ManyToOne many:manyToOnes){
KeyValue kv = manyToOne2KeyValue(many,entity);
if(kv!=null) keyValueList.add(kv);
}
if(keyValueList == null || keyValueList.size()==0) return null ;
SqlInfo sqlInfo = new SqlInfo();
StringBuffer strSQL=new StringBuffer("UPDATE ");
strSQL.append(table.getTableName());
strSQL.append(" SET ");
for(KeyValue kv : keyValueList){
strSQL.append(kv.getKey()).append("=?,");
sqlInfo.addValue(kv.getValue());
}
strSQL.deleteCharAt(strSQL.length() - 1);
strSQL.append(" WHERE ").append(table.getId().getColumn()).append("=?");
sqlInfo.addValue(idvalue);
sqlInfo.setSql(strSQL.toString());
return sqlInfo;
}
public static SqlInfo getUpdateSqlAsSqlInfo(Object entity,String strWhere){
TableInfo table=TableInfo.get(entity.getClass());
List<KeyValue> keyValueList = new ArrayList<KeyValue>();
//添加属性
Collection<Property> propertys = table.propertyMap.values();
for(Property property : propertys){
KeyValue kv = property2KeyValue(property,entity) ;
if(kv!=null) keyValueList.add(kv);
}
//添加外键(多对一)
Collection<ManyToOne> manyToOnes = table.manyToOneMap.values();
for(ManyToOne many:manyToOnes){
KeyValue kv = manyToOne2KeyValue(many,entity);
if(kv!=null) keyValueList.add(kv);
}
if(keyValueList == null || keyValueList.size()==0) {
throw new DbException("this entity["+entity.getClass()+"] has no property");
}
SqlInfo sqlInfo = new SqlInfo();
StringBuffer strSQL=new StringBuffer("UPDATE ");
strSQL.append(table.getTableName());
strSQL.append(" SET ");
for(KeyValue kv : keyValueList){
strSQL.append(kv.getKey()).append("=?,");
sqlInfo.addValue(kv.getValue());
}
strSQL.deleteCharAt(strSQL.length() - 1);
if(!TextUtils.isEmpty(strWhere)){
strSQL.append(" WHERE ").append(strWhere);
}
sqlInfo.setSql(strSQL.toString());
return sqlInfo;
}
public static String getCreatTableSQL(Class<?> clazz){
TableInfo table=TableInfo.get(clazz);
Id id=table.getId();
StringBuffer strSQL = new StringBuffer();
strSQL.append("CREATE TABLE IF NOT EXISTS ");
strSQL.append(table.getTableName());
strSQL.append(" ( ");
Class<?> primaryClazz = id.getDataType();
if( primaryClazz == int.class || primaryClazz==Integer.class
|| primaryClazz == long.class || primaryClazz == Long.class){
strSQL.append(id.getColumn()).append(" INTEGER PRIMARY KEY AUTOINCREMENT,");
}else{
strSQL.append(id.getColumn()).append(" TEXT PRIMARY KEY,");
}
Collection<Property> propertys = table.propertyMap.values();
for(Property property : propertys){
strSQL.append(property.getColumn());
Class<?> dataType = property.getDataType();
if( dataType== int.class || dataType == Integer.class
|| dataType == long.class || dataType == Long.class){
strSQL.append(" INTEGER");
}else if(dataType == float.class ||dataType == Float.class
||dataType == double.class || dataType == Double.class){
strSQL.append(" REAL");
}else if (dataType == boolean.class || dataType == Boolean.class) {
strSQL.append(" NUMERIC");
}
strSQL.append(",");
}
Collection<ManyToOne> manyToOnes = table.manyToOneMap.values();
for(ManyToOne manyToOne : manyToOnes){
strSQL.append(manyToOne.getColumn())
.append(" INTEGER")
.append(",");
}
strSQL.deleteCharAt(strSQL.length() - 1);
strSQL.append(" )");
return strSQL.toString();
}
/**
* @param key
* @param value
* @return eg1: name='afinal' eg2: id=100
*/
private static String getPropertyStrSql(String key,Object value){
StringBuffer sbSQL = new StringBuffer(key).append("=");
if(value instanceof String || value instanceof java.util.Date || value instanceof java.sql.Date){
sbSQL.append("'").append(value).append("'");
}else{
sbSQL.append(value);
}
return sbSQL.toString();
}
private static KeyValue property2KeyValue(Property property , Object entity){
KeyValue kv = null ;
String pcolumn=property.getColumn();
Object value = property.getValue(entity);
if(value!=null){
kv = new KeyValue(pcolumn, value);
}else{
if(property.getDefaultValue()!=null && property.getDefaultValue().trim().length()!=0)
kv = new KeyValue(pcolumn, property.getDefaultValue());
}
return kv;
}
private static KeyValue manyToOne2KeyValue(ManyToOne many , Object entity){
KeyValue kv = null ;
String manycolumn=many.getColumn();
Object manyobject=many.getValue(entity);
if(manyobject!=null){
Object manyvalue;
if(manyobject.getClass()==ManyToOneLazyLoader.class){
manyvalue = TableInfo.get(many.getManyClass()).getId().getValue(((ManyToOneLazyLoader)manyobject).get());
}else{
manyvalue = TableInfo.get(manyobject.getClass()).getId().getValue(manyobject);
}
if(manycolumn!=null && manyvalue!=null){
kv = new KeyValue(manycolumn, manyvalue);
}
}
return kv;
}
}
同时提供了一对多,多对一的加载模式,分别是ManyToOneLazyLoader和OneToManyLazyLoader,表的映射关系在table包下面
基本就是这样,另外
处理相关异常,其实也都是交给了RuntimeException处理
文章就是这样,还在学习的道路上,不足和错误之处还请谅解,请在回复区指正,非常感谢!!!