拓展-框架选型

框架选型

链接:

UI集:

UI库整理(泡在网上的日子)

XUI库介绍 XUI说明文档

QMUI_Android

UI介绍:

https://zhuanlan.zhihu.com/p/68671278

https://zhuanlan.zhihu.com/p/49726145

https://zhuanlan.zhihu.com/p/25324711

网络框架

优点 缺点
HttpClient 1. 支持连接池、多线程
2. 从官方demo可以看出httpclient只创建一次,被多个线程复用
3. HttpClient4.3后超时配置到request级
1. Android已经去掉了HttpClient改用了OKHttp。
2. 使用起来需要自己封装
3. 需要手动关闭httpclient.close()
OKHttp 1. 性能方面与httpclient类似
2. 不需要手动关闭
3. Android4.4开始换成OKHttp
1. 使用时需要自己封装
2. new OkHttpClient()每次使用都需要new出来
3. 超时配置在client级,没到每个request
Retrofit 1. 基于OKHttp的封装
2. 基于接口编程。封装度高,基于注解。无需手动关闭
3. Jetpack使用的Retrofit
1. 与OKHttp类似,new Retrofit.Builder()每次使用都要new 出来

路由框架

ARouter ActivityRouter VMRouter SPI
相关文章 开源最佳实践:Android平台页面路由框架ARouter
如何一步步实现一个类似ARouter的Android路由框架
Android Router从0到1
地址 https://github.com/alibaba/Arouter https://github.com/mzule/ActivityRouter https://github.com/meituan/WMRouter
共性 1. 支持降级处理。

2. 支持Activity的startActivityForResult

3. 组件单独运行的方式:切换library/application方式编译,框架本身没有提供切换方式,开发者自行解决

4. 没有组件调用的超时设置

5. 无法取消组件调用

6. 无法动态注册/注销组件

7. 组件调用代码侵入性都很高
1. 支持降级处理。

2. 支持Activity的startActivityForResult

3. 组件单独运行的方式:切换library/application方式编译,框架本身没有提供切换方式,开发者自行解决

4. 没有组件调用的超时设置

5. 无法取消组件调用

6. 无法动态注册/注销组件

7. 组件调用代码侵入性都很高
通信机制 路由+接口下沉 路由+静态方法
activity变量自动注入 1. 通过apt生成解析参数的代码
2. 在onCreate方法中调用ARouter.getInstance().inject(this);实现自动注入
调用方式(页面跳转) ARouter.getInstance().build("/test/activity").navigation(); Router.create(url).open(context);
调用方式(调用服务) ARouter.getInstance().navigation(HelloService.class).sayHello(); 与页面跳转相同
组件向外提供服务 接口继承IProvider并下沉到base中,组件中实现接口并通过注解来暴露服务 在静态方法上加注解来暴露服务,但不支持返回值,且参数固定位(context, bundle)
Fragment组件化支持 调用服务的方式实现,未支持后续Fragment内部的功能调用 不支持
组件自动注册方案 新版本(1.3.0)开始支持通过插件完成路由注册
1. apt生成各module的路由表
2. TransformAPI+ASM扫描路由表并注册到LogisticsCenter中,无需手动维护组件列表
1. apt生成各module的路由表
2. apt在application的module通过Modules注解生成RouterInit进行注册
3. 需要手动维护Modules注解中的组件列表
跨app组件调用支持 不支持 支持
组件app运行时调用其他组件 一起打包或者通过urlScheme来统一转发 UrlScheme原生支持跨app调用,组件同时安装在设备上接口
通过中介Activity转发:RouterActivity
组件依赖隔离 未隔离 无需依赖、完全隔离
AOP支持 拦截器AOP
特点 1. 阿里出品,使用者众多,QQ群里交流比较活跃
2. 支持分级按需加载
3. 是一个路由框架,并不是完整的组件化方案,可作为组件化架构通信引擎
1. 业内最早的组件化支持库
2. 通过注解静态方法的方式暴露服务
组件定义代码侵入性 注解定义路由及参数自动注入,侵入性高 注解定义路由,侵入性高
混淆配置 框架中的所有类及框架相关接口的实现类 框架中的所有类
Easy of use
starts 13.1k 2.8k 2k
大厂使用 WMRouter:美团外卖Android开源路由框架

