简易集成的MVP模块化App框架(1/3)

前言

一直想整理一个自己app框架,现在刚好不是很忙就整理一下,尚不成熟还有待改进

大纲

1.整体结构:MVP模式+模块化

2.网络框架:Retrofit+Rxjava

3.屏幕适配方案:头条的AndroidAutoSize

4.分享框架:Mob的ShareSDK

5.其他:base、常用工具类以及简易的自定义控件等

6.常见问题

7.使用说明

项目链接

https://github.com/UncleQing/QingFrame

1.整体结构

主要以MVP模式+模块化为基础展开

MVP参考:https://www.jianshu.com/p/3e981d261e90

模块化参考:https://www.jianshu.com/p/748bf621a9a0

目录结构如下

app:做为项目主体,依赖library-common,也可以再定义一个module-main做为主体

library-common:做为所有共通资源以及管理类库,现在设计的思想是其他module理论上都依赖common,然后再根据不同业务实现业务逻辑,好处是资源便于管理,缺点是导致common过于臃肿,可能不突出模块化的优势。

另外根据业务分的module应该还可以单独运行,例如module-login可以被app依赖做为一个module,也可以自己单独为一个可执行application,这部分暂时没有实现,日后有待更新

common依赖列举

//ARouter
annotationProcessor 'com.alibaba:arouter-compiler:1.1.4'
api 'com.alibaba:arouter-api:1.3.1'
//retrofit
api 'com.squareup.retrofit2:retrofit:2.4.0'
api 'com.squareup.retrofit2:converter-gson:2.4.0'
api 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'
//okhttp
api 'com.squareup.okhttp3:okhttp:3.11.0'
api 'com.squareup.okhttp3:logging-interceptor:3.9.1'
//rxjava and rxandroid
api 'io.reactivex.rxjava2:rxandroid:2.1.0'
api 'io.reactivex.rxjava2:rxjava:2.2.0'
//gson
api 'com.google.code.gson:gson:2.8.2'
//分包工具
api 'com.android.support:multidex:1.0.3'
//butterknife
api 'com.jakewharton:butterknife:8.8.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
//AndroidAutoSize
api 'me.jessyan:autosize:1.1.0'
//glide
api 'com.github.bumptech.glide:okhttp3-integration:1.4.0@aar'
api 'com.github.bumptech.glide:glide:4.3.1'
//圆形图片
api 'de.hdodenhof:circleimageview:2.2.0'
//AgentWeb
api 'com.just.agentweb:agentweb:4.0.2'

2.网络框架

Retrofit+Rxjava,当下最流行的网络请求框架,简单、好用、时髦,没说的,当然也有不少大牛是用自己实现的,以后也要自己实现一个

基本上这些东西吧,interceptor大概制定了四个DownloadInterceptor用于获取下载进度拦截器

/**
 * 下载拦截器,用于进度条显示
 */
public class DownloadInterceptor implements Interceptor {


    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        Response response = chain.proceed(request);
        String value = request.header("Upgrade");
        if (!TextUtils.isEmpty(value) || "upgrade".equals(value)) {
            //只有是下载的时候会包含这个Upgrade头信息
                return response.newBuilder()
                        .body(new ProgressResponseBody(response.body()))
                        .build();
        }
        return response;
    }

}

NetCheckInterceptor用于增加网络断开的拦截

/**
 * 网络检测 拦截器
 */
public class NetCheckInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        if (!NetworkUtils.isNetworkConnected(AppUtils.getApp())) {
            throw new UnknownHostException("no network is connected");
        }
        return chain.proceed(chain.request().newBuilder().build());
    }
}

RequestInterceptor和ResponseInterceptor做为请求头和请求体的处理,需要根据实际服务器设置,代码就不贴了
HttpHeader设定传输的头信息
HttpResult做为最基础最外层的服务器传输数据结构
IApiService做为管理所有网络请求的接口

Retrofit网络请求参数注解

/**
 * 所有网络请求接口管理类
 * 根据不同请求配置接口名、参数
 */
public interface IApiService {
    @POST("/test/ping")
    Observable<HttpResult> testPing();
}

RetrofitApiService做为Retrofit核心类,初始化Retrofit、OKhttp等

public class RetrofitApiService {
    private Retrofit mRetrofit;
    private IApiService mApiService;

