实例模仿新版QQ相册功能,RecyclerView实现相册选择,DiskLruCache实现图片缓存,ItemTouchHelper实现图片的拖拽排序,单线程轮播解决加载大量图片卡顿问题(参考:http://blog.csdn.net/lishengko/article/details/56498844)。
BaseActivity 相册功能比较常用,单独封装了下
public abstract class BaseActivity extends AppCompatActivity {protected RecyclerView mRecyclerView;protected CameraAdapter mAdapter;protected ItemTouchHelper mItemTouchHelper;protected List<PhotoItem> photoItemList;protected ImageButton mSubmit;protected CameraDialogFragment dialogFragment;protected int photoLayout[];protected void init(int layoutId){setContentView(layoutId);mSubmit = (ImageButton)findViewById(R.id.submit);mRecyclerView = (RecyclerView) findViewById(R.id.list);mRecyclerView.setLayoutManager(new GridLayoutManager(this, 3));mRecyclerView.setHasFixedSize(true);photoItemList = new ArrayList<>();photoItemList.add(new PhotoItem(-1,"", ""));photoLayout = new int[2];photoLayout[0]=(ScreenHelper.getScreenWidth(this) - ScreenHelper.dp2px(this, 20)) / 3 - ScreenHelper.dp2px(this, 10);photoLayout[1] = ScreenHelper.dp2px(this, 110);mAdapter = new CameraAdapter(mRecyclerView,photoItemList,photoLayout);mRecyclerView.setAdapter(mAdapter);ItemTouchHelper.Callback callback = new DragItemTouchHelperCallback(mAdapter);mItemTouchHelper = new ItemTouchHelper(callback);mItemTouchHelper.attachToRecyclerView(mRecyclerView);mAdapter.setOnItemClickListener(new OnItemClickListener() {@Overridepublic void onItemClick(RecyclerView.ViewHolder viewHolder, int position) {//添加图片if(viewHolder.getItemViewType()==0){showCameraDialog();}//图片明细else {}}});}public abstract void onSubmit(View view);public void onBack(View view){finish();}protected void showCameraDialog(){if(dialogFragment==null){dialogFragment = new CameraDialogFragment();dialogFragment.setOnItemClickListener(new OnItemClickListener() {@Overridepublic void onItemClick(RecyclerView.ViewHolder viewHolder, int position) {//拍照if(position==0)BaseActivity.this.startActivityForResult(new Intent(BaseActivity.this, CameraActivity.class), 1);//相册else{Intent intent = new Intent(BaseActivity.this, AlbumActivity.class);List<PhotoItem> _photoItemList = new ArrayList<>();if(photoItemList.size()>1)_photoItemList.addAll(photoItemList.subList(0,photoItemList.size()-1));intent.putExtra(FinalHelper.IMAGE_PATH,(Serializable)_photoItemList);BaseActivity.this.startActivityForResult(intent, 2);}}});dialogFragment.show(getFragmentManager(),"dialog");}elsedialogFragment.show(getFragmentManager(),"dialog");}@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) {if(resultCode== Activity.RESULT_OK){int start=photoItemList.size()-1;//拍照if(requestCode== FinalHelper.RequestCode_Camera){String path = data.getStringExtra(FinalHelper.IMAGE_PATH);photoItemList.add(start,new PhotoItem(0,"",path));//添加缓存CacheManager.put(path, ImageHelper.getSmallCropBitmap(path, photoLayout[0], photoLayout[1]));mAdapter.notifyItemInserted(start);ImageHelper.deleteTempleFile();}//相册else {List<PhotoItem> _photoItemList = (List<PhotoItem>)data.getSerializableExtra(FinalHelper.IMAGE_PATH);if(_photoItemList!=null && _photoItemList.size()!=0){for(int i=0;i<photoItemList.size();i++){if(photoItemList.get(i).getPhotoID()!=-1){photoItemList.remove(i--);}}photoItemList.addAll(0,_photoItemList);mAdapter.notifyDataSetChanged();}}}}
}
RepairActivity 实例Activity
public class RepairActivity extends BaseActivity {private EditText mEditText;@Overridepublic void onSubmit(View view) {}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);init(R.layout.activity_repair);mEditText = (EditText)findViewById(R.id.content);mEditText.addTextChangedListener(new TextWatcher() {@Overridepublic void beforeTextChanged(CharSequence s, int start, int count, int after) {}@Overridepublic void onTextChanged(CharSequence s, int start, int before, int count) {}@Overridepublic void afterTextChanged(Editable s) {if (s.toString().length() != 0) {mSubmit.setClickable(true);mSubmit.setAlpha(1f);} else {mSubmit.setClickable(false);mSubmit.setAlpha(0.7f);}}});}
}
AlbumActivity 相册功能
public class AlbumActivity extends AppCompatActivity{private RecyclerView mRecyclerView;private List<PhotoItem> photoItemList;private List<PhotoItem> selectItemList;private int[] photoLayout;private AlbumAdapter mAdapter;private Handler handler;private Button submit;private GridLayoutManager gridLayoutManager;private static final String[] STORE_IMAGES = {MediaStore.Images.Media.DISPLAY_NAME, // 显示的名字MediaStore.Images.Media.LATITUDE, // 维度MediaStore.Images.Media.LONGITUDE, // 经度MediaStore.Images.Media._ID, // idMediaStore.Images.Media.BUCKET_ID, // dir id 目录MediaStore.Images.Media.BUCKET_DISPLAY_NAME, // dir name 目录名字MediaStore.Images.Media.DATA//路径};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_album);submit = (Button)findViewById(R.id.submit);mRecyclerView = (RecyclerView)findViewById(R.id.list);gridLayoutManager = new GridLayoutManager(this, 3);mRecyclerView.setLayoutManager(gridLayoutManager);mRecyclerView.setHasFixedSize(true);photoLayout = new int[2];photoLayout[0]=(ScreenHelper.getScreenWidth(this) - ScreenHelper.dp2px(this, 20)) / 3 - ScreenHelper.dp2px(this, 10);photoLayout[1] = ScreenHelper.dp2px(this, 110);gridLayoutManager.findFirstCompletelyVisibleItemPosition();initPhoto();notifySubmit();mAdapter = new AlbumAdapter(mRecyclerView,photoItemList,selectItemList);mAdapter.setOnItemClickListener(new OnItemClickListener() {@Overridepublic void onItemClick(RecyclerView.ViewHolder viewHolder, int position) {notifySubmit();}});mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {@Overridepublic void onScrollStateChanged(RecyclerView recyclerView, int newState) {//停止滑动后缓存当前屏幕图片if(newState == 0){new Thread(new Runnable() {@Overridepublic void run() {for(int i=gridLayoutManager.findFirstVisibleItemPosition();i<gridLayoutManager.findLastVisibleItemPosition();i++){cacheBitmap(i);}}}).start();}}});mRecyclerView.setAdapter(mAdapter);handler = new Handler(new Handler.Callback() {@Overridepublic boolean handleMessage(Message msg) {mAdapter.notifyImage((int)msg.obj);return false;}});}private void initPhoto(){photoItemList = new ArrayList<>();selectItemList = (List<PhotoItem>)getIntent().getSerializableExtra(FinalHelper.IMAGE_PATH);if(selectItemList == null)selectItemList = new ArrayList<>();String path="";for(int i=0;i<selectItemList.size();i++){path += selectItemList.get(i).getPath()+",";}Cursor cursor = MediaStore.Images.Media.query(getContentResolver(), MediaStore.Images.Media.EXTERNAL_CONTENT_URI, STORE_IMAGES,"width >0","date_modified desc");cursor.moveToNext();for (int i = 0; i < cursor.getCount(); i++) {PhotoItem photoItem = new PhotoItem(cursor.getInt(3),path.indexOf(cursor.getString(6))!=-1?true:false,cursor.getString(0),cursor.getString(6));photoItemList.add(photoItem);cursor.moveToNext();}//从前往后存缓存缩略图 单线程轮播减少内存消耗确保屏幕不卡顿new Thread(new Runnable() {@Overridepublic void run() {for(int i=0;i<photoItemList.size();i++){cacheBitmap(i);}}}).start();}//缓存bitmappublic void cacheBitmap(int position){if(CacheManager.get(photoItemList.get(position).getPath())==null){Bitmap bitmap = ImageHelper.getSmallCropBitmap(photoItemList.get(position).getPath(), photoLayout[0], photoLayout[1]);CacheManager.put(photoItemList.get(position).getPath(),bitmap);Message message = new Message();message.obj=position;handler.sendMessage(message);}}public void notifySubmit(){if(selectItemList.size()>0){submit.setBackgroundResource(R.drawable.bg_album_submit_enable);submit.setText(getString(R.string.submit) + "(" + selectItemList.size() + ")");submit.setTextColor(ContextCompat.getColor(this, R.color.white));submit.setAlpha(1f);submit.setClickable(true);}else {submit.setBackgroundResource(R.drawable.bg_album_submit);submit.setTextColor(ContextCompat.getColor(this, R.color.darkgray));submit.setAlpha(.8f);submit.setText(getString(R.string.submit));submit.setClickable(false);}}public void onSubmit(View view){Intent intent = new Intent();intent.putExtra(FinalHelper.IMAGE_PATH, (Serializable) selectItemList);setResult(Activity.RESULT_OK, intent);finish();}public void onBack(View view){finish();}public void onCancel(View view){finish();}}
CameraActivity 相机拍照
public class CameraActivity extends Activity {private String mImageFilePath;public String IMAGEFILEPATH = "ImageFilePath";@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);//判断 activity被销毁后 有没有数据被保存下来if (savedInstanceState != null) {mImageFilePath = savedInstanceState.getString(IMAGEFILEPATH);File mFile = new File(IMAGEFILEPATH);if (mFile.exists()) {Intent intent = new Intent();intent.putExtra(FinalHelper.IMAGE_PATH, mImageFilePath);setResult(Activity.RESULT_OK, intent);} else {Toast.makeText(this, "图片拍摄失败", Toast.LENGTH_LONG).show();}insertToCamera();finish();}elseinitialUI();}public void initialUI() {File cameraFile = null;Date date = new Date();try {String dir = FileHelper.getImageFilePath();if(!dir.isEmpty()){cameraFile = new File(dir+File.separator+date.getTime()+".jpg");cameraFile.createNewFile();}} catch (IOException e) {e.printStackTrace();}if(cameraFile !=null){mImageFilePath = cameraFile.getPath();Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(cameraFile)); // setstartActivityForResult(intent, FinalHelper.RequestCode_Camera);}else{Toast.makeText(this, "未发现SD卡或SD卡不可用", Toast.LENGTH_LONG).show();finish();}}//插入到系统相册private void insertToCamera(){new Thread(new Runnable() {@Overridepublic void run() {File file = new File(mImageFilePath);try {MediaStore.Images.Media.insertImage(getContentResolver(), file.getAbsolutePath(), file.getName(), null);sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(file)));}catch (Exception e){e.printStackTrace();}}}).start();}@Overridepublic void onActivityResult(int requestCode, int resultCode, Intent intent) {if (FinalHelper.RequestCode_Camera == requestCode && resultCode == Activity.RESULT_OK) {Intent rsl = new Intent();rsl.putExtra(FinalHelper.IMAGE_PATH, mImageFilePath);setResult(Activity.RESULT_OK, rsl);insertToCamera();finish();} else {finish();}}
}
AlbumAdapter(AlbumActivity) 相册适配器
public class AlbumAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>{private List<PhotoItem> photoItemList;private List<PhotoItem> selectItemList;private RecyclerView mRecyclerView;private OnItemClickListener onItemClickListener;public AlbumAdapter(RecyclerView recyclerView,List<PhotoItem> photoItemList,List<PhotoItem> selectItemList){this.photoItemList = photoItemList;this.selectItemList = selectItemList;this.mRecyclerView = recyclerView;}public void setOnItemClickListener(OnItemClickListener onItemClickListener) {this.onItemClickListener = onItemClickListener;}//更新图片public void notifyImage(int position){if(mRecyclerView.findViewWithTag(position)!=null){ImageView imageView = (ImageView)mRecyclerView.findViewWithTag(position);imageView.setImageBitmap(CacheManager.get(photoItemList.get(position).getPath()));imageView.setVisibility(View.VISIBLE);}}@Overridepublic RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.album_list_photo, parent, false);AlbumViewHolder viewHolder = new AlbumViewHolder(view);return viewHolder;}@Overridepublic void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {final AlbumViewHolder viewHolder = (AlbumViewHolder)holder;viewHolder.photo.setVisibility(View.GONE);viewHolder.photo.setTag(position);viewHolder.select.setTag(photoItemList.get(position).getPath());setSelectBackground(viewHolder, photoItemList.get(position));Bitmap bitmap = CacheManager.get(photoItemList.get(position).getPath());if(bitmap!=null){viewHolder.photo.setImageBitmap(bitmap);viewHolder.photo.setVisibility(View.VISIBLE);}viewHolder.photo.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {photoItemList.get(position).setSelect(!photoItemList.get(position).isSelect());if (photoItemList.get(position).isSelect()) {selectItemList.add(photoItemList.get(position));} else {//selectItemList.remove(ListHelper.getIndex(selectItemList,photoItemList.))removeSelectItem(photoItemList.get(position));}setSelectBackground(viewHolder, photoItemList.get(position));setAllSelectBackground();onItemClickListener.onItemClick(viewHolder, position);}});}//更新当前照片索引private void setSelectBackground(AlbumViewHolder viewHolder,PhotoItem photoItem){viewHolder.select.setBackgroundResource(photoItem.isSelect() ? R.drawable.bg_album_pressed : R.drawable.bg_album);viewHolder.select.setText(photoItem.isSelect() ? String.valueOf(getSelectIndex(photoItem)) : "");viewHolder.select.setAlpha(photoItem.isSelect() ? 1.0f : 0.8f);}//更新所有照片索引private void setAllSelectBackground(){for(int i=0;i<selectItemList.size();i++){View view = mRecyclerView.findViewWithTag(selectItemList.get(i).getPath());if(view!=null){((TextView)view).setText(String.valueOf(i+1));}}}//获取选中图片索引private int getSelectIndex(PhotoItem photoItem){for(int i=0;i<selectItemList.size();i++){if(selectItemList.get(i).getPath().equals(photoItem.getPath()))return i+1;}return -1;}//删除选中图片private void removeSelectItem(PhotoItem photoItem){for(int i=0;i<selectItemList.size();i++){if(selectItemList.get(i).getPath().equals(photoItem.getPath())){selectItemList.remove(i);break;}}}@Overridepublic int getItemCount() {return photoItemList.size();}static class AlbumViewHolder extends RecyclerView.ViewHolder{private ImageView photo;private TextView select;public AlbumViewHolder(View view) {super(view);photo = (ImageView)view.findViewById(R.id.item_photo);select = (TextView)view.findViewById(R.id.item_text);}}
}
CameraAdapter(RepairActivity) 实例选中图片列表适配器
public class CameraAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> implements DragItemTouchHelperAdapter{final int VIEW_TYPE_PHOTO = 1;final int VIEW_TYPE_CAMERA = 0;private boolean pressed = false;private int photoLayout[];private RecyclerView mRecyclerView;private List<PhotoItem> photoItemList;private OnItemClickListener onItemClickListener;private Context mContext;public void setOnItemClickListener(OnItemClickListener onItemClickListener) {this.onItemClickListener = onItemClickListener;}public CameraAdapter(RecyclerView recyclerView, List<PhotoItem> photoItemList, int[] photoLayout){this.mRecyclerView = recyclerView;this.photoItemList = photoItemList;this.photoLayout = photoLayout;this.mContext = recyclerView.getContext();}public void OnItemSelected() {if(!pressed){Vibrator vibrator = (Vibrator)mContext.getSystemService(mContext.VIBRATOR_SERVICE);vibrator.vibrate(500);pressed = true;for(int i=0;i<photoItemList.size()-1;i++){View view = mRecyclerView.findViewWithTag(i);if(view!=null){view.setVisibility(View.VISIBLE);} }}}@Overridepublic int getItemViewType(int position) {if(position==photoItemList.size()-1)return VIEW_TYPE_CAMERA;elsereturn VIEW_TYPE_PHOTO;}@Overridepublic RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {View view = LayoutInflater.from(mContext).inflate(viewType==0?R.layout.repair_list_camera:R.layout.repair_list_photo, parent, false);RecyclerView.ViewHolder viewHolder = viewType==0?new CameraVieHolder(view):new PhotoViewHolder(view);return viewHolder;}@Overridepublic void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) {if(holder.getItemViewType()==0){CameraVieHolder viewHolder = (CameraVieHolder)holder;viewHolder.camera.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {onItemClickListener.onItemClick(holder,holder.getAdapterPosition());}});}else{PhotoViewHolder viewHolder = (PhotoViewHolder)holder;Bitmap bitmap = CacheManager.get(photoItemList.get(position).getPath());if(bitmap==null){bitmap=ImageHelper.getSmallCropBitmap(photoItemList.get(position).getPath(), photoLayout[0],photoLayout[1]);CacheManager.put(photoItemList.get(position).getPath(),ImageHelper.getSmallCropBitmap(photoItemList.get(position).getPath(), photoLayout[0],photoLayout[1]));}viewHolder.photo.setImageBitmap(bitmap);viewHolder.delete.setTag(position);viewHolder.delete.setVisibility(pressed?View.VISIBLE:View.GONE);viewHolder.delete.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {onItemDismiss(holder.getAdapterPosition());}});viewHolder.photo.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {onItemClickListener.onItemClick(holder,holder.getAdapterPosition());}});}}@Overridepublic void onItemDismiss(int position) {photoItemList.remove(position);notifyItemRemoved(position);}@Overridepublic boolean onItemMove(int fromPosition, int toPosition) {Collections.swap(photoItemList, fromPosition, toPosition);notifyItemMoved(fromPosition, toPosition);return true;}@Overridepublic int getItemCount() {return photoItemList.size();}class PhotoViewHolder extends RecyclerView.ViewHolder implements ItemTouchHelperViewHolder{RelativeLayout container;ImageView photo;LinearLayout delete;public PhotoViewHolder(View view) {super(view);container = (RelativeLayout)view.findViewById(R.id.item_container);photo = (ImageView)view.findViewById(R.id.item_photo);delete = (LinearLayout)view.findViewById(R.id.item_delete);}//选中放大动画@Overridepublic void onItemSelected() {RelativeLayout container = (RelativeLayout)itemView.findViewById(R.id.item_container);Animation animation = AnimationUtils.loadAnimation(mContext,R.anim.enlarge);container.startAnimation(animation);OnItemSelected();}//取消缩小动画@Overridepublic void onItemClear() {RelativeLayout container = (RelativeLayout)itemView.findViewById(R.id.item_container);Animation animation = AnimationUtils.loadAnimation(mContext,R.anim.narrow);container.startAnimation(animation);}}class CameraVieHolder extends RecyclerView.ViewHolder{LinearLayout camera;public CameraVieHolder(View view){super(view);camera = (LinearLayout)view.findViewById(R.id.item_camera);}}
}
DragItemTouchHelperCallback 拖拽排序动画
public class DragItemTouchHelperCallback extends ItemTouchHelper.Callback{private DragItemTouchHelperAdapter mAdapter;public DragItemTouchHelperCallback(DragItemTouchHelperAdapter mAdapter){this.mAdapter = mAdapter;}@Overridepublic boolean isLongPressDragEnabled() {return true;}@Overridepublic boolean isItemViewSwipeEnabled() {return false;}@Overridepublic int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {if(viewHolder.getItemViewType()>0){final int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;return makeMovementFlags(dragFlags, 0);}elsereturn makeMovementFlags(0,0);//0 滑动禁止}@Overridepublic boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder source, RecyclerView.ViewHolder target) {if (source.getItemViewType() != target.getItemViewType()) {return false;}// Notify the adapter of the movemAdapter.onItemMove(source.getAdapterPosition(), target.getAdapterPosition());return true;}@Overridepublic void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {if (viewHolder.getItemViewType()>0) {ItemTouchHelperViewHolder itemViewHolder = (ItemTouchHelperViewHolder) viewHolder;itemViewHolder.onItemSelected();}}super.onSelectedChanged(viewHolder, actionState);}@Overridepublic void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {super.clearView(recyclerView, viewHolder);if (viewHolder.getItemViewType()>0) {ItemTouchHelperViewHolder itemViewHolder = (ItemTouchHelperViewHolder) viewHolder;itemViewHolder.onItemClear();}}@Overridepublic void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {}
}
DragItemTouchHelperAdapter 选中图片拖拽,删除接口
public interface DragItemTouchHelperAdapter {boolean onItemMove(int fromPosition, int toPosition);void onItemDismiss(int position);
}
ItemTouchHelperViewHolder 选中图片长按,失去焦点接口
public interface ItemTouchHelperViewHolder {void onItemSelected();void onItemClear();
}
OnItemClickListener 相册子项选中接口
public interface OnItemClickListener {void onItemClick(RecyclerView.ViewHolder viewHolder,int position);
}
ScreenHelper 屏幕宽度
public class ScreenHelper {/*** dp转px* @param context* @param dp* @return*/public static int dp2px(Context context,float dp){return (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, context.getResources().getDisplayMetrics());}/*** 获得屏幕宽度** @param context* @return*/public static int getScreenWidth(Context context){WindowManager wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE );DisplayMetrics outMetrics = new DisplayMetrics();wm.getDefaultDisplay().getMetrics(outMetrics);return outMetrics .widthPixels;}
}
DiskLruCache 核心缓存类(第三方开源),DiskLruCacheManager 文件缓存管理器,LruCacheManager内存缓存管理器,CacheManager 文件+内存管理器
public class CacheManager {/*** 只使用内存缓存(LruCache)*/public static final int ONLY_LRU=1;/*** 只使用硬盘缓存(DiskLruCache)*/public static final int ONLY_DISKLRU=2;/*** 同时使用内存缓存(LruCache)与硬盘缓存(DiskLruCache)*/public static final int ALL_ALLOW=0;/*** 设置类型为硬盘缓存——用于取硬盘缓存大小*/public static final int DISKSIZE=0;/*** 设置类型为内存缓存——用于取内存缓存大小*/public static final int MEMORYSIZE=1;//设置硬盘缓存的最大值,单位为Mprivate static int maxSizeForDiskLruCache=0;//设置内存缓存的最大值,单位为Mprivate static int maxMemoryForLruCache=0;//设置自定义的硬盘缓存文件夹名称private static String dirNameForDiskLruCache="";//记录硬盘缓存与内存缓存起效标志private static int model=0;//硬盘缓存管理类private static DiskLruCacheManager diskLruCacheManager;//内存缓存管理类private static LruCacheManager lruCacheManager;private static Context ct;/*** 初始化缓存管理* @param context 上下文*/public static void init(Context context){ct=context;init_();}//根据传入的标志,初始化内存缓存以及硬盘缓存,默认开启是同时使用private static void init_(){switch (model) {case ALL_ALLOW:initDiskLruCacheManager();initLruCacheManager();break;case ONLY_LRU:initLruCacheManager();break;case ONLY_DISKLRU:initDiskLruCacheManager();break;default:break;}}//初始化内存缓存管理private static void initLruCacheManager(){if(maxMemoryForLruCache>0){lruCacheManager=new LruCacheManager(maxMemoryForLruCache);}else {lruCacheManager=new LruCacheManager();}}//初始化硬盘缓存管理private static void initDiskLruCacheManager(){if(maxSizeForDiskLruCache>0&&!TextUtils.isEmpty(dirNameForDiskLruCache)){diskLruCacheManager=new DiskLruCacheManager(ct,dirNameForDiskLruCache,maxSizeForDiskLruCache*1024*1024);}else if(maxSizeForDiskLruCache>0){diskLruCacheManager=new DiskLruCacheManager(ct, maxSizeForDiskLruCache*1024*1024);}else if(!TextUtils.isEmpty(dirNameForDiskLruCache)){diskLruCacheManager=new DiskLruCacheManager(ct, dirNameForDiskLruCache);}else {diskLruCacheManager=new DiskLruCacheManager(ct);}}/*** 设置硬盘缓存的最大值,单位为兆(M).* @param maxSizeForDisk 硬盘缓存最大值,单位为兆(M)*/public static void setMaxSize(int maxSizeForDisk){maxSizeForDiskLruCache=maxSizeForDisk;}/*** 设置内存缓存的最大值,单位为兆(M).* @param maxMemory 内存缓存最大值,单位为兆(M)*/public static void setMaxMemory(int maxMemory){maxMemoryForLruCache=maxMemory;}/*** 设置硬盘缓存自定义的文件名* @param dirName 自定义文件名*/public static void setDirName(String dirName){dirNameForDiskLruCache=dirName;}/*** 索引key对应的bitmap写入缓存* @param key 缓存索引* @param bitmap bitmap格式数据*/public static void put(String key,Bitmap bitmap){switch (model) {case ALL_ALLOW:if(lruCacheManager!=null&&diskLruCacheManager!=null){//设置硬盘缓存成功后,再设置内存缓存if(diskLruCacheManager.putDiskCache(key,bitmap)){lruCacheManager.putCache(key, bitmap);}}break;case ONLY_LRU:if(lruCacheManager!=null){lruCacheManager.putCache(key, bitmap);}break;case ONLY_DISKLRU:if(diskLruCacheManager!=null){diskLruCacheManager.putDiskCache(key,bitmap);}break;default:break;}}/*** 获取索引key对应的缓存内容* @param key 缓存索引key* @return key索引对应的Bitmap数据*/public static Bitmap get(String key){Bitmap bitmap=null;switch (model) {case ALL_ALLOW:if(lruCacheManager!=null&&diskLruCacheManager!=null){bitmap=lruCacheManager.getCache(key);if(bitmap==null){//如果硬盘缓存内容存在,内存缓存不存在。则在获取硬盘缓存后,将内容写入内存缓存bitmap=diskLruCacheManager.getDiskCache(key);lruCacheManager.putCache(key, bitmap);}}break;case ONLY_LRU:if(lruCacheManager!=null){bitmap=lruCacheManager.getCache(key);}break;case ONLY_DISKLRU:if(diskLruCacheManager!=null){bitmap=diskLruCacheManager.getDiskCache(key);}break;default:break;}return bitmap;}/*** 删除所有缓存*/public static void delete(){switch (model) {case ALL_ALLOW:if(lruCacheManager!=null&&diskLruCacheManager!=null){lruCacheManager.deleteCache();diskLruCacheManager.deleteDiskCache();}break;case ONLY_LRU:if(lruCacheManager!=null){lruCacheManager.deleteCache();}break;case ONLY_DISKLRU:if(diskLruCacheManager!=null){diskLruCacheManager.deleteDiskCache();}break;default:break;}}/*** 移除一条索引key对应的缓存* @param key 索引*/public static void remove(String key){switch (model) {case ALL_ALLOW:if(lruCacheManager!=null&&diskLruCacheManager!=null){lruCacheManager.removeCache(key);diskLruCacheManager.removeDiskCache(key);}break;case ONLY_LRU:if(lruCacheManager!=null){lruCacheManager.removeCache(key);}break;case ONLY_DISKLRU:if(diskLruCacheManager!=null){diskLruCacheManager.removeDiskCache(key);}break;default:break;}}/*** 缓存数据同步*/public static void flush(){switch (model) {case ALL_ALLOW:if(lruCacheManager!=null&&diskLruCacheManager!=null){diskLruCacheManager.fluchCache();}break;case ONLY_LRU:break;case ONLY_DISKLRU:if(diskLruCacheManager!=null){diskLruCacheManager.fluchCache();}break;default:break;}}/*** 设置缓存模式* @param modelSet ONLY_LRU、ONLY_DISK、ALL_ALLOW*/public static void setCacheModel(int modelSet){model=modelSet;}/*** 删除特定文件名的缓存文件* @param dirName 文件名*/public static void deleteFile(String dirName){if(diskLruCacheManager!=null){diskLruCacheManager.deleteFile(ct, dirName);}}/*** 获取缓存大小——内存缓存+硬盘缓存* @return*/public static int size(){int size=0;if(diskLruCacheManager!=null){size+=diskLruCacheManager.size();}if(lruCacheManager!=null){size+=lruCacheManager.size();}return size;}/*** 获取缓存大小* @param type 硬盘缓存类型:DISKSIZE、内存缓存类型:MEMORYSIZE* @return 对应类型的缓存大小*/public static int size(int type){int size=0;switch (type) {case DISKSIZE:if(diskLruCacheManager!=null){size+=diskLruCacheManager.size();}break;case MEMORYSIZE:if(lruCacheManager!=null){size+=lruCacheManager.size();}break;default:break;}return size;}/*** 关闭缓存*/public static void close(){if(diskLruCacheManager!=null){diskLruCacheManager.close();}}
}public class DiskLruCacheManager {private static int maxSize=100*1024*1024;private DiskLruCache mDiskLruCache;private final static String mImageCacheName="ImageCache";public DiskLruCacheManager(Context context){this(context, mImageCacheName, maxSize);}public DiskLruCacheManager(Context context,int maxDiskLruCacheSize){this(context, mImageCacheName, maxDiskLruCacheSize);}public DiskLruCacheManager(Context context,String dirName){this(context, dirName, maxSize);}public DiskLruCacheManager(Context context,String dirName,int maxDiskLruCacheSize){try {mDiskLruCache=DiskLruCache.open(getDiskCacheFile(context,dirName), getAppVersion(context), 1, maxDiskLruCacheSize);} catch (IOException e) {e.printStackTrace();}}/*** 获取文件夹地址,如果不存在,则创建* @param context 上下文* @param dirName 文件名* @return File 文件*/private File getDiskCacheFile(Context context,String dirName){File cacheDir=packDiskCacheFile(context,dirName);if (!cacheDir.exists()) { cacheDir.mkdirs(); } return cacheDir; }/*** 获取文件夹地址* @param context 上下文* @param dirName 文件名* @return File 文件*/private File packDiskCacheFile(Context context,String dirName){String cachePath; if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) || !Environment.isExternalStorageRemovable()) { cachePath = context.getExternalCacheDir().getPath(); } else { cachePath = context.getCacheDir().getPath(); } return new File(cachePath + File.separator + dirName); }/** * 获取当前应用程序的版本号。 */ private int getAppVersion(Context context) { try { PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(),0); return info.versionCode; } catch (NameNotFoundException e) { e.printStackTrace(); } return 1; } /** * 使用MD5算法对传入的key进行加密并返回。 */ private String Md5(String key) { String cacheKey; try { final MessageDigest mDigest = MessageDigest.getInstance("MD5"); mDigest.update(key.getBytes()); cacheKey = bytesToHexString(mDigest.digest()); } catch (NoSuchAlgorithmException e) { cacheKey = String.valueOf(key.hashCode()); } return cacheKey; } private String bytesToHexString(byte[] bytes) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < bytes.length; i++) { String hex = Integer.toHexString(0xFF & bytes[i]); if (hex.length() == 1) { sb.append('0'); } sb.append(hex); } return sb.toString(); } /*** Bitmap格式数据写入到outputstream中 * @param bm Bitmap数据* @param baos outputstream* @return outputstream*/private OutputStream Bitmap2OutputStream(Bitmap bm,OutputStream baos) { if(bm!=null){bm.compress(Bitmap.CompressFormat.JPEG, 80, baos);}return baos;} /** * 将缓存记录同步到journal文件中。 */ public void fluchCache() { if (mDiskLruCache != null) { try { mDiskLruCache.flush(); } catch (IOException e) { e.printStackTrace(); } } } /*** 获取硬盘缓存* @param key 所有* @return Bitmap格式缓存*/public Bitmap getDiskCache(String key){String md5Key=Md5(key);Bitmap bitmap=null;try {if(mDiskLruCache!=null){DiskLruCache.Snapshot snapshot = mDiskLruCache.get(md5Key);if(snapshot!=null){bitmap=BitmapFactory.decodeStream(snapshot.getInputStream(0)) ; }}}catch (IOException e) {e.printStackTrace();}return bitmap;}/*** 设置key对应的缓存* @param key 索引* @param bitmap Bitmap格式数据* @return 是否写入*/public boolean putDiskCache(String key,Bitmap bitmap){String md5Key=Md5(key);try {if(mDiskLruCache!=null){if(mDiskLruCache.get(md5Key)!=null){return true;}DiskLruCache.Editor editor=mDiskLruCache.edit(md5Key);if(editor!=null){OutputStream outputStream= editor.newOutputStream(0);Bitmap2OutputStream(bitmap,outputStream);if(outputStream!=null){editor.commit();return true;}else {editor.abort();return false;}}}} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}return false;}public void deleteDiskCache(){try {if(mDiskLruCache!=null){mDiskLruCache.delete();}} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}public void removeDiskCache(String key){if(mDiskLruCache!=null){try {mDiskLruCache.remove(key);} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}public void deleteFile(Context context,String dirName){try {DiskLruCache.deleteContents(packDiskCacheFile(context,dirName));} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}public int size(){int size=0;if(mDiskLruCache!=null){size=(int) mDiskLruCache.size();}return size;}public void close(){if(mDiskLruCache!=null){try {mDiskLruCache.close();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}}public class LruCacheManager {private LruCache<String, Bitmap> lruCache;public LruCacheManager(){this((int)Runtime.getRuntime().maxMemory()/1024/8);}//设置自定义大小的LruCachepublic LruCacheManager(int maxSize){lruCache=new LruCache<String, Bitmap>(maxSize*1024){@Overrideprotected int sizeOf(String key, Bitmap value) {return value.getByteCount()/1024;}};}/*** 写入索引key对应的缓存* @param key 索引* @param bitmap 缓存内容* @return 写入结果*/public Bitmap putCache(String key,Bitmap bitmap){Bitmap bitmapValue=getCache(key);if(bitmapValue==null){if(lruCache!=null&&bitmap!=null)bitmapValue= lruCache.put(key, bitmap);}return bitmapValue;}/*** 获取缓存* @param key 索引key对应的缓存* @return 缓存*/public Bitmap getCache(String key){ if(lruCache!=null){return lruCache.get(key);}return null;}public void deleteCache(){if(lruCache!=null)lruCache.evictAll();}public void removeCache(String key){if(lruCache!=null)lruCache.remove(key);}public int size(){int size=0;if(lruCache!=null)size+=lruCache.size();return size;}
}public final class DiskLruCache implements Closeable {static final String JOURNAL_FILE = "journal";static final String JOURNAL_FILE_TMP = "journal.tmp";static final String MAGIC = "libcore.io.DiskLruCache";static final String VERSION_1 = "1";static final long ANY_SEQUENCE_NUMBER = -1;private static final String CLEAN = "CLEAN";private static final String DIRTY = "DIRTY";private static final String REMOVE = "REMOVE";private static final String READ = "READ";private static final Charset UTF_8 = Charset.forName("UTF-8");private static final int IO_BUFFER_SIZE = 8 * 1024;/** This cache uses a journal file named "journal". A typical journal file* looks like this:* libcore.io.DiskLruCache* 1* 100* 2** CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054* DIRTY 335c4c6028171cfddfbaae1a9c313c52* CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342* REMOVE 335c4c6028171cfddfbaae1a9c313c52* DIRTY 1ab96a171faeeee38496d8b330771a7a* CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234* READ 335c4c6028171cfddfbaae1a9c313c52* READ 3400330d1dfc7f3f7f4b8d4d803dfcf6** The first five lines of the journal form its header. They are the* constant string "libcore.io.DiskLruCache", the disk cache's version,* the application's version, the value count, and a blank line.** Each of the subsequent lines in the file is a record of the state of a* cache entry. Each line contains space-separated values: a state, a key,* and optional state-specific values.* o DIRTY lines track that an entry is actively being created or updated.* Every successful DIRTY action should be followed by a CLEAN or REMOVE* action. DIRTY lines without a matching CLEAN or REMOVE indicate that* temporary files may need to be deleted.* o CLEAN lines track a cache entry that has been successfully published* and may be read. A publish line is followed by the lengths of each of* its values.* o READ lines track accesses for LRU.* o REMOVE lines track entries that have been deleted.** The journal file is appended to as cache operations occur. The journal may* occasionally be compacted by dropping redundant lines. A temporary file named* "journal.tmp" will be used during compaction; that file should be deleted if* it exists when the cache is opened.*/private final File directory;private final File journalFile;private final File journalFileTmp;private final int appVersion;private final long maxSize;private final int valueCount;private long size = 0;private Writer journalWriter;private final LinkedHashMap<String, Entry> lruEntries= new LinkedHashMap<String, Entry>(0, 0.75f, true);private int redundantOpCount;/*** To differentiate between old and current snapshots, each entry is given* a sequence number each time an edit is committed. A snapshot is stale if* its sequence number is not equal to its entry's sequence number.*/private long nextSequenceNumber = 0;/* From java.util.Arrays */@SuppressWarnings("unchecked")private static <T> T[] copyOfRange(T[] original, int start, int end) {final int originalLength = original.length; // For exception priority compatibility.if (start > end) {throw new IllegalArgumentException();}if (start < 0 || start > originalLength) {throw new ArrayIndexOutOfBoundsException();}final int resultLength = end - start;final int copyLength = Math.min(resultLength, originalLength - start);final T[] result = (T[]) Array.newInstance(original.getClass().getComponentType(), resultLength);System.arraycopy(original, start, result, 0, copyLength);return result;}/*** Returns the remainder of 'reader' as a string, closing it when done.*/public static String readFully(Reader reader) throws IOException {try {StringWriter writer = new StringWriter();char[] buffer = new char[1024];int count;while ((count = reader.read(buffer)) != -1) {writer.write(buffer, 0, count);}return writer.toString();} finally {reader.close();}}/*** Returns the ASCII characters up to but not including the next "\r\n", or* "\n".** @throws EOFException if the stream is exhausted before the next newline* character.*/public static String readAsciiLine(InputStream in) throws IOException {// TODO: support UTF-8 here insteadStringBuilder result = new StringBuilder(80);while (true) {int c = in.read();if (c == -1) {throw new EOFException();} else if (c == '\n') {break;}result.append((char) c);}int length = result.length();if (length > 0 && result.charAt(length - 1) == '\r') {result.setLength(length - 1);}return result.toString();}/*** Closes 'closeable', ignoring any checked exceptions. Does nothing if 'closeable' is null.*/public static void closeQuietly(Closeable closeable) {if (closeable != null) {try {closeable.close();} catch (RuntimeException rethrown) {throw rethrown;} catch (Exception ignored) {}}}/*** Recursively delete everything in {@code dir}.*/// TODO: this should specify paths as Strings rather than as Filespublic static void deleteContents(File dir) throws IOException {File[] files = dir.listFiles();if (files == null) {throw new IllegalArgumentException("not a directory: " + dir);}for (File file : files) {if (file.isDirectory()) {deleteContents(file);}if (!file.delete()) {throw new IOException("failed to delete file: " + file);}}}/** This cache uses a single background thread to evict entries. */private final ExecutorService executorService = new ThreadPoolExecutor(0, 1,60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());private final Callable<Void> cleanupCallable = new Callable<Void>() {@Override public Void call() throws Exception {synchronized (DiskLruCache.this) {if (journalWriter == null) {return null; // closed}trimToSize();if (journalRebuildRequired()) {rebuildJournal();redundantOpCount = 0;}}return null;}};private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize) {this.directory = directory;this.appVersion = appVersion;this.journalFile = new File(directory, JOURNAL_FILE);this.journalFileTmp = new File(directory, JOURNAL_FILE_TMP);this.valueCount = valueCount;this.maxSize = maxSize;}/*** Opens the cache in {@code directory}, creating a cache if none exists* there.** @param directory a writable directory* @param appVersion* @param valueCount the number of values per cache entry. Must be positive.* @param maxSize the maximum number of bytes this cache should use to store* @throws IOException if reading or writing the cache directory fails*/public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)throws IOException {if (maxSize <= 0) {throw new IllegalArgumentException("maxSize <= 0");}if (valueCount <= 0) {throw new IllegalArgumentException("valueCount <= 0");}// prefer to pick up where we left offDiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);if (cache.journalFile.exists()) {try {cache.readJournal();cache.processJournal();cache.journalWriter = new BufferedWriter(new FileWriter(cache.journalFile, true),IO_BUFFER_SIZE);return cache;} catch (IOException journalIsCorrupt) {
// System.logW("DiskLruCache " + directory + " is corrupt: "
// + journalIsCorrupt.getMessage() + ", removing");cache.delete();}}// create a new empty cachedirectory.mkdirs();cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);cache.rebuildJournal();return cache;}private void readJournal() throws IOException {InputStream in = new BufferedInputStream(new FileInputStream(journalFile), IO_BUFFER_SIZE);try {String magic = readAsciiLine(in);String version = readAsciiLine(in);String appVersionString = readAsciiLine(in);String valueCountString = readAsciiLine(in);String blank = readAsciiLine(in);if (!MAGIC.equals(magic)|| !VERSION_1.equals(version)|| !Integer.toString(appVersion).equals(appVersionString)|| !Integer.toString(valueCount).equals(valueCountString)|| !"".equals(blank)) {throw new IOException("unexpected journal header: ["+ magic + ", " + version + ", " + valueCountString + ", " + blank + "]");}while (true) {try {readJournalLine(readAsciiLine(in));} catch (EOFException endOfJournal) {break;}}} finally {closeQuietly(in);}}private void readJournalLine(String line) throws IOException {String[] parts = line.split(" ");if (parts.length < 2) {throw new IOException("unexpected journal line: " + line);}String key = parts[1];if (parts[0].equals(REMOVE) && parts.length == 2) {lruEntries.remove(key);return;}Entry entry = lruEntries.get(key);if (entry == null) {entry = new Entry(key);lruEntries.put(key, entry);}if (parts[0].equals(CLEAN) && parts.length == 2 + valueCount) {entry.readable = true;entry.currentEditor = null;entry.setLengths(copyOfRange(parts, 2, parts.length));} else if (parts[0].equals(DIRTY) && parts.length == 2) {entry.currentEditor = new Editor(entry);} else if (parts[0].equals(READ) && parts.length == 2) {// this work was already done by calling lruEntries.get()} else {throw new IOException("unexpected journal line: " + line);}}/*** Computes the initial size and collects garbage as a part of opening the* cache. Dirty entries are assumed to be inconsistent and will be deleted.*/private void processJournal() throws IOException {deleteIfExists(journalFileTmp);for (Iterator<Entry> i = lruEntries.values().iterator(); i.hasNext(); ) {Entry entry = i.next();if (entry.currentEditor == null) {for (int t = 0; t < valueCount; t++) {size += entry.lengths[t];}} else {entry.currentEditor = null;for (int t = 0; t < valueCount; t++) {deleteIfExists(entry.getCleanFile(t));deleteIfExists(entry.getDirtyFile(t));}i.remove();}}}/*** Creates a new journal that omits redundant information. This replaces the* current journal if it exists.*/private synchronized void rebuildJournal() throws IOException {if (journalWriter != null) {journalWriter.close();}Writer writer = new BufferedWriter(new FileWriter(journalFileTmp), IO_BUFFER_SIZE);writer.write(MAGIC);writer.write("\n");writer.write(VERSION_1);writer.write("\n");writer.write(Integer.toString(appVersion));writer.write("\n");writer.write(Integer.toString(valueCount));writer.write("\n");writer.write("\n");for (Entry entry : lruEntries.values()) {if (entry.currentEditor != null) {writer.write(DIRTY + ' ' + entry.key + '\n');} else {writer.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');}}writer.close();journalFileTmp.renameTo(journalFile);journalWriter = new BufferedWriter(new FileWriter(journalFile, true), IO_BUFFER_SIZE);}private static void deleteIfExists(File file) throws IOException {
// try {
// Libcore.os.remove(file.getPath());
// } catch (ErrnoException errnoException) {
// if (errnoException.errno != OsConstants.ENOENT) {
// throw errnoException.rethrowAsIOException();
// }
// }if (file.exists() && !file.delete()) {throw new IOException();}}/*** Returns a snapshot of the entry named {@code key}, or null if it doesn't* exist is not currently readable. If a value is returned, it is moved to* the head of the LRU queue.*/public synchronized Snapshot get(String key) throws IOException {checkNotClosed();validateKey(key);Entry entry = lruEntries.get(key);if (entry == null) {return null;}if (!entry.readable) {return null;}/** Open all streams eagerly to guarantee that we see a single published* snapshot. If we opened streams lazily then the streams could come* from different edits.*/InputStream[] ins = new InputStream[valueCount];try {for (int i = 0; i < valueCount; i++) {ins[i] = new FileInputStream(entry.getCleanFile(i));}} catch (FileNotFoundException e) {// a file must have been deleted manually!return null;}redundantOpCount++;journalWriter.append(READ + ' ' + key + '\n');if (journalRebuildRequired()) {executorService.submit(cleanupCallable);}return new Snapshot(key, entry.sequenceNumber, ins);}/*** Returns an editor for the entry named {@code key}, or null if another* edit is in progress.*/public Editor edit(String key) throws IOException {return edit(key, ANY_SEQUENCE_NUMBER);}private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException {checkNotClosed();validateKey(key);Entry entry = lruEntries.get(key);if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER&& (entry == null || entry.sequenceNumber != expectedSequenceNumber)) {return null; // snapshot is stale}if (entry == null) {entry = new Entry(key);lruEntries.put(key, entry);} else if (entry.currentEditor != null) {return null; // another edit is in progress}Editor editor = new Editor(entry);entry.currentEditor = editor;// flush the journal before creating files to prevent file leaksjournalWriter.write(DIRTY + ' ' + key + '\n');journalWriter.flush();return editor;}/*** Returns the directory where this cache stores its data.*/public File getDirectory() {return directory;}/*** Returns the maximum number of bytes that this cache should use to store* its data.*/public long maxSize() {return maxSize;}/*** Returns the number of bytes currently being used to store the values in* this cache. This may be greater than the max size if a background* deletion is pending.*/public synchronized long size() {return size;}private synchronized void completeEdit(Editor editor, boolean success) throws IOException {Entry entry = editor.entry;if (entry.currentEditor != editor) {throw new IllegalStateException();}// if this edit is creating the entry for the first time, every index must have a valueif (success && !entry.readable) {for (int i = 0; i < valueCount; i++) {if (!entry.getDirtyFile(i).exists()) {editor.abort();throw new IllegalStateException("edit didn't create file " + i);}}}for (int i = 0; i < valueCount; i++) {File dirty = entry.getDirtyFile(i);if (success) {if (dirty.exists()) {File clean = entry.getCleanFile(i);dirty.renameTo(clean);long oldLength = entry.lengths[i];long newLength = clean.length();entry.lengths[i] = newLength;size = size - oldLength + newLength;}} else {deleteIfExists(dirty);}}redundantOpCount++;entry.currentEditor = null;if (entry.readable | success) {entry.readable = true;journalWriter.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');if (success) {entry.sequenceNumber = nextSequenceNumber++;}} else {lruEntries.remove(entry.key);journalWriter.write(REMOVE + ' ' + entry.key + '\n');}if (size > maxSize || journalRebuildRequired()) {executorService.submit(cleanupCallable);}}/*** We only rebuild the journal when it will halve the size of the journal* and eliminate at least 2000 ops.*/private boolean journalRebuildRequired() {final int REDUNDANT_OP_COMPACT_THRESHOLD = 2000;return redundantOpCount >= REDUNDANT_OP_COMPACT_THRESHOLD&& redundantOpCount >= lruEntries.size();}/*** Drops the entry for {@code key} if it exists and can be removed. Entries* actively being edited cannot be removed.** @return true if an entry was removed.*/public synchronized boolean remove(String key) throws IOException {checkNotClosed();validateKey(key);Entry entry = lruEntries.get(key);if (entry == null || entry.currentEditor != null) {return false;}for (int i = 0; i < valueCount; i++) {File file = entry.getCleanFile(i);if (!file.delete()) {throw new IOException("failed to delete " + file);}size -= entry.lengths[i];entry.lengths[i] = 0;}redundantOpCount++;journalWriter.append(REMOVE + ' ' + key + '\n');lruEntries.remove(key);if (journalRebuildRequired()) {executorService.submit(cleanupCallable);}return true;}/*** Returns true if this cache has been closed.*/public boolean isClosed() {return journalWriter == null;}private void checkNotClosed() {if (journalWriter == null) {throw new IllegalStateException("cache is closed");}}/*** Force buffered operations to the filesystem.*/public synchronized void flush() throws IOException {checkNotClosed();trimToSize();journalWriter.flush();}/*** Closes this cache. Stored values will remain on the filesystem.*/public synchronized void close() throws IOException {if (journalWriter == null) {return; // already closed}for (Entry entry : new ArrayList<Entry>(lruEntries.values())) {if (entry.currentEditor != null) {entry.currentEditor.abort();}}trimToSize();journalWriter.close();journalWriter = null;}private void trimToSize() throws IOException {while (size > maxSize) {
// Map.Entry<String, Entry> toEvict = lruEntries.eldest();final Map.Entry<String, Entry> toEvict = lruEntries.entrySet().iterator().next();remove(toEvict.getKey());}}/*** Closes the cache and deletes all of its stored values. This will delete* all files in the cache directory including files that weren't created by* the cache.*/public void delete() throws IOException {close();deleteContents(directory);}private void validateKey(String key) {if (key.contains(" ") || key.contains("\n") || key.contains("\r")) {throw new IllegalArgumentException("keys must not contain spaces or newlines: \"" + key + "\"");}}private static String inputStreamToString(InputStream in) throws IOException {return readFully(new InputStreamReader(in, UTF_8));}/*** A snapshot of the values for an entry.*/public final class Snapshot implements Closeable {private final String key;private final long sequenceNumber;private final InputStream[] ins;private Snapshot(String key, long sequenceNumber, InputStream[] ins) {this.key = key;this.sequenceNumber = sequenceNumber;this.ins = ins;}/*** Returns an editor for this snapshot's entry, or null if either the* entry has changed since this snapshot was created or if another edit* is in progress.*/public Editor edit() throws IOException {return DiskLruCache.this.edit(key, sequenceNumber);}/*** Returns the unbuffered stream with the value for {@code index}.*/public InputStream getInputStream(int index) {return ins[index];}/*** Returns the string value for {@code index}.*/public String getString(int index) throws IOException {return inputStreamToString(getInputStream(index));}@Override public void close() {for (InputStream in : ins) {closeQuietly(in);}}}/*** Edits the values for an entry.*/public final class Editor {private final Entry entry;private boolean hasErrors;private Editor(Entry entry) {this.entry = entry;}/*** Returns an unbuffered input stream to read the last committed value,* or null if no value has been committed.*/public InputStream newInputStream(int index) throws IOException {synchronized (DiskLruCache.this) {if (entry.currentEditor != this) {throw new IllegalStateException();}if (!entry.readable) {return null;}return new FileInputStream(entry.getCleanFile(index));}}/*** Returns the last committed value as a string, or null if no value* has been committed.*/public String getString(int index) throws IOException {InputStream in = newInputStream(index);return in != null ? inputStreamToString(in) : null;}/*** Returns a new unbuffered output stream to write the value at* {@code index}. If the underlying output stream encounters errors* when writing to the filesystem, this edit will be aborted when* {@link #commit} is called. The returned output stream does not throw* IOExceptions.*/public OutputStream newOutputStream(int index) throws IOException {synchronized (DiskLruCache.this) {if (entry.currentEditor != this) {throw new IllegalStateException();}return new FaultHidingOutputStream(new FileOutputStream(entry.getDirtyFile(index)));}}/*** Sets the value at {@code index} to {@code value}.*/public void set(int index, String value) throws IOException {Writer writer = null;try {writer = new OutputStreamWriter(newOutputStream(index), UTF_8);writer.write(value);} finally {closeQuietly(writer);}}/*** Commits this edit so it is visible to readers. This releases the* edit lock so another edit may be started on the same key.*/public void commit() throws IOException {if (hasErrors) {completeEdit(this, false);remove(entry.key); // the previous entry is stale} else {completeEdit(this, true);}}/*** Aborts this edit. This releases the edit lock so another edit may be* started on the same key.*/public void abort() throws IOException {completeEdit(this, false);}private class FaultHidingOutputStream extends FilterOutputStream {private FaultHidingOutputStream(OutputStream out) {super(out);}@Override public void write(int oneByte) {try {out.write(oneByte);} catch (IOException e) {hasErrors = true;}}@Override public void write(byte[] buffer, int offset, int length) {try {out.write(buffer, offset, length);} catch (IOException e) {hasErrors = true;}}@Override public void close() {try {out.close();} catch (IOException e) {hasErrors = true;}}@Override public void flush() {try {out.flush();} catch (IOException e) {hasErrors = true;}}}}private final class Entry {private final String key;/** Lengths of this entry's files. */private final long[] lengths;/** True if this entry has ever been published */private boolean readable;/** The ongoing edit or null if this entry is not being edited. */private Editor currentEditor;/** The sequence number of the most recently committed edit to this entry. */private long sequenceNumber;private Entry(String key) {this.key = key;this.lengths = new long[valueCount];}public String getLengths() throws IOException {StringBuilder result = new StringBuilder();for (long size : lengths) {result.append(' ').append(size);}return result.toString();}/*** Set lengths using decimal numbers like "10123".*/private void setLengths(String[] strings) throws IOException {if (strings.length != valueCount) {throw invalidLengths(strings);}try {for (int i = 0; i < strings.length; i++) {lengths[i] = Long.parseLong(strings[i]);}} catch (NumberFormatException e) {throw invalidLengths(strings);}}private IOException invalidLengths(String[] strings) throws IOException {throw new IOException("unexpected journal line: " + Arrays.toString(strings));}public File getCleanFile(int i) {return new File(directory, key + "." + i);}public File getDirtyFile(int i) {return new File(directory, key + "." + i + ".tmp");}}
}
CameraDialogFragment 弹出菜单
public class CameraDialogFragment extends DialogFragment {private OnItemClickListener onItemClickListener;public void setOnItemClickListener(OnItemClickListener onItemClickListener) {this.onItemClickListener = onItemClickListener;}@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setStyle(DialogFragment.STYLE_NO_TITLE, R.style.BottomDialog);}@Nullable@Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {//屏幕外退出getDialog().setCanceledOnTouchOutside(true);//背景透明getDialog().getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));Window window = getDialog().getWindow();WindowManager.LayoutParams wlp = window.getAttributes();wlp.gravity = Gravity.BOTTOM;window.setAttributes(wlp);View view = inflater.inflate(R.layout.fragment_camera,null);TextView camera = (TextView)view.findViewById(R.id.camera);TextView album = (TextView)view.findViewById(R.id.album);TextView cancel = (TextView)view.findViewById(R.id.cancel);camera.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {dismiss();onItemClickListener.onItemClick(null,0);}});album.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {dismiss();onItemClickListener.onItemClick(null,1);}});cancel.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {dismiss();}});return view;}
}
activity_repair.xml 实例布局页
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"android:layout_height="match_parent"android:background="@color/white"tools:context="com.room.activity.RepairActivity"><RelativeLayout
android:id="@id/tab_title"android:layout_width="match_parent"android:layout_height="50dp"android:background="@color/skyblue"android:paddingLeft="10dp"android:paddingRight="10dp"><ImageButton
android:layout_centerVertical="true"android:background="@drawable/back"android:onClick="onBack"android:layout_width="30dp"android:layout_height="30dp" /><TextView
android:layout_centerInParent="true"android:id="@id/tab_title_name"android:text="@string/repair"android:textSize="18dp"android:textColor="@color/white"android:gravity="center"android:layout_width="wrap_content"android:layout_height="match_parent" /><ImageButton
android:layout_alignParentRight="true"android:background="@drawable/release"android:layout_centerVertical="true"android:alpha=".6"android:clickable="false"android:onClick="onSubmit"android:id="@id/submit"android:layout_width="30dp"android:layout_height="30dp" /></RelativeLayout><LinearLayout
android:layout_marginTop="10dp"android:paddingLeft="10dp"android:paddingRight="10dp"android:orientation="vertical"android:layout_below="@id/tab_title"android:layout_width="match_parent"android:layout_height="wrap_content"><EditText
android:id="@id/content"android:minHeight="100dp"android:textSize="16sp"android:background="@null"android:gravity="left|top"android:hint="@string/hint_repair"android:textColor="@color/black"android:layout_width="match_parent"android:layout_height="wrap_content" /><LinearLayout
android:layout_marginTop="10dp"android:orientation="horizontal"android:layout_width="match_parent"android:layout_height="wrap_content"><android.support.v7.widget.RecyclerView
android:id="@id/list"android:layout_width="wrap_content"android:layout_height="wrap_content" /></LinearLayout><View
android:layout_marginTop="10dp"android:background="@color/grey"android:layout_width="match_parent"android:layout_height="0.5dp"/></LinearLayout></RelativeLayout>
activity_album.xml 相册布局页
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"android:layout_height="match_parent"android:background="@color/white"tools:context="com.room.activity.AlbumActivity"><RelativeLayoutandroid:id="@id/tab_title"android:layout_width="match_parent"android:layout_height="50dp"android:background="@color/skyblue"android:paddingLeft="10dp"android:paddingRight="10dp"><ImageButtonandroid:layout_centerVertical="true"android:background="@drawable/back"android:onClick="onBack"android:layout_width="30dp"android:layout_height="30dp" /><TextViewandroid:layout_centerInParent="true"android:id="@id/tab_title_name"android:text="@string/album"android:textSize="18dp"android:textColor="@color/white"android:gravity="center"android:layout_width="wrap_content"android:layout_height="match_parent" /><TextViewandroid:layout_alignParentRight="true"android:layout_centerVertical="true"android:text="@string/cancel"android:background="@color/transparent"android:textColor="@color/white"android:onClick="onCancel"android:textSize="18dp"android:gravity="center_vertical"android:id="@id/cancel"android:layout_width="wrap_content"android:layout_height="match_parent" /></RelativeLayout><android.support.v7.widget.RecyclerViewandroid:id="@id/list"android:paddingBottom="10dp"android:layout_marginLeft="10dp"android:layout_marginRight="10dp"android:layout_below="@id/tab_title"android:layout_above="@id/container"android:layout_width="match_parent"android:layout_height="match_parent" /><RelativeLayoutandroid:id="@id/container"android:background="#f3f3f3"android:layout_alignParentBottom="true"android:layout_width="match_parent"android:layout_height="50dp"><Viewandroid:background="@color/grey"android:layout_width="match_parent"android:layout_height="0.5dp"/><Buttonandroid:id="@id/submit"android:text="@string/submit"android:clickable="false"android:onClick="onSubmit"android:textColor="@color/darkgray"android:background="@drawable/bg_album_submit"android:layout_centerVertical="true"android:layout_marginRight="10dp"android:alpha=".8"android:layout_alignParentRight="true"android:layout_width="70dp"android:layout_height="35dp" /></RelativeLayout>
</RelativeLayout>
repair_list_camera.xml 选中图片布局页
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical" android:layout_width="match_parent"android:layout_height="120dp"><LinearLayout
android:layout_marginTop="10dp"android:layout_marginRight="10dp"android:id="@id/item_camera"android:orientation="vertical"android:background="#f2f2f2"android:layout_width="match_parent"android:layout_height="match_parent"><ImageView
android:layout_marginTop="20dp"android:layout_gravity="center"android:background="@drawable/camera"android:layout_width="40dp"android:layout_height="40dp" /><TextView
android:text="@string/camera"android:textSize="18sp"android:textColor="#cfcfcf"android:layout_marginTop="5dp"android:gravity="center"android:layout_width="match_parent"android:layout_height="wrap_content" /></LinearLayout>
</RelativeLayout>
repair_list_photo.xml 拍照布局页
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical" android:layout_width="match_parent"android:layout_height="120dp" ><RelativeLayout
android:layout_marginTop="10dp"android:layout_marginRight="10dp"android:id="@id/item_container"android:background="#f2f2f2"android:layout_width="match_parent"android:layout_height="match_parent"><ImageView
android:id="@id/item_photo"android:layout_centerInParent="true"android:layout_width="match_parent"android:layout_height="match_parent" /><LinearLayout
android:id="@id/item_delete"android:layout_alignParentRight="true"android:gravity="center"android:alpha=".8"android:background="@drawable/bg_repair_delete"android:layout_width="16dp"android:layout_height="16dp"><ImageView
android:background="@drawable/x"android:layout_width="10dp"android:layout_height="10dp" /></LinearLayout></RelativeLayout>
</RelativeLayout>
fragment_camera.xml 弹出页布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical" android:layout_width="match_parent"android:layout_height="match_parent"><LinearLayout
android:background="@drawable/bg_fragment_album"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="wrap_content"><TextView
android:id="@id/camera"android:text="@string/camera"android:gravity="center"android:textColor="#0077f4"android:textSize="18dp"android:layout_width="match_parent"android:layout_height="50dp" /><View
android:background="@color/darkgray"android:layout_width="match_parent"android:layout_height="0.5dp"/><TextView
android:id="@id/album"android:text="@string/album"android:gravity="center"android:textColor="#0077f4"android:textSize="18dp"android:layout_width="match_parent"android:layout_height="50dp" /></LinearLayout><TextView
android:layout_marginTop="10dp"android:background="@drawable/bg_fragment_album"android:id="@id/cancel"android:gravity="center"android:text="@string/cancel"android:textSize="18dp"android:layout_marginBottom="10dp"android:textColor="#0077f4"android:layout_width="match_parent"android:layout_height="50dp" /></LinearLayout>
album_list_photo.xml 相册子项布局文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical" android:layout_width="match_parent"android:layout_height="120dp"><RelativeLayout
android:layout_marginRight="5dp"android:layout_marginTop="5dp"android:background="@color/grey"android:layout_width="match_parent"android:layout_height="match_parent"><ImageView
android:id="@id/item_photo"android:layout_width="match_parent"android:layout_height="match_parent" /><TextView
android:id="@id/item_text"android:layout_alignParentRight="true"android:layout_marginRight="3dp"android:layout_marginTop="3dp"android:alpha=".8"android:textColor="@color/white"android:textSize="16dp"android:gravity="center"android:background="@drawable/bg_album"android:layout_width="25dp"android:layout_height="25dp" /></RelativeLayout></RelativeLayout>