android使用主流库搭建应用框架
通用库
在开发android应用时,一般会使用一些现有库来缩短开发周期,将代码进行模块化;
使用框架虽然可能会增加最终代码量,但在开发过程中会非常方便。
项目模版:GITHUB地址
android从出生到现在已经很多年了,因此有大量的库可供使用,android使用的技能不会特别多,但比较杂,虽然所有的代码都可以通过手✖完成,但这个过程肯定会相当痛苦。
很多库使用率都很高,通过github上的star数量可以分辨出哪些库比较主流,通过更新频率也可以查看哪些库依然在维护。现整理一些比较好的库,同时通过这些项目来搭建出一般android项目可以直接通用的框架代码。
一、框架需设计的库
虽然说一般框架不需要太多的内容,不过要实现大部分的功能,还是需要引用较多的资源库的,这里进行列举,然后接下来进行简单的说明:
- 基本通用包(系统自带或者support库提供):
- multidex:dex分包处理
- constraint
- palette:着色
- cardview:卡片布局
- design
- 数据库:
- litepal
- 自动注入/变量赋值框架:
- dagger:为成员变量等自动赋值
- butterknife:依据xml自动生成变量并赋值,点击监听
- 线程任务处理:
- Rxjava(RxAndroid)
- 网络任务:
- retrofit
- okhttp:http核心库
- 数据类型转换:
- Gson:json数据处理
- 图片处理:
- Glide:图片下载裁剪
- zxing-library:二维码图片识别或生成
- takephoto:从设备中选择图片或拍照获得图片
- 工具类:
- utilcode:这是工具库集合,提供各种常用的操作处理
- statusbarutil:状态栏工具类
- 调试时检测优化:
- leakcanary-android
- 日志工具
- Logger:打印日志
- 弹出框:
- pickerview:时间选择器、条件选择器
- 权限处理:
- permissionsdispatcher:无反射动态请求权限
- 自定义view:
- flowlayout:tag组
- xrecyclerview:可上拉加载下拉刷新的recyclerview
- convenientbanner:轮播插件
- BottomNavigationViewEx:底部导航栏(多tab视图)
- roundedimageview:圆角头像,可存在border
- gridPasswordView:密码框
- badge:角标库
- 界面跳转:
- ARouter:阿里推出的库,可实现路由跳转、拦击、降级、参数自动注入
- 其他:
- pushsdk:友盟统计、消息推送(module形式)
- rxdownload:基于Rxjava的用于软件更新的库
- logging-interceptor:网络请求时打印信息
- adapter-rxjava:将网络请求变为Rxjava监听形式
- converter-gson:网络返回数据(若为gson格式)转为bean
大部分的库都已经列举出来了;
其中一些库如自定义View等内容可以参考github源码,也可以在项目中找到对应使用的位置
对于友盟推送等内容,也已经在代码中集成;同时在BaseApplication类中进行了初始化
动态权限库自身在AndroidStudio中是有插件的,并且源码中启动Activity就是示例;
LocationPickerView也提供了自定义View的模版。
其中有些库功能很强大,但使用起来会进行其他配置,因此单独说明:
二、网络请求模块
该模块由Rxjava、RxAndroid、logging-interceptor、adapter-rxjava、converter-gson、retrofit、okhttp等项目包组成,将网络请求、数据转换、异常处理、日志输出等功能内聚到一起,整个架构可用简图概括(只是草图):
这样用户只需要将网络请求的字段传入,并定义可以处理的结构以及异常情况下的处理,就可以省略中间的具体的过程。
具体实现如下:
@Provides@SingletonHttpInterface provideHttpInterface() { //网络请求的Host String baseUrl = BaseApplication.app.getBaseNetUrl(); //生成JSON转换的库 Gson gson = new GsonBuilder() .serializeNulls() .setDateFormat("yyyy:MM:dd HH:mm:ss") .create(); GsonConverterFactory gsonConverterFactory = GsonConverterFactory.create(gson); //生成RxJava转换的adapter RxJava2CallAdapterFactory rxJava2CallAdapterFactory = RxJava2CallAdapterFactory.create(); //生成OkHttp网络传输的客户端 HashMap> cookieStore = new HashMap<>(); OkHttpClient okHttpClient = new OkHttpClient.Builder() .cookieJar(new CookieJar() { @Override public void saveFromResponse(HttpUrl url, List cookies) { cookieStore.put(url.host(), cookies); } @Override public List loadForRequest(HttpUrl url) { List cookies = cookieStore.get(url.host()); return cookies != null ? cookies : new ArrayList<>(); } }) .addInterceptor(chain -> { Request request = chain.request() .newBuilder() .addHeader("SDK", String.valueOf(Build.VERSION.SDK_INT )) .build(); return chain.proceed(request); }) .addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)) .addNetworkInterceptor(new StethoInterceptor()) .connectTimeout(2000, TimeUnit.MILLISECONDS) .readTimeout(2000, TimeUnit.MILLISECONDS) .writeTimeout(2000,TimeUnit.MILLISECONDS) .build(); //最后组合成Retrofit对象 Retrofit retrofit = new Retrofit.Builder() .addConverterFactory(gsonConverterFactory) .addCallAdapterFactory(rxJava2CallAdapterFactory) .baseUrl(baseUrl) .client(okHttpClient) .build(); //将注解后的interface请求接口转换为真正可用的网络请求对象 return retrofit.create(HttpInterface.class);}
可以看到,最后其实是生成了HttpInterface对象。这个HttpInterface对象是自定义的,自身是接口类型,用于定义网络请求所需的参数,以及可以处理的返回数据的类型,如假如需要请求一次网络:
public interface HttpInterface { /** * 登录 * * @param username 用户名 * @param pwd 密码 * @return 登录返回对象 */ @POST("login/doLogin") @FormUrlEncoded Observable> doLogin(@Field("username") String username, @Field("pwd") String pwd); }
如上一般,只需要定义参数,以及可解析的数据对象即可。
然后发起网络请求:
/** * 举例请求网络数据 */@OnClick(R.id.fab)public void onViewClicked() { httpInterface.doLogin("name", "password") .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Consumer>() { @Override public void accept(BaseHttpBean
网络请求的超时判断、数据 转换等操作,观察者方法执行时,就已经完成,如果需要添加其他的网络任务,只需要在HttpInterface接口中添加对应的方法即可。
三、自动注入模块
通过dagger可以把已经注解的成员变量进行赋值,只要提供对应类型的提供方即可;
利用dagger框架也可以很方便的实现MVP模式,同时可以省略View层与Presenter层的赋值操作,只要有需要对应变量的地方,直接通过@Inject注解即可。
Butterknife则专注于 对xml布局文件中,变量的生成,以及控件的点击监听,同时ButterKnife还在Androidstudio上实现了plugin,使用起来比较方便。直接百度即可,这里不额外说明;
以刚才的网络请求模块,可能需要在多处调用HttpInterface的实例,这时就可以 通过dagger来完成这一功能:
1、为dagger定义变量作用范围
一般按照模版,定义两个注解即可:
定义Activity作用范围:
/** * 功能----定义每个activity的生命周期,供dagger框架使用 * * Created by MNLIN on 2017/9/22. */
@Scope@Documented@Retention(RetentionPolicy.RUNTIME)public @interface PerActivity {}
定义Fragment作用范围:
/** * 功能----fragment对应dagger的生命周期控制 * * Created by MNLIN on 2017/9/23. */
@Scope@Documented@Retention(RetentionPolicy.RUNTIME)public @interface PerFragment {}
2、定义作用范围内需要的变量,或全局变量
Activity和Fragment作用范围内的变量是局部的;
Application作用范围是全局的(Activity和Fragment需设置依赖Application的compont);
这点是通过compont组件来保证的;
这里只写出全局模式变量的注入方法:
/** * 功能----应用的组件 * * Created by MNLIN on 2017/9/22. */
@Singleton@Component(modules = ApplicationModule.class)public interface ApplicationComponent { void inject(BaseApplication application); HttpInterface initHttpInterface();}
可以看到,在ApplicationComponent中声明了HttpInterface,这样所有的位置只要通过@Inject进行注解,就可以获取对应实例。
而该实例的创建则是通过module提供的,这点对于Activity,Fragment,Application相同:
/** * 功能----Application的module,为ApplicationComponent提供对象生成器 * Created by MNLIN on 2017/9/22 */@Singleton@Modulepublic class ApplicationModule { String tag = ""; private BaseApplication application; public ApplicationModule(BaseApplication application) { this.application = application; } @Provides @Singleton HttpInterface provideHttpInterface() { //网络请求的Host String baseUrl = BaseApplication.app.getBaseNetUrl(); //生成JSON转换的库 Gson gson = new GsonBuilder() .serializeNulls() .setDateFormat("yyyy:MM:dd HH:mm:ss") .create(); GsonConverterFactory gsonConverterFactory = GsonConverterFactory.create(gson); //生成RxJava转换的adapter RxJava2CallAdapterFactory rxJava2CallAdapterFactory = RxJava2CallAdapterFactory.create(); //生成OkHttp网络传输的客户端 HashMap> cookieStore = new HashMap<>(); OkHttpClient okHttpClient = new OkHttpClient.Builder() .cookieJar(new CookieJar() { @Override public void saveFromResponse(HttpUrl url, List cookies) { cookieStore.put(url.host(), cookies); } @Override public List loadForRequest(HttpUrl url) { List cookies = cookieStore.get(url.host()); return cookies != null ? cookies : new ArrayList<>(); } }) .addInterceptor(chain -> { Request request = chain.request() .newBuilder() .addHeader("SDK", String.valueOf(Build.VERSION.SDK_INT )) .build(); return chain.proceed(request); }) .addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)) .addNetworkInterceptor(new StethoInterceptor()) .connectTimeout(2000, TimeUnit.MILLISECONDS) .readTimeout(2000, TimeUnit.MILLISECONDS) .writeTimeout(2000,TimeUnit.MILLISECONDS) .build(); //最后组合成Retrofit对象 Retrofit retrofit = new Retrofit.Builder() .addConverterFactory(gsonConverterFactory) .addCallAdapterFactory(rxJava2CallAdapterFactory) .baseUrl(baseUrl) .client(okHttpClient) .build(); //将注解后的interface请求接口转换为真正可用的网络请求对象 return retrofit.create(HttpInterface.class); }}
通过这样一个流程,就可以完成前期设定工作,当然还需要最后一步:
3、在基类中初始化
之前只是进行了准备,但是真正注入还是需要在代码执行时候来启动,因此在BaseActivity、BaseFragment、BaseApplication中需要添加初始化代码:
这里只显示Application部分:BaseApplication类:
//注入dagger框架applicationComponent = DaggerApplicationComponent.builder().applicationModule(new ApplicationModule(this)).build();applicationComponent.inject(this);
具体详细设置可以参考源码;
四、跳转拦截模块——ARouter
这个模块虽然基本功能看上去和平时使用startActivity的方式差不了多少,但是真正带入后会发现很多精巧的地方,表面上看起来是提供路由跳转的其他一种方式,只是高端一些,不过真正使用起来会发现出奇的好。
1、定义字符串,供路由跳转
一般来说service使用的情况比Activity和Fragment要少的多,因此如果需要使用service的话,可以前往ARouter的GITHUB进行查看。
ARouter框架提供了:路由跳转,碎片生成,降级,自定义序列化等服务;不过用的虽然不多,但比较精巧的则是权限判断;
引用ARouter自己的说明:
6.为目标页面声明更多信息// 我们经常需要在目标页面中配置一些属性,比方说"是否需要登陆"之类的// 可以通过 Route 注解中的 extras 属性进行扩展,这个属性是一个 int值,换句话说,单个int有4字节,也就是32位,可以配置32个开关// 剩下的可以自行发挥,通过字节操作可以标识32个开关,通过开关标记目标页面的一些属性,在拦截器中可以拿到这个标记进行业务逻辑判断@Route(path = "/test/activity", extras = Consts.XXXX)
具体功能可以查看项目代码:
以下给出引导部分:
/** * 功能----路径跳转activity/fragment * * * Created by MNLIN on 2017/11/24. */
public final class ARouterConst { /** * 无权限 * 登录 * 绿色通道(若设定则无法跳转,相当于禁止功能) * activity启动:清除任务栈 */ public static final int FLAG_NONE = 0x00000000; public static final int FLAG_LOGIN = 0x00000003; public static final int FLAG_FORCE_ACCESS = 0x00000040; public static final int FLAG_ACTIVITY_CLEAR_TOP = 0x00000200; /** * activity/fragment */ public static final String Activity_SelectFunctionActivity = "/activity/SelectFunctionActivity"; public static final String Activity_SearchFilterActivity = "/activity/SearchFilterActivity"; public static final String Fragment_WalletFragment = "/fragment/WalletFragment";}
这里下方以Activity_或者Fragment_开头的常量提供ARouter路由进行最基本的功能跳转,而上方则定义了项目中可能会用到的“权限”;
假设现在有一场景,活动A向活动B进行跳转,此时要求用户已经登录,否则的话就需要中断跳向B的操作,直接跳转到活动C;
一般情况下可能我们会在B中进行判断,若是未登录的话,则主动将页面路由到C,可是这样的话,实际并没有阻止跳转操作,若是C中一开始就进行危险操作,则可能会直接崩溃。
或者说可以在A中进行判断,但如此一来,若是向B跳转的活动过多,就需要在多个活动中编写同样的代码,当然这可以编写工具类,但若是页面过多,这种逻辑会很麻烦.
而ARouter则相当于在跳转过程中进行拦截,所有不符合条件的跳转都将另行处理:
/** * function : 跳转拦截器(权限拦截) * * 比较经典的应用就是在跳转过程中处理登陆事件,这样就不需要在目标页重复做登陆检查 * 拦截器会在跳转之间执行,多个拦截器会按优先级顺序依次执行 * * @author MNLIN */
@Interceptor(priority = 2, name = "ARouter跳转拦截器")public class ARouterInterceptor implements IInterceptor { @Override public void process(Postcard postcard, InterceptorCallback callback) { Logger.v("######界面跳转 : " + postcard.toString()); //当前所有的权限 String[] permissions = new String[]{ "登录", "绿色通道",//若目标此flag设定,则表示禁止跳转 "栈单例模式"//若目标设置此flag,则添加singTask标志 }; int[] FLAGS_ALL = new int[]{ ARouterConst.FLAG_LOGIN, ARouterConst.FLAG_FORCE_ACCESS, ARouterConst.FLAG_ACTIVITY_CLEAR_TOP }; //当前所有权限对应的boolean值;为false则对应权限设为 ARouterConst.FLAG_NONE boolean[] FLAGS_ALL_VALUE = new boolean[]{ DefaultPreferenceUtil.getInstance().hasLogin(), false, false }; //当前所有的权限 int currentFlags = Integer.MIN_VALUE; for (int position = 0; position < FLAGS_ALL.length; position++) { currentFlags |= FLAGS_ALL_VALUE[position] ? FLAGS_ALL[position] : ARouterConst.FLAG_NONE; } Logger.v("######当前所有权限 : " + Integer.toBinaryString(currentFlags)); //目标界面需要的权限 int requireFlags = postcard.getExtra() | Integer.MIN_VALUE; Logger.v("######目标所需权限 : " + Integer.toBinaryString(requireFlags)); //如果需要的权限都已存在,则直接跳转,不做处理 if ((requireFlags & currentFlags) == requireFlags) { callback.onContinue(postcard); return; } //如果发现不一致,说明某些权限不存在,则需要依次判断哪个权限不存在 for (int position = 0; position < FLAGS_ALL.length; position++) { if ((requireFlags & FLAGS_ALL[position]) != 0 && (currentFlags & FLAGS_ALL[position]) == 0) { // TODO: 2018/1/20 没有对应的f权限 boolean consume = false; switch (position) { case 0: //未登录 consume = dispatchLogin(postcard, callback); break; case 1: break; case 2: //栈单例模式 consume = dispatchSingleTask(postcard, callback); break; default: { callback.onInterrupt(new RuntimeException("没有 " + permissions[position] + " 权限")); } } if (!consume) { callback.onInterrupt(new RuntimeException("界面无法跳转")); } return; } } //权限定义错误 RxBus.getInstance().post(new BaseEvent(Const.SHOW_TOAST, "未知权限")); } /** * 请求单例启动 * * 清除栈上其他活动 */ private boolean dispatchSingleTask(Postcard postcard, InterceptorCallback callback) { postcard.withFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); callback.onContinue(postcard); return true; } /** * 处理未登录操作 */ private boolean dispatchLogin(Postcard postcard, InterceptorCallback callback) { RxBus.getInstance().post(new BaseEvent(Const.SHOW_LOGIN_DIALOG, null)); return false; } @Override public void init(Context context) { // 拦截器的初始化,会在sdk初始化的时候调用该方法,仅会调用一次 } /** * 更换意图的跳转路径 * 然后进行跳转处理 * * @param postcard 意图 * @param des 目的 string */ private void replaceDes(Postcard postcard, String des) { //动态的修改postcard信息,更换跳转路径 Postcard newPostcard = ARouter.getInstance().build(des); LogisticsCenter.completion(newPostcard); postcard.setPath(newPostcard.getPath()).setGroup(newPostcard.getGroup()).setDestination(newPostcard.getDestination()); }}
如代码所示,可以分为四个步骤:
- 获取当前应用拥有的所有权限
- 获取目标页面所需的权限
- 进行判断,若是目标页面所需权限当前已拥有,则跳转成功;否则进行步骤4
- 按顺序判断是什么权限未满足,通过 dispatch*** 方法进行处理
同时 项目中放置了很多方便操作的工具类以及代码生成器;
如在 build.gradle 中添加了生成MVP模式类的代码:
/** * Desc: generate mvp architecture code automatically * * @Des For example: gradle mvp -D domain="Test" -D path="/activity" * def domainParam = System.getProperty('domain') * def pathParam = System.getProperty('path') * * */task mvp_activity(type: GenerateMVPActivity) { group 'personal' description 'generate java code for MVP-Activity architecture' def domainParam = "Test" def pathParam = "/activity" if (domainParam && pathParam) { domain domainParam uiPath pathParam }}
使用时将 domainParam 重新赋值,然后通过任务就可以生成相应的代码。
任务执行可以通过AndroidStudio右侧的任务列表进行:
也可以在该任务上: 右键 => Assign ShortCut => 添加快捷键
以后每次生成代码,只需要修改domainParam 字段值,然后使用快捷键生成代码即可,不过要注意: 不能使用相同名字的Activity及Fragment,会相互覆盖;
功能一般而言足够普通项目使用,只需要在使用时进行:修改包名,设置友盟KEY 等操作后就可以添加业务处理了。
更多相关文章
- 浅谈android @id和@+id的区别
- Android(安卓)动态权限最全解析
- Android系统使用socket在Java层和native之间数据通信
- Android(安卓)通过URL scheme 实现点击浏览器中的URL链接,启动特
- android Https请求的使用
- Android权限申请的学习实践
- Android获取WIFI的BSSID遇到的坑(已解决)
- Android中@+id和@id的区别
- Android(安卓)快速接入广告( 广点通、穿山甲、百青藤 )