    private static class Holder {
        private static final RetrofitApiService INSTANCE = new RetrofitApiService();
    }

    public static RetrofitApiService getInstance() {
        return Holder.INSTANCE;
    }

    private RetrofitApiService() {
        OkHttpClient client = new OkHttpClient.Builder()
                .addNetworkInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
                .addInterceptor(new NetCheckInterceptor())  //网络连接状态
                .addInterceptor(new RequestInterceptor())   //requset
                .addInterceptor(new ResponseInterceptor())  //response
                .addNetworkInterceptor(new DownloadInterceptor())   //下载进度
                .connectTimeout(RetrofitConfig.DEFAULT_TIMEOUT, TimeUnit.SECONDS)
                .readTimeout(RetrofitConfig.DEFAULT_TIMEOUT, TimeUnit.SECONDS)
                .writeTimeout(RetrofitConfig.DEFAULT_TIMEOUT, TimeUnit.SECONDS) //超时和重连,上同
                .cache(new Cache(AppUtils.getApp().getExternalCacheDir(), RetrofitConfig.CACHE_SIZE))
                .retryOnConnectionFailure(true) //错误重连
                .build();

        mRetrofit = new Retrofit.Builder()
                .client(client)
                .baseUrl(RetrofitConfig.BASE_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .build();

        mApiService = mRetrofit.create(IApiService.class);
    }

    public IApiService getApiService() {
        return mApiService;
    }

    /**
     * ----------------------------
     * 根据不同业务请求创建
     * ----------------------------
     */

    /**
     * 测试模块-测试接口
     * @return
     */
    public Observable<HttpResult> testPing() {
        return mApiService.testPing();
    }
}

RetrofitConfig管理一些基本参数常量,服务器url、接口版本、超时、缓存大小等

3.屏幕适配方案

参照仿头条的AndroidAutoSize

屏幕适配方案小结

4.分享框架

如果需要分享平台过多单一集成太过麻烦,推荐大家使用Mob的shareSDK集成框架。

优点:集成简单、使用方便、易于管理、客服强大

使用ShareSDK集成分享框架

自定义一个简单popWindow即可,如果对UI没有要求直接用内部的OneKeyShare,剩下的交给框架去完成

NormalSharePop自定义popwindow,当前制作了qq、微信、朋友圈、微信收藏、新浪微博,可以根据实际需求修改
public class NormalSharePop extends PopupWindow implements OnClickListener{
    private LinearLayout wechatBtn, wechatMomentsBtn, wechatFavoriteBtn, qqBtn, sinaWeiBoBtn;
    private Button btnCancel;
    private View cancelView;
    private Context mContext;
    private Platform plat;

    /**
     * 分享参数详解
     * http://wiki.mob.com/%e4%b8%8d%e5%90%8c%e5%b9%b3%e5%8f%b0%e5%88%86%e4%ba%ab%e5%86%85%e5%ae%b9%e7%9a%84%e8%af%a6%e7%bb%86%e8%af%b4%e6%98%8e/
     */
    private String title;
    private String url;
    private String siteUrl;
    private String site;
    private String text;
    private String imageUrl;
    private String imagePath;
    private String titleUrl;