图片加载

参考链接

Picasso Glide Fresco
说明
地址 https://github.com/square/picasso https://github.com/bumptech/glide https://github.com/facebook/fresco
发布时间 2013年5月 2014年9月 2015年5月
是否支持gif false true true
是否支持webP true true true
视频缩略图 false true true
加载速度
大小 100kb 500kb 2~3M
占内存大小
Easy of use low medium difficult
Disk+MenCache true true true
大厂使用

详细属性对比

对比项 Picasso Glide Fresco
配置 compile ‘com.github.bumptech.glide:glide:XXX.XXX’ compile ‘com.facebook.fresco:fresco:XXX.XXX
初始化 直接使用 Fresco.initialize(this);
layout 普通ImageView 独有的SimpleDraweeView
圆角, 圆形 需要自己实现圆角,继承自BitmapTransformation操作bitmap对象实现 通过RoundingParams设置参数
缓存 Glide内存和磁盘缓存 三级缓存,分别是 Bitmap缓存,未解码图片缓存, 文件缓存。
缓存图像大小 Glide则会根据ImageView控件尺寸获得对应的大小的bitmap来展示,从而缓存也可以针对不同的对象:原始图像(source),结果图像(result) 缓存原始图像
加载策略 Glide只有占位图 先加载小尺寸图片,再加载大尺寸的
加载进度 false true

Glide和Fresco比较

依赖

Glidecompile com.github.bumptech.glide:glide:3.7.0

Fresco

1
2
3
4
5
6
7
8
9
compile 'com.facebook.fresco:fresco:1.2.0'
//在 API < 14 上的及其支持 WebP 时,需要添加
compile 'com.facebook.fresco:animated-base-suport:1.2.0'
//支持 GIF 动图,需要添加
compile 'com.facebook.fresco:animated-gif:1.2.0'
//支持 WebP (静态+动图),需要添加
compile 'com.facebook.fresco:animated-webp:1.2.0'
compile 'com.facebook.fresco:websupport:1.2.0'
compile 'com.facebook.fresco:imagepipeline-okhttp3:1.2.0'

bitmap操作

Glide

1
2
3
4
Bitmap myBitmap = Glide.with(上下文)
.load(url)
.asBitmap() //必须
.get()

Fresco

Fresco获取bitmap更加复杂,使用起来也不是那么顺畅。

首先,Fresco为了更好地管理bitmap 对象(bitmap对象申请和释放会引起频繁的GC操作,从而引起界面卡顿), 引入了可关闭的引用(CloseableReference), 持有者在离开作用域的时候需要关闭该引用,而我们要获取的bitmap 对象就是可关闭的引用。也就是说,我们不能像上面Glide那样把bitmap 对象取出来传递给其它地方使用, 只能在Fresco提供的作用域范围内使用。 实际项目中会获取缓冲的文件对象:

1
2
3
4
5
//同样在DataSubscriber中获取
FileBinaryResource resource = (FileBinaryResource) Fresco.getImagePipelineFactory().getMainFileCache().getResource(new SimpleCacheKey(url));
if (resource != null && resource.getFile() != null) {
setImage(ImageSource.uri(Uri.fromFile(resource.getFile())));
}

优点

Glide

  • 多种图片格式的缓存,适用于更多内容表现形式(如 Gif、WebP、缩略图、Video)
  • 生命周期集成(根据Activity或Fragment的生命周期管理图片加载请求)
  • 高校处理Bitmap(bitmap的复用和主动回收,减少系统回收压力)
  • 高校的缓存策略,灵活(Picasso只会缓存原始尺寸的图片,Glide缓存的是多种规格),加载速度快且内存开销小(默认Bitmap格式的不同,使得内存开销是Picasso的一半)

Fresco

最大的优势在于5.0以下(最低2.3)的bitmap加载。在5.0以下系统,Fresco将图片放到一个特别的内存区域(Ashmem区)

