android使用主流库搭建应用框架

通用库


在开发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
@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<String, List<Cookie>> cookieStore = new HashMap<>();
    OkHttpClient okHttpClient = new OkHttpClient.Builder()
            .cookieJar(new CookieJar() {
                @Override
                public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
                    cookieStore.put(url.host(), cookies);
                }

                @Override
                public List<Cookie> loadForRequest(HttpUrl url) {
                    List<Cookie> 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<BaseHttpBean<Object>> 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<BaseHttpBean<Object>>() {
                @Override
                public void accept(BaseHttpBean<Object> objectBaseHttpBean) throws Exception {
                    // TODO: 2018/3/10 成功 
                }
            }, new Consumer<Throwable>() {
                @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框架使用
 * <p>
 * Created by MNLIN on 2017/9/22.
 */
@Scope
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface PerActivity {
}

定义Fragment作用范围:

/**
 * 功能----fragment对应dagger的生命周期控制
 * <p>
 * Created by MNLIN on 2017/9/23.
 */
@Scope
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface PerFragment {

}

2、定义作用范围内需要的变量,或全局变量

Activity和Fragment作用范围内的变量是局部的;
Application作用范围是全局的(Activity和Fragment需设置依赖Application的compont);
这点是通过compont组件来保证的;

这里只写出全局模式变量的注入方法:

/**
 * 功能----应用的组件
 * <p>
 * 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
@Module
public 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<String, List<Cookie>> cookieStore = new HashMap<>();
        OkHttpClient okHttpClient = new OkHttpClient.Builder()
                .cookieJar(new CookieJar() {
                    @Override
                    public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
                        cookieStore.put(url.host(), cookies);
                    }

                    @Override
                    public List<Cookie> loadForRequest(HttpUrl url) {
                        List<Cookie> 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
 * 
 * <p>
 * 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 : 跳转拦截器(权限拦截)
 * <p>
 * 比较经典的应用就是在跳转过程中处理登陆事件,这样就不需要在目标页重复做登陆检查
 * 拦截器会在跳转之间执行,多个拦截器会按优先级顺序依次执行
 *
 * @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 等操作后就可以添加业务处理了。

猜你喜欢

转载自blog.csdn.net/lovingning/article/details/79508678
今日推荐