    public NormalSharePop(Activity context, ShareBean shareBean) {

        super(context);
        mContext = context;
        View mView = View.inflate(mContext, R.layout.pop_normalshare, null);

        wechatBtn = mView.findViewById(R.id.normalshare_wechat);
        wechatMomentsBtn = mView.findViewById(R.id.normalshare_wechat_moments);
        wechatFavoriteBtn = mView.findViewById(R.id.normalshare_wechat_favorite);
        qqBtn = mView.findViewById(R.id.normalshare_qq);
        sinaWeiBoBtn = mView.findViewById(R.id.normalshare_sinaweibo);
        btnCancel = mView.findViewById(R.id.normalshare_cancel);
        cancelView = mView.findViewById(R.id.normalshare_cancel_view);

        // 设置按钮监听
        wechatBtn.setOnClickListener(this);
        wechatMomentsBtn.setOnClickListener(this);
        wechatFavoriteBtn.setOnClickListener(this);
        qqBtn.setOnClickListener(this);
        sinaWeiBoBtn.setOnClickListener(this);
        btnCancel.setOnClickListener(this);
        cancelView.setOnClickListener(this);
        btnCancel.setOnClickListener(this);

        //分享内容
        title = shareBean.getTitle();
        url = shareBean.getUrl();
        siteUrl = shareBean.getSiteUrl();
        site = shareBean.getSite();
        text = shareBean.getText();
        imageUrl = shareBean.getImageUrl();
        imagePath = shareBean.getImagePath();
        titleUrl = shareBean.getTitleUrl();

        //设置PopupWindow的View
        this.setContentView(mView);

        //设置PopupWindow弹出窗体的宽
        this.setWidth(ViewGroup.LayoutParams.MATCH_PARENT);
        //设置PopupWindow弹出窗体的高
        this.setHeight(ViewGroup.LayoutParams.MATCH_PARENT);
        //设置PopupWindow弹出窗体可点击
        this.setFocusable(true);
        //设置SelectPicPopupWindow弹出窗体动画效果
        this.setAnimationStyle(R.style.AnimationBottomFade);
        //实例化一个ColorDrawable颜色为半透明
        ColorDrawable dw = new ColorDrawable(0x60000000);
        //设置SelectPicPopupWindow弹出窗体的背景
        this.setBackgroundDrawable(dw);
        //全屏遮罩
        this.setClippingEnabled(false);
        //底部状态栏计算
        if (UIUtils.isNavigationBarShow(context)) {
            int heigth = UIUtils.getNavigationBarHeight(context);
            LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) btnCancel.getLayoutParams();
            lp.bottomMargin = heigth;
            btnCancel.setLayoutParams(lp);
        }

    }

    @Override
    public void onClick(View v) {
        int i = v.getId();
        if (i == R.id.normalshare_wechat) {
            //微信通讯录
            if (!CommonUtil.isWeixinAvilible(AppUtils.getApp())) {
                ToastUtils.showToast(AppUtils.getApp(), "检查到您手机没有安装微信,请安装后使用该功能");
                return;
            }
            Wechat.ShareParams wechatSP = new Wechat.ShareParams();
            wechatSP.setShareType(Wechat.SHARE_WEBPAGE);
            wechatSP.setTitle(title);
            wechatSP.setText(text);
            wechatSP.setUrl(url);
            wechatSP.setImagePath(imagePath);
            wechatSP.setImageUrl(imageUrl);
            plat = ShareSDK.getPlatform(Wechat.NAME);
            plat.share(wechatSP);

        } else if (i == R.id.normalshare_wechat_moments) {
            //微信朋友圈
            if (!CommonUtil.isWeixinAvilible(AppUtils.getApp())) {
                ToastUtils.showToast(AppUtils.getApp(), "检查到您手机没有安装微信,请安装后使用该功能");
                return;
            }
            Wechat.ShareParams wechatSP2 = new Wechat.ShareParams();
            wechatSP2.setShareType(WechatMoments.SHARE_WEBPAGE);
            wechatSP2.setTitle(title);
            wechatSP2.setText(text);
            wechatSP2.setUrl(url);
            wechatSP2.setImagePath(imagePath);
            wechatSP2.setImageUrl(imageUrl);
            plat = ShareSDK.getPlatform(WechatMoments.NAME);
            plat.share(wechatSP2);

        } else if (i == R.id.normalshare_wechat_favorite) {
            //微信收藏
            if (!CommonUtil.isWeixinAvilible(AppUtils.getApp())) {
                ToastUtils.showToast(AppUtils.getApp(), "检查到您手机没有安装微信,请安装后使用该功能");
                return;
            }
            Wechat.ShareParams wechatSP3 = new Wechat.ShareParams();
            wechatSP3.setShareType(WechatFavorite.SHARE_WEBPAGE);
            wechatSP3.setTitle(title);
            wechatSP3.setText(text);
            wechatSP3.setUrl(url);
            wechatSP3.setImagePath(imagePath);
            wechatSP3.setImageUrl(imageUrl);
            plat = ShareSDK.getPlatform(WechatFavorite.NAME);
            plat.share(wechatSP3);

        } else if (i == R.id.normalshare_qq) {
            //qq通讯录
            if (!CommonUtil.isQQClientAvailable(AppUtils.getApp())) {
                ToastUtils.showToast(AppUtils.getApp(), "检查到您手机没有安装QQ,请安装后使用该功能");
                return;
            }
            QQ.ShareParams qqSP = new QQ.ShareParams();
            qqSP.setShareType(QQ.SHARE_WEBPAGE);
            qqSP.setTitle(title);
            qqSP.setText(text);
            qqSP.setUrl(url);
            qqSP.setTitleUrl(titleUrl);
            qqSP.setSite(site);
            qqSP.setSiteUrl(siteUrl);
            qqSP.setImagePath(imagePath);
            qqSP.setImageUrl(imageUrl);
            plat = ShareSDK.getPlatform(QQ.NAME);
            plat.share(qqSP);

        } else if (i == R.id.normalshare_sinaweibo) {
             //新浪微博
            SinaWeibo.ShareParams weiboSP = new SinaWeibo.ShareParams();
            weiboSP.setShareType(SinaWeibo.SHARE_WEBPAGE);
            weiboSP.setTitle(title);
            //微博分享链接带入描述,不设置url,否则不能显示图片
            weiboSP.setText(text + "\n" + url);
//                weiboSP.setUrl(url);
            weiboSP.setImagePath(imagePath);
            weiboSP.setImageUrl(imageUrl);
            plat = ShareSDK.getPlatform(SinaWeibo.NAME);
            plat.share(weiboSP);

        }else {
            dismiss();
        }
    }
}