大大减少OOM(在更底层的Native蹭对OOM进行处理,图片将不再占用App的内存)

适用于需要高性能加载大量图片的场景

缺点

Glide

  • 没有文件缓存
  • java heap比Fresco高

Fresco

  • 包较大(2~3M)
  • 用法复杂
  • 底层涉及c++领域,阅读源码深入学习难度大

结论

picasso不支持gif且上面表格体现的数据也不理想,我们忽略它。

专业的图片App用Fresco比较好。普通App用Glide上手快,使用简单,配置方便。

图片裁剪

说明
地址
加载速度
大小
占内存大小
Easy of use
是否会自动矫正角度
大厂使用

图片压缩

Luban Compress
说明 可控制压缩档次。
仿微信朋友圈压缩策略
满足几MB图的高保真压缩到几十KB
地址 https://github.com/Curzibn/Luban https://github.com/zetbaitsu/Compressor
Easy of use 支持普通调用方式和RxJava调用 支持普通调用方式和RxJava调用
最后更新时间0 3年前 2021.3
stars 12.5k 5.8k
群友推荐使用

开源的。商业使用,记得遵循其对应的开源协议。

Luban的效果与对比

内容 原图 Luban Wechat
截屏 720P 720*1280,390k 720*1280,87k 720*1280,56k
截屏 1080P 1080*1920,2.21M 1080*1920,104k 1080*1920,112k
拍照 13M(4:3) 3096*4128,3.12M 1548*2064,141k 1548*2064,147k
拍照 9.6M(16:9) 4128*2322,4.64M 1032*581,97k 1032*581,74k
滚动截屏 1080*6433,1.56M 1080*6433,351k 1080*6433,482k

Luban

导入Luban

1
implementation 'top.zibin:Luban:1.1.8'

使用Luban

方法列表

方法 描述
load 传入原图
filter 设置开启压缩条件
ignoreBy 不压缩的阈值,单位为K
setFocusAlpha 设置是否保留透明通道
setTargetDir 缓存压缩图片路径
setCompressListener 压缩回调接口
setRenameListener 压缩前重命名接口
异步调用

Luban内部采用IO线程进行图片压缩,外部调用只需设置好结果监听即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//普通调用方式
Luban.get(this) //传入要压缩的图片
.load(file) //设定压缩档次,默认三挡
.putGear(Luban.THIRE_GREA)
.ignoreBy(100)
.setTargetDir(getPath())
.filter(new CompressionPredicate() {
@Override
public boolean apply(String path) {
return !(TextUtils.isEmpty(path) || path.toLowerCase().endsWith(".gif"));
}
})
.setCompressListener(new OnCompressListener(){ //设置回调
@Override
public void onStart(){
//TODO 压缩开始前调用,可以在方法内启动Loading UI
}
@Override
public void onSuccess(File file){
//TODO 压缩成功后调用,返回压缩后的图片文件
}
@Override
public void onError(Throwable e){
//TODO 当压缩过程出现问题时调用
}
}).launch();//启动压缩
同步调用

同步方法请尽量避免在主线程调用以免阻塞主线程,下面以rxJava调用为例

