通用库


在开发android应用时,一般会使用一些现有库来缩短开发周期,将代码进行模块化;
使用框架虽然可能会增加最终代码量,但在开发过程中会非常方便。

项目模版:GITHUB地址

android从出生到现在已经很多年了,因此有大量的库可供使用,android使用的技能不会特别多,但比较杂,虽然所有的代码都可以通过手✖完成,但这个过程肯定会相当痛苦。

很多库使用率都很高,通过github上的star数量可以分辨出哪些库比较主流,通过更新频率也可以查看哪些库依然在维护。现整理一些比较好的库,同时通过这些项目来搭建出一般android项目可以直接通用的框架代码。

一、框架需设计的库

虽然说一般框架不需要太多的内容,不过要实现大部分的功能,还是需要引用较多的资源库的,这里进行列举,然后接下来进行简单的说明:

  1. 基本通用包(系统自带或者support库提供):
    • multidex:dex分包处理
    • constraint
    • palette:着色
    • cardview:卡片布局
    • design
  2. 数据库
    • litepal
  3. 自动注入/变量赋值框架:
    • dagger:为成员变量等自动赋值
    • butterknife:依据xml自动生成变量并赋值,点击监听
  4. 线程任务处理
    • Rxjava(RxAndroid)
  5. 网络任务:
    • retrofit
    • okhttp:http核心库
  6. 数据类型转换:
    • Gson:json数据处理
  7. 图片处理:
    • Glide:图片下载裁剪
    • zxing-library:二维码图片识别或生成
    • takephoto:从设备中选择图片或拍照获得图片
  8. 工具类:
    • utilcode:这是工具库集合,提供各种常用的操作处理
    • statusbarutil:状态栏工具类
  9. 调试时检测优化:
    • leakcanary-android
  10. 日志工具
    • Logger:打印日志
  11. 弹出框:
    • pickerview:时间选择器、条件选择器
  12. 权限处理:
    • permissionsdispatcher:无反射动态请求权限
  13. 自定义view:
    • flowlayout:tag组
    • xrecyclerview:可上拉加载下拉刷新的recyclerview
    • convenientbanner:轮播插件
    • BottomNavigationViewEx:底部导航栏(多tab视图)
    • roundedimageview:圆角头像,可存在border
    • gridPasswordView:密码框
    • badge:角标库
  14. 界面跳转:
    • ARouter:阿里推出的库,可实现路由跳转、拦击、降级、参数自动注入
  15. 其他:
    • 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 objectBaseHttpBean) throws Exception {                    // TODO: 2018/3/10 成功                 }            }, new Consumer() {                @Override                public void accept(Throwable throwable) throws Exception {                    // TODO: 2018/3/10 失败                 }            });}   

网络请求的超时判断、数据 转换等操作,观察者方法执行时,就已经完成,如果需要添加其他的网络任务,只需要在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使用的情况比ActivityFragment要少的多,因此如果需要使用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()); }}

如代码所示,可以分为四个步骤:

  1. 获取当前应用拥有的所有权限
  2. 获取目标页面所需的权限
  3. 进行判断,若是目标页面所需权限当前已拥有,则跳转成功;否则进行步骤4
  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 等操作后就可以添加业务处理了。

更多相关文章

  1. 浅谈android @id和@+id的区别
  2. Android(安卓)动态权限最全解析
  3. Android系统使用socket在Java层和native之间数据通信
  4. Android(安卓)通过URL scheme 实现点击浏览器中的URL链接,启动特
  5. android Https请求的使用
  6. Android权限申请的学习实践
  7. Android获取WIFI的BSSID遇到的坑(已解决)
  8. Android中@+id和@id的区别
  9. Android(安卓)快速接入广告( 广点通、穿山甲、百青藤 )

随机推荐

  1. android 四大组件之Service
  2. android中的xml处理
  3. Android 常用dialog提示对话框
  4. Android(安卓)8.1 开机流程分析(1)
  5. Android入门(9)AudioRecord和AudioTrack类
  6. Android开发中出现Attempt to invoke vir
  7. Android 入门知识点梳理之一 四大组件
  8. Android(安卓)之Content Provider(URI)存储
  9. 初学Android,开机自启动的Service(七十三
  10. Android Jetpack组件学习 ViewModel & Li