ShareBean分享数据结构,基本都罗列了,不用修改

5.其他

utils

比较多,简单介绍几个吧,详细请见项目内代码

dialog,封装一套管理dialog,使用dialogFragment显示,基本上一个项目大多数dialog都是共通的,有几个特殊的单独在里面定义即可

BaseDialog继承Dialog,可以设置dialog的宽高、文案信息等
public class BaseDialog extends Dialog {
    private int height, width;
    private View mView;

    public BaseDialog(Builder builder) {
        this(builder, 0);
    }

    public BaseDialog(Builder builder, int resStyle) {
        super(builder.context, builder.resStyle);
        init(builder);
    }


    private void init(Builder builder) {
        this.height = builder.height;
        this.width = builder.width;
        this.mView = builder.view;

    }


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(mView);

        Window window = getWindow();
        WindowManager.LayoutParams layoutParams = window.getAttributes();
        layoutParams.gravity = Gravity.CENTER;
        layoutParams.height = height;
        layoutParams.width = width;
        window.setAttributes(layoutParams);

    }

    public final static class Builder {
        private Context context;
        private int height, width;
        private View view;
        private int resStyle = -1;

        public Builder(Context context) {
            this.context = context;
        }

        public Builder layout(int resView) {
            view = LayoutInflater.from(context).inflate(resView, null);
            return this;
        }

        public Builder height(int val) {
            height = val;
            return this;
        }

        public Builder width(int val) {
            width = val;
            return this;
        }

        public Builder style(int resStyle) {
            this.resStyle = resStyle;
            return this;
        }

        public Builder addViewOnclick(int viewRes, View.OnClickListener listener) {
            View btn = view.findViewById(viewRes);
            btn.setVisibility(View.VISIBLE);
            btn.setOnClickListener(listener);
            return this;
        }

        public Builder setText(int viewRes, int msgRes) {
            View view1 = view.findViewById(viewRes);
            if (view1 instanceof Button) {
                Button button = (Button) view1;
                button.setText(msgRes);
            } else if (view1 instanceof TextView) {
                TextView textView = (TextView) view1;
                textView.setText(msgRes);
            } else {
                return this;
            }
            view1.setVisibility(View.VISIBLE);
            return this;
        }

        public Builder setImage(int viewRes, String imgUrl) {
            View view1 = view.findViewById(viewRes);
            if (view1 instanceof ImageView) {
                ImageView iv = (ImageView) view1;
                GlideApp.with(context).load(imgUrl)
                        .diskCacheStrategy(DiskCacheStrategy.RESOURCE)
                        .into(iv);
            } else {
                return this;
            }
            view1.setVisibility(View.VISIBLE);
            return this;
        }

        public Builder setImageBitmap(int viewRes, Bitmap bitmap) {
            View view1 = view.findViewById(viewRes);
            if (view1 instanceof ImageView) {
                ImageView iv = (ImageView) view1;
                iv.setImageBitmap(bitmap);
            } else {
                return this;
            }
            view1.setVisibility(View.VISIBLE);
            return this;
        }

        public Builder setText(int viewRes, String msg) {
            TextView textView = view.findViewById(viewRes);
            textView.setVisibility(View.VISIBLE);
            textView.setText(msg);
            return this;
        }

        public View getView() {
            return view;
        }

        public BaseDialog build() {
            if (resStyle == -1) {
                return new BaseDialog(this);
            } else {
                return new BaseDialog(this, resStyle);
            }
        }
    }
}
BaseDialogFragment做为实际显示的dialog主题,因为是fragment所以生命周期是同步主体的,google也推荐这种方式显示dialog
public class BaseDialogFragment extends DialogFragment {

