前言

一直想整理一个自己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 {@Overridepublic 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 {@Overridepublic 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的Viewthis.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);}}@Overridepublic 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;}@Overrideprotected 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@Overridepublic Dialog onCreateDialog(Bundle savedInstanceState) {if (null == mOnCallDialog) {super.onCreate(savedInstanceState);}return mOnCallDialog.getDialog(getActivity());}@Overridepublic 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);}}@Overridepublic 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() {@Overridepublic 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() {@Overridepublic void onClick(View v) {sBaseDialogFragment.dismissAllowingStateLoss();}}).addViewOnclick(R.id.tv_dialog_ok, new View.OnClickListener() {@Overridepublic void onClick(View view) {positiveListener.onClick(view);sBaseDialogFragment.dismissAllowingStateLoss();}}).build();return dialog;}}, cancelable);sBaseDialogFragment.show(fragmentManager, CONFIRM_TAG);}

未完待续

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

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

  1. android项目集成okgo,Android中MVP+RXJAVA+OKGO框架

    [实例简介] Android中MVP+RXJAVA+OKGO框架 Glide的封装 沉浸式状态栏 butterknife 和recyclerview的使用 [实例截图] [核心代码] 882096ee ...

  2. app框架一(组件化)

    背景 在早期app功能比较简单业务不复杂的情况下,我们一般都不需要组件化,最多就是基础库抽成私有库,正常情况下不需要划分业务组件也不需要路由等.在合适的时机引入合适的框架,而不是盲目的跟风炫技.特别是 ...

  3. ArcBlock 博客 | OCAP超简易集成攻略(Android 篇)

    作者: Nate Robinson(ArcBlock 团队移动开发工程师) 5 天后,ArcBlock 第三场.中国的第一场内测版黑客马拉松即将在北京举行,截至本文推送之际,已有 56 人报名,剩余名 ...

  4. 物联网控制APP入门专题(四)---使用android studio制作一个控制页面的APP框架

    摘要:上篇文章讲了如何用阿里云IoT Studio快速制作一个网页版的手机端,以及通过第三方平台将这个网页打包成一个APK文件,使它可以安装到手机实现APP的功能.但是使用第三方平台做的APP是需要收 ...

  5. RN、Flutter、Uni-app APP框架对比

    RN.Flutter.Uni-app APP框架对比 框架背景介绍 Flutter Flutter 是 Google 开源的 UI 工具包,帮助开发者通过一套代码库高效构建多平台精美应用,支持移动.W ...

  6. halcon机器视觉算法原理与编程实战_快速弄懂机器学习里的集成算法:原理、框架与实战...

    作者:  博观厚积 简书专栏:https://www.jianshu.com/u/2f376f777ef1 1. 关于集成学习算法 集成学习算法,通俗地讲就是:三个臭皮匠,顶个诸葛亮,这在很多地方都有 ...

  7. 模块化解耦框架RxFluxArchitecture3-订阅管理绑定生命周期

    相关系列文章 模块化解耦框架RxFluxArchitecture1-框架简介 模块化解耦框架RxFluxArchitecture2-基本功能实现 模块化解耦框架RxFluxArchitecture3- ...

  8. 使用cordova + vue搭建混合app框架

    1. 前言:在进行hybrid app开发时,可以采用vue.js前端框架进行h5页面开发,然后使用跨平台cordova工具打包成app,如android或ios等,再h5页面也可以使用cordova ...

  9. 信捷PLC程序,八轴程序,有伺服也有步进,内部有伺服和步进计算公式换算,模块化编程框架

    信捷PLC程序,八轴程序,有伺服也有步进,内部有伺服和步进计算公式换算,模块化编程框架,包含各功能区规划,伺服步进电机DOG+JOG,气缸手动,公式计算数据处理,报警功能区,自动步进S调用等. 研究透 ...

最新文章

  1. 【SICP练习】91 练习2.64
  2. HPUX在oracle10g安装和卸载缩写
  3. Oracle计算两个整数的和与这两个整数的差与商
  4. go不使用工具包将大写字符转成小写字符的方法
  5. 2021-11-18可变参数
  6. html布局overflow,overflow的中文意思
  7. Android基础常用日期操作工具类
  8. MongoDB查询时排序字段为int类型和string类型的区别
  9. 40. Combination Sum II
  10. 服务器mysql占用_mysql占用服务器cpu过高的原因以及解决办法
  11. 配置php apache,apache如何配置php
  12. Visual Studio使用技巧---(1-10)
  13. 哪些版本linux支持arm,腾讯QQ Linux版本正式回归 支持x64 ARM64 MIPS64架构
  14. 总纲篇:产品结构设计指导VII(本博客指引章节)
  15. 2022新版UI云购H5系统源码+完美运行/功能强大
  16. netty权威指南(第二版)对应的源码
  17. QT移植Linux平台
  18. 排序数据图-R/python
  19. 简洁的桌面整理工具(Coodesker)
  20. python-把excel里面的数据存储到矩阵里面

热门文章

  1. 设备更新,工作室搬迁
  2. 雅虎邮箱停用对网民的影响
  3. html5 商品分类页面效果zepto
  4. c语言教程+school,C语言教程方法用法 _C语言-w3school教程
  5. QQ浏览器隐私泄露报告
  6. 阿里云服务器被入侵执行MoneroOcean(门罗币)挖矿脚本
  7. Lu求解含积分的复杂非线性方程(组)
  8. GPS 卫星的信号结构
  9. 成功解决sklearn.exceptions.NotFittedError: This StandardScaler instance is not fitted yet. Call ‘fit‘ wi
  10. 修改Cisco交换机ntp服务器,如何配置Brocade和Cisco光纤交换机的NTP服务器