Glide 4
一、集成和基本用法
Github地址: https://github.com/bumptech/glide
app或lib级别的build.gradle文件添加依赖:
dependencies {
compile 'com.github.bumptech.glide:glide:4.0.0-RC1'
annotationProcessor 'com.github.bumptech.glide:compiler:4.0.0-RC1'
}
在proguard.pro/proguard.cfg中添加混淆:
-keep public class * implements com.bumptech.glide.module.GlideModule
-keep public class * extends com.bumptech.glide.AppGlideModule
-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {
**[] $VALUES;
public *;
}
# for DexGuard only
-keepresourcexmlelements manifest/application/meta-data@value=GlideModule
基本用法
//加载图片
Glide.with(fragment)
.load(myUrl)
.into(imageView);
//取消加载
Glide.with(fragment).clear(imageView);
//GlideApp提供placeholder、fitCenter等操作方法
GlideApp.with(fragment)
.load(myUrl)
.placeholder(R.drawable.placeholder)
.fitCenter()
.into(imageView);
//这部分可在RecyclerView滚动状态(RecyclerView.SCROLL_STATE_SETTLING)暂停,之后恢复
GlideApp.with(context).pauseRequests();//暂停加载请求
GlideApp.with(context).resumeRequests();//恢复加载请求
//override设置图片尺寸
GlideApp.with(mContext)
.load(url)
.override(width,height)
.into(view);
ps:当你在with方法中传入的Activity或Fragment被销毁的时候,Glide会自动取消加载并且回收所有的加载过程中所使用的资源
二、占位符
Glide支持三种占位符:
Placeholder 请求图片加载中
Error 请求图片加载错误
Fallback 请求url/model为空
使用方法
GlideApp.with(fragment)
.load(url)
.placeholder(R.drawable.placeholder)
.error(new ColorDrawable(Color.RED))
.fallback(new ColorDrawable(Color.GREY))
.into(view);
三、缓存设置
设置内存缓存和磁盘缓存,注意@GlideModule的添加
@GlideModule
public class CustomGlideModule extends AppGlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {
int memoryCacheSizeBytes = 1024 * 1024 * 20; // 20mb
builder.setMemoryCache(new LruResourceCache(memoryCacheSizeBytes));//设置内存缓存20M
builder.setDiskCache(new InternalCacheDiskCacheFactory(context, diskCacheSizeBytes*5));设置磁盘缓存100M
}
}
GlideApp.get(context).clearMemory();//清除内存缓存
GlideApp.get(context).clearDiskCache();//清除磁盘缓存
GlideApp.with(getActivity())
.load(url)
.skipMemoryCache(true)//使用时也可跳过内存缓存
.diskCacheStrategy(DiskCacheStrategy.ALL)//使用时选择磁盘缓存策略
.into(imageView);
磁盘缓存策略:
DiskCacheStrategy.ALL 使用DATA和RESOURCE缓存远程数据,仅使用RESOURCE来缓存本地数据。
DiskCacheStrategy.NONE 不使用磁盘缓存 DiskCacheStrategy.DATA
在资源解码前就将原始数据写入磁盘缓存 DiskCacheStrategy.RESOURCE
在资源解码后将数据写入磁盘缓存,即经过缩放等转换后的图片资源。 DiskCacheStrategy.AUTOMATIC
根据原始图片数据和资源编码策略来自动选择磁盘缓存策略。
四、Options配置
RequestOptions:可通过apply()设置
可配置项:
Placeholders 占位符
Transformations 变换
Caching Strategies 缓存策略
组件特定参数:编码质量,解码参数等
TransitionOptions:可通过transition()设置
可配置项
淡入
交叉淡入
不过渡
RequestBuilder:可通过as获得一个RequestBuilder对象
可配置项:
指定加载类型。asBitmap()、asGif()、asDrawable()、asFile()。
指定要加载url/model。
指定要加载到那个View。
指定要应用的RequestOption
指定要应用的TransitionOption
指定要加载的缩略图
eg:
RequestBuilder requestBuilder = Glide.with(this).asDrawable();
requestBuilder.apply(new RequestOptions());
requestBuilder.transition(new DrawableTransitionOptions());
五、自定义扩展
1.扩展操作功能,比如缩略图
新建一个类CustomGlideExtension,注意两个注解@GlideExtension @GlideOption;且miniThumb方法必须是静态的
@GlideExtension
public class CustomGlideExtension {
//缩略图的最小尺寸,单位:px
private static final int MINI_THUMB_SIZE = 100;
/**
* 将构造方法设为私有,作为工具类使用
*/
private CustomGlideExtension() {
}
/**
* 1.自己新增的方法的第一个参数必须是RequestOptions options
* 2.方法必须是静态的
* @param options
*/
@GlideOption
public static void miniThumb(RequestOptions options) {
options
.fitCenter()
.override(MINI_THUMB_SIZE);
}
}
编译工程,打开build目录中的GlideOptions,Glide为我们自动生成了这样的方法
public class GlideOptions extends RequestOptions {
/**
* @see CustomGlideExtension#miniThumb(RequestOptions)
*/
public GlideOptions miniThumb() {
CustomGlideExtension.miniThumb(this);
return this;
}
/**
* @see CustomGlideExtension#miniThumb(RequestOptions)
*/
public static GlideOptions miniThumbOf() {
return new GlideOptions().miniThumb();
}
...
}
使用方式
GlideApp.with(fragment)
.load(url)
.miniThumb(thumbnailSize)//直接就可以用了。
.into(imageView);
2.增加支持类型,目前Glide已经支持gif,但我们还是拿他做例子
和上面扩展的方式一样,用注解的方式
新建一个类CustomGlideExtension ;注意注解@GlideExtension @GlideType的添加以及asGIF需静态。此外GifDrawable这个类是GIF动画播放的实现,继承Drawable实现Animatable实现逐帧播放功能,有兴趣可以去看下源码。
@GlideExtension
public class CustomGlideExtension {
private static final RequestOptions DECODE_TYPE_GIF = GlideOptions.decodeTypeOf(GifDrawable.class).lock();
@GlideType(GifDrawable.class)
public static void asGIF(RequestBuilder<GifDrawable> requestBuilder) {
requestBuilder
.transition(new DrawableTransitionOptions())
.apply(DECODE_TYPE_GIF);
}
}
使用方式
GlideApp.with(fragment)
.asGIF()
.load(url)
.into(imageView);
Alibaba-ARouter
一、简介、集成、基本用法
路由框架的基本功能:
分发:把一个URL或者请求按照一定的规则分配给一个服务或者页面来处理,这个流程就是分发
管理:将组件和页面按照一定的规则管理起来,在分发的时候提供搜索、加载、修改等操作
控制:就像路由器一样,路由的过程中,会有限速、屏蔽等一些控制操作,路由框架也需要在路由的过程中,对路由操作做一些定制性的扩展,如AOP,后期的功能更新等
ARouter的七大优势
github地址:https://github.com/alibaba/arouter
添加依赖配置
android {
defaultConfig {
...
javaCompileOptions {
annotationProcessorOptions {
arguments = [ moduleName : project.getName() ]
}
}
}
}
dependencies {
compile 'com.alibaba:arouter-api:x.x.x'
annotationProcessor 'com.alibaba:arouter-compiler:x.x.x'
...
}
// Kotlin配置参考官方描述
添加混淆规则
-keep public class com.alibaba.android.arouter.routes.**{*;}
-keep class * implements com.alibaba.android.arouter.facade.template.ISyringe{*;}
初始化
if (isDebug()) { // 这两行必须写在init之前,否则这些配置在init过程中将无效
ARouter.openLog(); // 打印日志
ARouter.openDebug(); // 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险)
}
ARouter.init(mApplication); // 尽可能早,推荐在Application中初始化
添加注解
// 在支持路由的页面上添加注解(必选)
// 这里的路径需要注意的是至少需要有两级,/xx/xx
@Route(path = "/test/activity")
public class YourActivity extend Activity {
...
}
发起路由操作
// 1. 应用内简单的跳转(通过URL跳转在'进阶用法'中)
ARouter.getInstance().build("/test/activity").navigation();
// 2. 跳转并携带参数
ARouter.getInstance().build("/test/1")
.withLong("key1", 666L)
.withString("key3", "888")
.withObject("key4", new Test("Jack", "Rose"))
.navigation();
最佳实践:以上这种方式在阅读代码跳转的哪里完全不知道,基本靠搜索,下面提供了解决方案
二、URL跳转
大致流程:写一个类继承Activity并在AndroidManifest.xml中配置并对外暴露android:exported=true。由这个Activity来接收所有的URL请求,然后把url转发给ARouter。ARouter会根据url来查找合适的接收者。
新建一个类SchameFilterActivity 来接收所有url请求,并转发给ARouter
// 新建一个Activity用于监听Schame事件,之后直接把url传递给ARouter即可
public class SchameFilterActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Uri uri = getIntent().getData();
ARouter.getInstance().build(uri).navigation();
finish();
}
}
AndroidManifest.xml配置
<activity android:name=".activity.SchameFilterActivity">
<!-- Schame -->
<intent-filter>
<data
android:host="m.aliyun.com"
android:scheme="arouter"/>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
</intent-filter>
</activity>
这里假设发起的url为:arouter://m.aliyun.com/test/activity1?name=huangshunbo&obj={“name”:”jack”,”id”:”666”}
那么匹配的Activity是长什么样的呢?
@Route(path = "/test/activity1")
public class Test1Activity extends Activity {
@Autowired(name = "name") // 通过name来映射URL中的不同参数
boolean mName;
@Autowired
TestObj obj; // 支持解析自定义对象,URL中使用json传递
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ARouter.getInstance().inject(this);
// ARouter会自动对字段进行赋值,无需主动获取
Log.d("param", name);
}
}
TestObj实体类会被自动解析出来,也可以自定义序列号方式
@Route(path = "/service/json")
public class JsonServiceImpl implements SerializationService {
@Override
public void init(Context context) {
}
@Override
public <T> T json2Object(String text, Class<T> clazz) {
return JSON.parseObject(text, clazz);
}
@Override
public String object2Json(Object instance) {
return JSON.toJSONString(instance);
}
}
三、拦截器
这里以登录检查做例子
Activity的Route注解
//extras是int型
@Route(path = "/test/usercenter" , extras = 100)
// 拦截器会在跳转之间执行,多个拦截器会按优先级顺序依次执行
@Interceptor(priority = 8, name = "测试用拦截器")
public class TestInterceptor implements IInterceptor {
@Override
public void process(Postcard postcard, InterceptorCallback callback) {
...
//如果extra是100,那跳转的目的地是需要做是否登录的判断,拦截
if(postcard.getExtra() == 100){
if(!isLogin()){//未登录,跳转到登录界面
//ARouter跳转到登录界面
}
}
...//各种拦截处理
//最后都没问题了,跳转到目的地
callback.onContinue(postcard);
// callback.onInterrupt(new RuntimeException("我觉得有点异常")); // 觉得有问题,中断路由流程
// onContinue和onInterrupt两种至少需要调用其中一种,否则不会继续路由
}
@Override
public void init(Context context) {
// 拦截器的初始化,会在sdk初始化的时候调用该方法,仅会调用一次
}
}
priority是优先级,可以定义多个拦截器,每次ARouter做跳转的时候都会被所有拦截器按优先级顺序做拦截,做逻辑判断。
ARouter做跳转的时候也可以获得本次路由跳转的结果
// 通过两个参数的navigation方法,可以获取单次跳转的结果
ARouter.getInstance().build("/test/usercenter").navigation(this, new NavigationCallback() {
@Override
public void onFound(Postcard postcard) {
...
}
@Override
public void onLost(Postcard postcard) {
...
}
});
四、降级策略
// 实现DegradeService接口,并加上一个Path内容任意的注解即可
@Route(path = "/xxx/xxx") // 必须标明注解
public class DegradeServiceImpl implements DegradeService {
/**
* Router has lost.
*
* @param postcard meta
*/
@Override
public void onLost(Context context, Postcard postcard) {
// 做降级策略处理
}
/**
* Do your init work in this method, it well be call when processor has been load.
*
* @param context ctx
*/
@Override
public void init(Context context) {
}
}
每个跳转如果跳转失败都会调用onLost。(貌似path是没有意义的。。。???不是很理解)
最佳实践
将跳转的目的地传给H5页面,H5页面可以随时更新。H5页面再根据目的地以及业务需求再进行合适的跳转。
ps:
开源最佳实践:Android平台页面路由框架ARouter
ARouter源码解析
Butter Knife
一、概述
Butter Knife是Jake Wharton创建的一个视图快速注入库。
优势:
1.强大的View绑定和Click事件处理功能,简化代码,提升开发效率
2.方便的处理Adapter里的ViewHolder绑定问题
3.运行时性能影响较低
4.代码清晰,可读性强
对于性能的影响问题:ButterKnife对性能的影响
github:https://github.com/JakeWharton/butterknife
添加依赖
dependencies {
compile 'com.jakewharton:butterknife:8.8.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
}
二、用法
视图绑定@BindView
Activity中
class ExampleActivity extends Activity {
@BindView(R.id.title) TextView title;
@BindView(R.id.subtitle) TextView subtitle;
@BindView(R.id.footer) TextView footer;
@Override public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.simple_activity);
ButterKnife.bind(this);
// TODO Use fields...
}
}
非Activity中,Fragment记得在解绑定
public class FancyFragment extends Fragment {
private Unbinder mUnBinder;
@BindView(R.id.button1) Button button1;
@BindView(R.id.button2) Button button2;
@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fancy_fragment, container, false);
mUnBinder = ButterKnife.bind(this, view);
return view;
}
@Override
public void onDestroy() {
super.onDestroy();
if (mUnBinder != null) {
mUnBinder.unbind();
}
}
}
还有一个常见例子是Adapter的ViewHolder中
public class MyAdapter extends BaseAdapter {
@Override public View getView(int position, View view, ViewGroup parent) {
ViewHolder holder;
if (view != null) {
holder = (ViewHolder) view.getTag();
} else {
view = inflater.inflate(R.layout.whatever, parent, false);
holder = new ViewHolder(view);
view.setTag(holder);
}
holder.name.setText("John Doe");
// etc...
return view;
}
static class ViewHolder {
@BindView(R.id.title)
TextView name;
@BindView(R.id.job_title) TextView jobTitle;
public ViewHolder(View view) {
ButterKnife.bind(this, view);
}
}
}
【一次性绑定多个View】
@BindViews({ R.id.first_name, R.id.middle_name, R.id.last_name })
List<EditText> nameViews;
【设置View属性】
ButterKnife.apply(nameViews, ENABLED, false);
ButterKnife.apply(nameViews, View.ALPHA, 0.0f);
【View的绑定,自动类型转换】
View view = LayoutInflater.from(context).inflate(R.layout.thing, null);
TextView firstName = ButterKnife.findById(view, R.id.first_name);
TextView lastName = ButterKnife.findById(view, R.id.last_name);
ImageView photo = ButterKnife.findById(view, R.id.photo);
资源绑定:@BindBool、@BindColor、@BindDimen、@BindDrawable、@BindInt、@BindString
class ExampleActivity extends Activity {
@BindString(R.string.title) String title;
@BindDrawable(R.drawable.graphic) Drawable graphic;
@BindColor(R.color.red) int red; // int or ColorStateList field
@BindDimen(R.dimen.spacer) Float spacer; // int (for pixel size) or float (for exact value) field
// ...
}
监听绑定
常见的监听:
@OnClick
@OnItemClick
@OnItemSelected
@OnLongClick
@OnPageChange
OnPageChange.Callback
@OnTextChanged
OnTextChanged.Callback
@OnTouch
@OnItemLongClick
@OnCheckedChanged
绑定一个OnClick
@OnClick(R.id.btnLogin)
public void btnLoginClick(View view) {
.....
.....
.....
}
绑定多个OnClick
@OnClick({ R.id.button1, R.id.button2, R.id.button2 })
public void buttonClicks(View view) {
switch(view.getId()) {
case R.id.button1:
Toast.makeText(this, "Button1 clicked!", LENGTH_SHORT).show();
break;
case R.id.button1:
Toast.makeText(this, "Button2 Clicked!", LENGTH_SHORT).show();
break;
case R.id.button1:
Toast.makeText(this, "Button3 clicked!", LENGTH_SHORT).show();
break;
}
}
其他
@OnLongClick(R.id.hello) boolean sayGetOffMe() {
Toast.makeText(this, "Let go of me!", LENGTH_SHORT).show();
return true;
}
@OnItemClick(R.id.list_of_things) void onItemClick(int position) {
Toast.makeText(this, "You clicked: " + adapter.getItem(position), LENGTH_SHORT).show();
}
@OnItemSelected({R.id.spinnerCountry})
void onItemSelected(Spinner spinner, int position) {
}
@OnItemSelected(value=R.id.spinnerCountry, callback = OnItemSelected.Callback.NOTHING_SELECTED)
void onNothingSelected() {
}
其他
注意:
1.Activity ButterKnife.bind(this);必须在setContentView();之后,且父类bind绑定后,子类不需要再bind
2.Fragment ButterKnife.bind(this, mRootView);
3.属性布局不能用private or static 修饰,否则会报错
4.setContentView()不能通过注解实现。
5.ButterKnife已经更新到版本7.0.1了,以前的版本中叫做@InjectView了,而现在改用叫@Bind,更加贴合语义。
6.在Fragment生命周期中,onDestoryView也需要Butterknife.unbind(this)
7.ButterKnife不能再你的library module中使用哦!!这是因为你的library中的R字段的id值不是final类型的,但是你自己的应用module中确是final类型的。针对这个问题,有人在Jack的github上issue过这个问题,他本人也做了回答,点击这里。
【@Nullable 】加入该标签则绑定的View允许不存在为空
【@Optional 】加入该标签则绑定的监听允许不存在
@Nullable @BindView(R.id.might_not_be_there) TextView mightNotBeThere;
@Optional @OnClick(R.id.maybe_missing) void onMaybeMissingClicked() {
// TODO ...
}
EventBus
一、简介
EventBus是一款针对Android优化的发布/订阅事件总线。简化了应用程序内各组件间、组件与后台线程间的通信。优点是开销小,代码更优雅,以及将发送者和接收者解耦
EventBus有三个主要的元素需要我们先了解一下:
- Event:事件,可以是任意类型的对象。
- Subscriber:事件订阅者,在EventBus3.0之前消息处理的方法只能限定于onEvent、onEventMainThread、onEventBackgroundThread和onEventAsync,他们分别代表四种线程模型。而在EventBus3.0之后,事件处理的方法可以随便取名,但是需要添加一个注解@Subscribe,并且要指定线程模型(默认为POSTING),四种线程模型下面会讲到。
EventBus的四种ThreadMode(线程模型)
- POSTING(默认):如果使用事件处理函数指定了线程模型为POSTING,那么该事件在哪个线程发布出来的,事件处理函数就会在这个线程中运行,也就是说发布事件和接收事件在同一个线程。在线程模型为POSTING的事件处理函数中尽量避免执行耗时操作,因为它会阻塞事件的传递,甚至有可能会引起ANR
- MAIN: 事件的处理会在UI线程中执行。事件处理时间不能太长,长了会ANR的。
- BACKGROUND:如果事件是在UI线程中发布出来的,那么该事件处理函数就会在新的线程中运行,如果事件本来就是子线程中发布出来的,那么该事件处理函数直接在发布事件的线程中执行。在此事件处理函数中禁止进行UI更新操作。
- ASYNC:无论事件在哪个线程发布,该事件处理函数都会在新建的子线程中执行,同样,此事件处理函数中禁止进行UI更新操作。
github:https://github.com/greenrobot/EventBus/
依赖
compile 'org.greenrobot:eventbus:3.0.0'
二、基本使用
定义消息体载体即事件
public class MessageEvent {
public final String message;
public MessageEvent(String message) {
this.message = message;
}
}
注册/注销以及定义接收对象
public class MyActivity extends Activity
{
// This method will be called when a MessageEvent is posted
//默认POSTING
@Subscribe
public void onMessageEvent(MessageEvent event){
Toast.makeText(getActivity(), event.message, Toast.LENGTH_SHORT).show();
}
// This method will be called when a SomeOtherEvent is posted
@Subscribe(threadMode = ThreadMode.MAIN)
public void handleSomethingElse(SomeOtherEvent event){
doSomethingWith(event);
}
@Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
@Override
public void onStop() {
EventBus.getDefault().unregister(this);
super.onStop();
}
}
发送事件/消息
EventBus.getDefault().post(new MessageEvent("Hello everyone!"));
这边接收方的判断全靠参数,即只要有@Subscribe注解并且参数是MessageEvent都可以接收到这个消息。而传送消息过程中可以设置优先级和中断消息传送,如下
//优先级设置,priority越大,级别越高
@Subscribe(priority = 1);
public void onEvent(MessageEvent event) {
…
}
// 中止事件传递,后续事件不在调用,注意,只能在传递事件的时候调用
@Subscribe
public void onEvent(MessageEvent event){
…
EventBus.getDefault().cancelEventDelivery(event) ;
}
三、粘性事件StickyEvent
上面的事件发送流程是:接收方注册->发送方发送消息->接收方收到消息并处理
粘性事件就是当接收方还没注册的时候发送方发送消息保存到内存中,等接收方注册之后就可以获得这些消息。
与上面代码的区别就是
//发送粘性事件
EventBus.getDefault().postSticky(new MessageEvent("粘性事件"));
//接收方设置sticky=true
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
public void onEvent(MessageEvent event) {
// UI updates must run on MainThread
textField.setText(event.message);
}
另外,
sticky=true时,接收方已经注册也是可以接收post发出的事件。
sticky=false时,接收方已经注册也是可以接收postSticky发出的事件