    /**
     * 监听弹出窗是否被取消
     */
    private OnDialogCancelListener mCancelListener;

    /**
     * 回调获得需要显示的 dialog
     */
    private OnCallDialog mOnCallDialog;

    public interface OnDialogCancelListener {
        void onCancel();
    }

    public interface OnCallDialog {
        Dialog getDialog(Context context);
    }

    public static BaseDialogFragment newInstance(OnCallDialog callDialog, boolean cancelable) {
        return newInstance(callDialog, cancelable, null);
    }

    public static BaseDialogFragment newInstance(OnCallDialog callDialog, boolean cancelable, OnDialogCancelListener cancelListener) {
        BaseDialogFragment instance = new BaseDialogFragment();
        instance.setCancelable(cancelable);
        instance.mCancelListener = cancelListener;
        instance.mOnCallDialog = callDialog;
        return instance;
    }

    @NonNull
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        if (null == mOnCallDialog) {
            super.onCreate(savedInstanceState);
        }
        return mOnCallDialog.getDialog(getActivity());
    }

    @Override
    public void onStart() {
        super.onStart();
        Dialog dialog = getDialog();
        if (dialog != null) {
            // 在 5.0 以下的版本会出现白色背景边框,若在 5.0 以上设置则会造成文字部分的背景也变成透明
            if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
                // 目前只有这两个 dialog 会出现边框
                if (dialog instanceof ProgressDialog || dialog instanceof DatePickerDialog) {
                    getDialog().getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
                }
            }

            Window window = getDialog().getWindow();
            WindowManager.LayoutParams windowParams = window.getAttributes();
            window.setAttributes(windowParams);
        }
    }

    @Override
    public void onCancel(DialogInterface dialog) {
        super.onCancel(dialog);
        if (mCancelListener != null) {
            mCancelListener.onCancel();
        }
    }

}
DialogFragmentHelper做为dialog管理类,已经定义几个常用dialog,可以根据实际需求再定义自己属于自己项目的dialog
定义一个dialog需要一个tag,然后定义方法,参数必须传入FragmentManager,剩下根据需要传入宽高、title、点击事件等,
然后在getDialog中创建dialog....大致模仿下面即可
    /**
     * ------------------------------------------------------
     * 根据自己项目定义dialog
     * ------------------------------------------------------
     */

    /**
     * 信息+取消btn+确定btn
     */
    private static final String CONFIRM_TAG = TAG_HEAD + ":confirm";

    public static void showNoTitleConfirmDialog(FragmentManager fragmentManager, final String message
            , boolean cancelable, final View.OnClickListener positiveListener) {
        sBaseDialogFragment = BaseDialogFragment.newInstance(new BaseDialogFragment.OnCallDialog() {
            @Override
            public Dialog getDialog(Context context) {
                BaseDialog.Builder builder = new BaseDialog.Builder(context);
                int width = UIUtils.dp2px(context, 280);
                int height = UIUtils.dp2px(context, 150);

                BaseDialog dialog = builder.layout(R.layout.dialog_no_title_confirm).style(BASE_THEME).height(height).width(width)
                        .setText(R.id.tv_diaglog_msg, message)
                        .addViewOnclick(R.id.tv_dialog_cancel, new View.OnClickListener() {
                            @Override
                            public void onClick(View v) {
                                sBaseDialogFragment.dismissAllowingStateLoss();
                            }
                        })
                        .addViewOnclick(R.id.tv_dialog_ok, new View.OnClickListener() {
                            @Override
                            public void onClick(View view) {
                                positiveListener.onClick(view);
                                sBaseDialogFragment.dismissAllowingStateLoss();
                            }
                        }).build();
                return dialog;
            }
        }, cancelable);
        sBaseDialogFragment.show(fragmentManager, CONFIRM_TAG);

    }

未完待续

简易集成的MVP模块化App框架(2/3)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值