封装android开发中常用的Utils,也许你的项目只需要这一个库就完全够了。不信你看,有图有真相。集成优雅的日志打印风格、app引导页面两行代码实现(也可用作轮播广告图,支持加载本地和网络,一键开启自动翻页功能,内设好几种翻页动画)、高仿iOS进度条和对话框、activity基类的封装(可继承自BaseActivity自行拓展)、常用自定义View(圆角头像等)、Glide一行代码加载图片、万能的RecycleView适配器(一行代码添加脚布局头布局,支持多级展开页面,内部已优化,支持多种列表动画效果,支持上啦加载下拉刷新等)、数据库操作GreenDao的使用案例、网络请求OkGo的二次封装(一行代码发起请求,支持缓存,文件上传下载进度监听,自定义session机制等等)、可直接依赖使用,喜欢的话不妨star一下吧。
E-mail:dev_zwy@aliyun.com
即将更新
+ ===============================================================
--- V2.4.2:增加Tab+ViewPager实现滑动控件封装
--- V2.4.3:对android6.0权限检测进行封装,调用只需一行代码
--- V2.4.4:增加多媒体选择库
--- V2.4.5:将增加仿微信朋友圈九图预览控件
--- V2.4.6:增加多媒体选择工具(可选择视频、图片、gif)
+ ==============================================================
- 注:项目编译异常时请使用如下代码跳过编译时的异常检测:
//打包时跳过检测过期的Api以及系统自检的错误代码
aaptOptions {
cruncherEnabled = false
useNewCruncher = false
}
lintOptions {
checkReleaseBuilds false
// Or, if you prefer, you can continue to check for errors in release builds,
// but continue the build even when errors are found:
abortOnError false
}
- 优雅的日志输出
- 突破系统日志打印长度的限制
- 自动校验Json串输出格式
- 打印异常输出位置(精确到行)
- 日志一目了然
private String json = "{\"key_a\":999,\"key_b\":\"这是b的值\"}";
private String text = "这是一条测试日志";
Log.d(json);
Log.e(json);
Log.w(json);
Log.d(text);
Log.e(text);
Log.w(text);
- 引导界面导航效果
- 支持根据服务端返回的数据动态设置广告条的总页数
- 支持大于等于1页时的无限循环自动轮播、手指按下暂停轮播、抬起手指开始轮播
- 支持自定义指示器位置和广告文案位置
- 支持图片指示器和数字指示器
- 支持 ViewPager 各种切换动画
- 支持选中特定页面
- 支持监听 item 点击事件
- 加载网络数据时支持占位图设置,避免出现整个广告条空白的情况
- 多个 ViewPager 跟随滚动
<com.zwy.kutils.widget.guide.BGABanner
android:id="@+id/banner_guide_content"
style="@style/MatchMatch"
app:banner_pageChangeDuration="1000"
app:banner_pointAutoPlayAble="false"
app:banner_pointContainerBackground="@android:color/transparent"
app:banner_pointDrawable="@drawable/bga_banner_selector_point_hollow"
app:banner_pointTopBottomMargin="15dp"
app:banner_transitionEffect="alpha" />
有多种配置数据源的方式,这里仅列出三种方式。
配置数据源的方式1:通过传入数据模型并结合 Adapter 的方式配置数据源。这种方式主要用于加载网络图片,以及实现少于3页时的无限轮播
mContentBanner.setAdapter(new BGABanner.Adapter<ImageView, String>() {
@Override
public void fillBannerItem(BGABanner banner, ImageView itemView, String model, int position) {
Glide.with(MainActivity.this)
.load(model)
.placeholder(R.drawable.holder)
.error(R.drawable.holder)
.centerCrop()
.dontAnimate()
.into(itemView);
}
});
mContentBanner.setData(Arrays.asList("网络图片路径1", "网络图片路径2", "网络图片路径3"), Arrays.asList("提示文字1", "提示文字2", "提示文字3"));
配置数据源的方式2:通过直接传入视图集合的方式配置数据源,主要用于自定义引导页每个页面布局的情况
List<View> views = new ArrayList<>();
views.add(BGABannerUtil.getItemImageView(this, R.drawable.ic_guide_1));
views.add(BGABannerUtil.getItemImageView(this, R.drawable.ic_guide_2));
views.add(BGABannerUtil.getItemImageView(this, R.drawable.ic_guide_3));
mContentBanner.setData(views);
配置数据源的方式3:通过传入图片资源 id 的方式配置数据源,主要用于引导页每一页都是只显示图片的情况
mContentBanner.setData(R.drawable.uoko_guide_foreground_1, R.drawable.uoko_guide_foreground_2, R.drawable.uoko_guide_foreground_3);
mContentBanner.setDelegate(new BGABanner.Delegate<ImageView, String>() {
@Override
public void onBannerItemClick(BGABanner banner, ImageView itemView, String model, int position) {
Toast.makeText(banner.getContext(), "点击了" + position, Toast.LENGTH_SHORT).show();
}
});
4.设置「进入按钮」和「跳过按钮」控件资源 id 及其点击事件,如果进入按钮和跳过按钮有一个不存在的话就传 0,在 BGABanner 里已经帮开发者处理了防止重复点击事件,在 BGABanner 里已经帮开发者处理了「跳过按钮」和「进入按钮」的显示与隐藏
mContentBanner.setEnterSkipViewIdAndDelegate(R.id.btn_guide_enter, R.id.tv_guide_skip, new BGABanner.GuideDelegate() {
@Override
public void onClickEnterOrSkip() {
startActivity(new Intent(GuideActivity.this, MainActivity.class));
finish();
}
});
<declare-styleable name="BGABanner">
<!-- 指示点容器背景 -->
<attr name="banner_pointContainerBackground" format="reference|color" />
<!-- 指示点背景 -->
<attr name="banner_pointDrawable" format="reference" />
<!-- 指示点容器左右内间距 -->
<attr name="banner_pointContainerLeftRightPadding" format="dimension" />
<!-- 指示点上下外间距 -->
<attr name="banner_pointTopBottomMargin" format="dimension" />
<!-- 指示点左右外间距 -->
<attr name="banner_pointLeftRightMargin" format="dimension" />
<!-- 指示器的位置 -->
<attr name="banner_indicatorGravity">
<flag name="top" value="0x30" />
<flag name="bottom" value="0x50" />
<flag name="left" value="0x03" />
<flag name="right" value="0x05" />
<flag name="center_horizontal" value="0x01" />
</attr>
<!-- 是否开启自动轮播 -->
<attr name="banner_pointAutoPlayAble" format="boolean" />
<!-- 自动轮播的时间间隔 -->
<attr name="banner_pointAutoPlayInterval" format="integer" />
<!-- 页码切换过程的时间长度 -->
<attr name="banner_pageChangeDuration" format="integer" />
<!-- 页面切换的动画效果 -->
<attr name="banner_transitionEffect" format="enum">
<enum name="defaultEffect" value="0" />
<enum name="alpha" value="1" />
<enum name="rotate" value="2" />
<enum name="cube" value="3" />
<enum name="flip" value="4" />
<enum name="accordion" value="5" />
<enum name="zoomFade" value="6" />
<enum name="fade" value="7" />
<enum name="zoomCenter" value="8" />
<enum name="zoomStack" value="9" />
<enum name="stack" value="10" />
<enum name="depth" value="11" />
<enum name="zoom" value="12" />
</attr>
<!-- 提示文案的文字颜色 -->
<attr name="banner_tipTextColor" format="reference|color" />
<!-- 提示文案的文字大小 -->
<attr name="banner_tipTextSize" format="dimension" />
<!-- 加载网络数据时覆盖在 BGABanner 最上层的占位图 -->
<attr name="banner_placeholderDrawable" format="reference" />
<!-- 是否是数字指示器 -->
<attr name="banner_isNumberIndicator" format="boolean" />
<!-- 数字指示器文字颜色 -->
<attr name="banner_numberIndicatorTextColor" format="reference|color" />
<!-- 数字指示器文字大小 -->
<attr name="banner_numberIndicatorTextSize" format="dimension" />
<!-- 数字指示器背景 -->
<attr name="banner_numberIndicatorBackground" format="reference" />
<!-- 当只有一页数据时是否显示指示器,默认值为 false -->
<attr name="banner_isNeedShowIndicatorOnOnlyOnePage" format="boolean" />
<!-- 自动轮播区域距离 BGABanner 底部的距离,用于使指示器区域与自动轮播区域不重叠 -->
<attr name="banner_contentBottomMargin" format="dimension"/>
</declare-styleable>
DialogUIUtils.showLoadingHorizontal(mContext, "请稍后……", true, false, true).show();
DialogUIUtils.showTwoButtonAlertDialog(mContext, "提示", "是否进入主页", "取消", new View.OnClickListener() {
@Override
public void onClick(View v) {
}
}, "进入", new View.OnClickListener() {
@Override
public void onClick(View v) {
readyGoThenKill(MainActivity.class);
}
}, false);
new ActionSheetDialog(mContext)
.builder()
.setCancelable(false)
.setCanceledOnTouchOutside(false)
.setTitle("提示")
.addSheetItem("删除", ActionSheetDialog.SheetItemColor.Red, new ActionSheetDialog.OnSheetItemClickListener() {
@Override
public void onClick(int which) {
}
})
.addSheetItem("进入", ActionSheetDialog.SheetItemColor.Blue, new ActionSheetDialog.OnSheetItemClickListener() {
@Override
public void onClick(int which) {
readyGoThenKill(MainActivity.class);
}
}).show();
public abstract class BaseActivity extends AppCompatActivity {
protected BaseActivity mContext;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (isTranslucentStatus()!=0) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
setTranslucentStatus(true);
SystemBarTintManager tintManager = new SystemBarTintManager(this);
tintManager.setStatusBarTintEnabled(true);
tintManager.setStatusBarTintResource(isTranslucentStatus());//通知栏所需颜色
}
}
Bundle extras = getIntent().getExtras();
if (null != extras) {
getBundleExtras(extras);
}
mContext = this;
setContentView(getLayoutId());
ButterKnife.bind(this);
AppManager.getAppManager().addActivity(this);
initView(savedInstanceState);
initData();
}
/**
* 是否需要沉浸式状态栏 不需要时返回0 需要时返回颜色
*
* @return StatusBarTintModle(boolean isTranslucentStatus, int color);
*/
protected abstract @ColorRes int isTranslucentStatus();
/**
* 设置布局ID
*
* @return 资源文件ID
*/
protected abstract
@LayoutRes
int getLayoutId();
/**
* 初始化View
*
* @param savedInstanceState aty销毁时保存的临时参数
*/
protected abstract void initView(Bundle savedInstanceState);
/**
* 初始化数据源
*/
protected abstract void initData();
/**
* Bundle 传递数据
*
* @param extras
*/
protected abstract void getBundleExtras(Bundle extras);
@TargetApi(19)
private void setTranslucentStatus(boolean on) {
Window win = getWindow();
WindowManager.LayoutParams winParams = win.getAttributes();
final int bits = WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
if (on) {
winParams.flags |= bits;
} else {
winParams.flags &= ~bits;
}
win.setAttributes(winParams);
}
//Toast显示
protected void showToast(String string) {
DialogUIUtils.showToast(string);
}
@Override
protected void onDestroy() {
super.onDestroy();
ButterKnife.unbind(this);
AppManager.getAppManager().finishActivity(this);
}
/**
* 界面跳转
*
* @param cls 目标Activity
*/
protected void readyGo(Class<?> cls) {
readyGo(cls, null);
}
/**
* 跳转界面,传参
*
* @param cls 目标Activity
* @param bundle 数据
*/
protected void readyGo(Class<?> cls, Bundle bundle) {
Intent intent = new Intent(this, cls);
if (null != bundle)
intent.putExtras(bundle);
startActivity(intent);
}
/**
* 跳转界面并关闭当前界面
*
* @param cls 目标Activity
*/
protected void readyGoThenKill(Class<?> cls) {
readyGoThenKill(cls, null);
}
/**
* @param cls 目标Activity
* @param bundle 数据
*/
protected void readyGoThenKill(Class<?> cls, Bundle bundle) {
readyGo(cls, bundle);
finish();
}
/**
* startActivityForResult
*
* @param cls 目标Activity
* @param requestCode 发送判断值
*/
protected void readyGoForResult(Class<?> cls, int requestCode) {
Intent intent = new Intent(this, cls);
startActivityForResult(intent, requestCode);
}
/**
* startActivityForResult with bundle
*
* @param cls 目标Activity
* @param requestCode 发送判断值
* @param bundle 数据
*/
protected void readyGoForResult(Class<?> cls, int requestCode, Bundle bundle) {
Intent intent = new Intent(this, cls);
if (null != bundle) {
intent.putExtras(bundle);
}
startActivityForResult(intent, requestCode);
}
}
<!--border_color 边框的颜色 border_width边框的宽度-->
<com.zwy.kutils.widget.customview.circleimageview.CircleImageView
android:id="@+id/cv"
android:layout_width="40dp"
android:layout_height="40dp"
app:border_color="@color/blue_3682"
app:border_width="2dp"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:layout_marginLeft="15dp"/>
Glide.with(mContext).load("http://img14.poco.cn/mypoco/myphoto/20130410/14/173420773201304101425203047950463653_010.jpg")
.asBitmap().placeholder(R.drawable.nullimg).into(mCv);
apply plugin: 'org.greenrobot.greendao'
android {
greendao {
schemaVersion 1//数据库版本号
daoPackage 'com.alan.kutilssample.greendao'//设置DaoMaster、DaoSession、Dao包名
targetGenDir 'src/main/java'//设置DaoMaster、DaoSession、Dao目录
//targetGenDirTestA:设置生成单元测试目录
//generateTests:设置自动生成单元测试用例
}
}
- @Entity-------实体注解
- @NotNull-------设置表中当前列的值不可为空
- @Convert-------指定自定义类型
- @Generated-------GreenDao运行所产生的构造函数或者方法,被此标注的代码可以变更或者下次运行时清除
- @Id-------主键Long型,可以通过@Id(autoincrement=true)设置自增长。通过这个注解标记的字段必须是Long,数据库中表示它就是主键,并且默认是自增的。
- @Index-------使用@Index作为一个属性来创建一个索引;定义多列索引(@linkEntity#indexes())
- @JoinEntity-------定义表连接关系
- @JoinProperty-------定义名称和引用名称属性关系
- @Keep-------注解的代码段在GreenDao下次运行时保持不变---1.注解实体类:默认禁止修改此类---2.注解其他代码段,默认禁止修改注解的代码段
- @OrderBy------- 指定排序
- @Property-------设置一个非默认关系映射所对应的列名,默认是的使用字段名,举例:@Property(nameInDb="name")
- @ToMany-------定义与多个实体对象的关系
- @ToOne-------定义与另一个实体(一个实体对象)的关系
- @Transient-------添加次标记之后不会生成数据库表的列
- @Unique-------向数据库列添加了一个唯一的约束
/**
* ================================================================
* 创建时间:2017/7/31 上午10:42
* 创建人:Alan
* 文件描述:greendao测试表 注意 该类不能实现Serializable接口
* 至尊宝:长夜漫漫无心睡眠,我以为只有我睡不着,原来晶晶姑娘你也睡不着 !
* ================================================================
*/
@Entity
public class User{
@Id(autoincrement = true)
private long id;
private String name;
private int age;
}
private void initGreenDao() {
DaoMaster.DevOpenHelper openHelper = new DaoMaster.DevOpenHelper(getApplicationContext(), DBNAME);
Database db = openHelper.getWritableDb();
DaoMaster daoMaster = new DaoMaster(db);
mDaoSession = daoMaster.newSession();
Log.d("GreenDao初始化成功");
}
public DaoSession getDaoSession() {
return mDaoSession;
}
//添加一条数据
String userName = Utils.getRandomName(1);
int age = new Random().nextInt(30);
User user = new User();
user.setName(userName);
user.setAge(age);
userDao.insert(user);
apped("已成功插入一条(name = " + userName + ",age = " + age + ")的数据");
//删除全部数据
// //删除一般配和查询完成
// QueryBuilder<User> userQueryBuilder = userDao.queryBuilder();
// List<User> userList = userQueryBuilder.where(UserDao.Properties.Name.eq("志文")).list();
// User u = (userList != null && userList.size() > 0) ? userList.get(0) : null;
// if (u == null) {
// apped("当前要删除的用户不存在");
// return;
// }
// userDao.delete(u);
// apped("删除成功");
userDao.deleteAll();
apped("已删除全部数据");
//修改
QueryBuilder<User> userQueryBuilder = userDao.queryBuilder();
List<User> userList = userQueryBuilder.where(UserDao.Properties.Name.eq("志强")).list();
if (userList != null && userList.size() > 0) {
userList.get(0).setName("张三");
userDao.update(userList.get(0));
apped("已将姓名为志强的数据修改姓名为张三");
} else {
apped("要修改的数据不存在");
}
//查询
apped("当前User表中所有的数据:");
for (int i = 0; i < userDao.loadAll().size(); i++) {
apped(userDao.loadAll().get(i).toString());
}
private class MyAdapter extends BaseQuickAdapter<TitleModel, BaseViewHolder> {
public MyAdapter(@Nullable List<TitleModel> data) {
super(R.layout.item_rv_main, data);//item 资源ID
}
/**
* Implement this method and use the helper to adapt the view to the given item.
*
* @param helper A fully initialized helper.
* @param item The item that needs to be displayed.
*/
@Override
protected void convert(BaseViewHolder helper, TitleModel item) {
helper.setText(R.id.title, item.getTitle());//给控件设值
helper.addOnClickListener(R.id.clicktestview);//给子view添加点击事件
}
}
private void initAdapter() {
mAdapter = new MyAdapter(null);//可以直接传入数据,数据未获取到的情况下可以传null
mAdapter.openLoadAnimation(BaseQuickAdapter.ALPHAIN);//设置列表加载动画
mAdapter.isFirstOnly(false);//是否仅在第一次加载列表时展示动画
mRv.setLayoutManager(new LinearLayoutManager(mContext));
mRv.setAdapter(mAdapter);
}
//第一种方式
List<TitleModel> list = new ArrayList<>();
list.add(new TitleModel("GreenDao使用", true));
list.add(new TitleModel("功能2", false));
list.add(new TitleModel("功能3", false));
list.add(new TitleModel("...", false));
mAdapter.setNewData(list);//添加集合数据
//第二种方式
for (int i = 0; i < 6; i++) {
mAdapter.addData(new TitleModel("RV功能展示", false));//添加单条数据
}
// mAdapter.setEmptyView(getEmptyview());//设置没有数据时的空白页面
// mAdapter.addFooterView(getFootView());//添加一个脚布局 有三个相应的方法
// mAdapter.addHeaderView(getHeaderView());//添加一个头布局 有三个相应的方法
// mAdapter.notify...();//内容发生改变时刷新方法
// mAdapter.remove();
//适配器Item点击事件
mAdapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() {
@Override
public void onItemClick(BaseQuickAdapter adapter, View view, int position) {
if( ((TitleModel)adapter.getData().get(position)).getIndex()){
toNext(position);
}else {
showToast("您点击了第" + (position + 1) + "条数据");
}
}
});
//适配器子view点击事件
mAdapter.setOnItemChildClickListener(new BaseQuickAdapter.OnItemChildClickListener() {
@Override
public void onItemChildClick(BaseQuickAdapter adapter, View view, int position) {
switch (view.getId()) {
case R.id.clicktestview:
showToast("您点击了第" + (position + 1) + "条数据的子view");
break;
}
}
});
//下拉刷新监听
mSw.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
setAdapterData();
}
});
//加载更多监听
mAdapter.setOnLoadMoreListener(new BaseQuickAdapter.RequestLoadMoreListener() {
@Override
public void onLoadMoreRequested() {
addData();
}
}, mRv);
Random random = new Random();
int a = random.nextInt(3);
Log.d("nextInt = " + a);
if (a == 0) {
Log.d("nextInt = " + a);
for (int i = 0; i < 5; i++) {
mAdapter.addData(new TitleModel("我是新添加的第" + (i + 1) + "条数据", false));
}
mAdapter.loadMoreComplete();//关闭“正在加载更多提示”
} else if (a == 1) {
mAdapter.loadMoreEnd();//显示没有更多数据
} else if (a == 2) {
mAdapter.loadMoreFail();//显示加载失败,点击重试
} else {
Log.d("nextInt = " + a);
for (int i = 0; i < 20; i++) {
mAdapter.addData(new TitleModel("我是新添加的第" + (i + 1) + "条数据", false));
}
mAdapter.loadMoreComplete();
}
//发起一个简单的网络请求
OkGo.<String>post("").tag("").params("key", "v").execute(new AbsCallback<String>() {
@Override
public void onSuccess(Response<String> response) {
}
@Override
public String convertResponse(okhttp3.Response response) throws Throwable {
return null;
}
});
根据EventBus3.0源码拓展而来,实现post事件和接收事件时可以通过tag进行筛选,比如EventBus.getDefault().post("内容","tag1");发出的事件只有@Subscribe(threadMode=ThreadMode.MAIN,tag="tag1")这样的形式接收才能收到,如果在发送时未指定tag,那么接收时也不要使用tag,相反的,如果发送时用了tag,而接收时未使用tag,也是收不到的。粘性事件保持与EventBus3.0一样的机制,未加入tag
EventBus.getDefault().register(mContext);
EventBus.getDefault().unregister(mContext);
EventBus.getDefault().post("我是事件内容1");//未指定tag,接收时也不要写入tag
EventBus.getDefault().post("我是事件内容2","tag1");//指定tag,只有接收时指定了该tag才能收到事件
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent(String msg){
// TODO: 2017/8/1 我能接收到 "我是事件内容1"
}
@Subscribe(threadMode = ThreadMode.MAIN,tag = "tag1")
public void onEvent(String msg){
// TODO: 2017/8/1 我能接收到 "我是事件内容2"
}
EventBus.getDefault().postSticky("我是粘性事件");
@Subscribe(threadMode = ThreadMode.MAIN,sticky = true)
public void onEvent(String msg){
// TODO: 2017/8/1 我能接收到粘性事件
}
YoYo.with(Techniques.Tada)
.duration(700)
.repeat(5)
.playOn(findViewById(R.id.edit_area));
Flash
, Pulse
, RubberBand
, Shake
, Swing
, Wobble
, Bounce
, Tada
, StandUp
, Wave
Hinge
, RollIn
, RollOut
,Landing
,TakingOff
,DropOut
BounceIn
, BounceInDown
, BounceInLeft
, BounceInRight
, BounceInUp
FadeIn
, FadeInUp
, FadeInDown
, FadeInLeft
, FadeInRight
FadeOut
, FadeOutDown
, FadeOutLeft
, FadeOutRight
, FadeOutUp
FlipInX
, FlipOutX
, FlipOutY
RotateIn
, RotateInDownLeft
, RotateInDownRight
, RotateInUpLeft
, RotateInUpRight
RotateOut
, RotateOutDownLeft
, RotateOutDownRight
, RotateOutUpLeft
, RotateOutUpRight
SlideInLeft
, SlideInRight
, SlideInUp
, SlideInDown
SlideOutLeft
, SlideOutRight
, SlideOutUp
, SlideOutDown
ZoomIn
, ZoomInDown
, ZoomInLeft
, ZoomInRight
, ZoomInUp
ZoomOut
, ZoomOutDown
, ZoomOutLeft
, ZoomOutRight
, ZoomOutUp
HideUtil.init(context);
//手动隐藏
HideUtil.hideSoftKeyboard(getActivity());
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.3'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
//greenDao
classpath 'org.greenrobot:greendao-gradle-plugin:3.2.1'//加入这一行代码
}
}
allprojects {
repositories {
jcenter()
maven { url "https://jitpack.io" }//加入这一行代码
}
}
compile 'com.github.zwy:kutils:+'
compile project(':kutils')
repositories { flatDir { dirs '../aars' } }
compile(name: 'kutils-release', ext: 'aar')
@Override
public void onCreate() {
super.onCreate();
KUtilLibs.init(true, TAG, this, new HttpBuild.Build(null, 10, HttpBuild.CookieType.MemoryCookieStore));
}
集成过程出现问题可联系本人QQ:3648415(注明来自github)
Copyright 2015 bingoogolapple
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.