1
2
3
4
5
6
7
8
9
10
Flowable.just(photos)
.observeOn(Schedulers.io())
.map(new Function<List<String>, List<File>>() {
@Override public List<File> apply(@NonNull List<String> list) throws Exception {
// 同步方法直接返回压缩后的文件
return Luban.with(MainActivity.this).load(list).get();
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//RxJava方式
//RxJava调用方式请自行随意控制线程
Luban.get(this)
.load(file)
.putGear(Luban.THIRD_GREA)
.asObservable()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnError(new Action1<Throwable>(){
@Override
public void call(Throwable throwable){
throwable.printStackTrace();
}
})
.onErrorResumeNext(new Func1<Throwable, Observable<? extends File>>(){
@Override
public Observable<? extends File> call(Throwable throwable){
return Observable.empty();
}
})
.subscribe(new Action1<File>(){
@Override
public void call(File file){
//TODO 压缩成功后调用,返回压缩后的图片文件
}
}).launch();// 启动压缩

Compress

导入Compress

1
2
3
dependencies {
implementation 'id.zelory:compressor:3.0.1'
}

使用Compress

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Compress Image File
compressdImageFile = Compressor.getDefault(this).compressToFile(actualImageFile);

//Compress Image File to Bitmap
compressedImageBitmap = Compressor.getDefault(this).compressToBitmap(actualImageFile);

//I want to custom Compressor
compressedImage = new Compressor.Builder(this)
.setMaxWidth(640)
.setMaxHeight(480)
.setQuality(75)
.setCompressFormat(Bitmap.CompressFormat.WEBP)
.setDestinationDirectoryPath(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).getAbsolutePath())
.build()
.compressToFile(actualImage);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Stay cool compress image asynchronously with RxJava
Compressor.getDefault(this)
.compressToFileAsObservable(actualImage)
.subscribeOn(Schedulers.io())
.observeOn(AndoridSchedulers.mainThread())
.subscribe(new Action1<File>(){
@Override
public void call(File file){
compresseedImage = file;
}
}, new Action1<Throwable>(){
@Override
public void call(Throwable throwable){
showError(throwable.getMessage());
}
});

文件上传

说明
地址
加载速度
大小
占内存大小
Easy of use
大厂使用

文件下载

说明
地址
加载速度
大小
占内存大小
Easy of use
大厂使用

视频框架

说明
地址
加载速度
大小
占内存大小
Easy of use
大厂使用

IM

说明
地址
加载速度
大小
占内存大小
Easy of use
大厂使用

弹窗Dialog

说明
地址
加载速度
大小
占内存大小
Easy of use
大厂使用

弹窗PopupWindow

说明
地址
加载速度
大小
占内存大小
Easy of use
大厂使用

上拉加载下拉刷新

说明
地址
加载速度
大小
占内存大小
Easy of use
大厂使用

列表适配器

说明
地址
加载速度
大小
占内存大小
Easy of use
大厂使用

日期选择器

说明
地址
加载速度
大小
占内存大小
Easy of use
大厂使用

滚轮选择器WheelView

说明
地址
加载速度
大小
占内存大小
Easy of use
大厂使用

数据存储

数据存储框架对比

SQLite SharedPreference GreenDao Room Litepal MMKV
说明 轻量级关系型 腾讯开发。键值对存储(对标SP)
地址 https://github.com/greenrobot/greenDAO https://github.com/guolindev/LitePal https://github.com/Tencent/MMKV
支持的数据类型 boolean、int、long、float、double、string、set集合、byte[]、可序列化对象
易用性

GreenDao使用

Room使用

Litepal使用

MMKV使用

  1. 引入依赖

    1
    2
    3
    dependencies {
    implementation 'com.tencent:mmkv-static:1.2.7'
    }
  2. 在自定义 Application 中初始化

    在Application中初始化MMKV的时候,可以采用默认存储路径的方式初始化,也可以采用自定义文件存储路径的方式初始化

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //在App启动时进行MMKV初始化。返回默认储存路径("/data/user/0/项目包名/files/mmkv")
    String rootDir = MMKV.initialize(this);

    //或者初始化MMKV的时候自定义根目录
    //String dir = getFilesDir().getAbsolutePath() + "/mmkv_2";
    //String rootDir = MMKV.initialize(dir);

    //或者初始化MMKV时自定义某个文件的目录
    //String relativePath = getFilesDir().getAbsolutePath() + "/mmkv_3";
    //MMKV kv = MMKV.mmkvWithID("testCustomDir", relativePath);
  3. MMKV提供一个全局的实例,可以直接使用

    1
    2
    3
    4
    5
    6
    7
    8
    import com.tencent.mmkv.MMKV;
    MMKV kv = MMKV.defaultMMKV();
    kv.encode("bool", true);//存储
    boolean bValue = kv.decodeBool("bool");//取出
    kv.encode("int", Integer.MIN_VALUE);
    int iValue = kv.decodeInt("int");
    kv.encode("string", "Hello from mmkv");
    String str = kv.decodeString("string");

扫码

  1. zxing
  2. 华为统一扫码服务:https://juejin.cn/post/6967890062423883783