简易集成的MVP模块化App框架(1/3)
前言
一直想整理一個自己app框架,現(xiàn)在剛好不是很忙就整理一下,尚不成熟還有待改進
大綱
1.整體結(jié)構(gòu):MVP模式+模塊化
2.網(wǎng)絡(luò)框架:Retrofit+Rxjava
3.屏幕適配方案:頭條的AndroidAutoSize
4.分享框架:Mob的ShareSDK
5.其他:base、常用工具類以及簡易的自定義控件等
6.常見問題
7.使用說明
項目鏈接
https://github.com/UncleQing/QingFrame
1.整體結(jié)構(gòu)
主要以MVP模式+模塊化為基礎(chǔ)展開
MVP參考:https://www.jianshu.com/p/3e981d261e90
模塊化參考:https://www.jianshu.com/p/748bf621a9a0
目錄結(jié)構(gòu)如下
app:做為項目主體,依賴library-common,也可以再定義一個module-main做為主體
library-common:做為所有共通資源以及管理類庫,現(xiàn)在設(shè)計的思想是其他module理論上都依賴common,然后再根據(jù)不同業(yè)務(wù)實現(xiàn)業(yè)務(wù)邏輯,好處是資源便于管理,缺點是導(dǎo)致common過于臃腫,可能不突出模塊化的優(yōu)勢。
另外根據(jù)業(yè)務(wù)分的module應(yīng)該還可以單獨運行,例如module-login可以被app依賴做為一個module,也可以自己單獨為一個可執(zhí)行application,這部分暫時沒有實現(xiàn),日后有待更新
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.網(wǎng)絡(luò)框架
Retrofit+Rxjava,當下最流行的網(wǎng)絡(luò)請求框架,簡單、好用、時髦,沒說的,當然也有不少大牛是用自己實現(xiàn)的,以后也要自己實現(xiàn)一個
基本上這些東西吧,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用于增加網(wǎng)絡(luò)斷開的攔截
/*** 網(wǎng)絡(luò)檢測 攔截器*/ 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做為請求頭和請求體的處理,需要根據(jù)實際服務(wù)器設(shè)置,代碼就不貼了
HttpHeader設(shè)定傳輸?shù)念^信息
HttpResult做為最基礎(chǔ)最外層的服務(wù)器傳輸數(shù)據(jù)結(jié)構(gòu)
IApiService做為管理所有網(wǎng)絡(luò)請求的接口
Retrofit網(wǎng)絡(luò)請求參數(shù)注解
/*** 所有網(wǎng)絡(luò)請求接口管理類* 根據(jù)不同請求配置接口名、參數(shù)*/ 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()) //網(wǎng)絡(luò)連接狀態(tài).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;}/*** ----------------------------* 根據(jù)不同業(yè)務(wù)請求創(chuàng)建* ----------------------------*//*** 測試模塊-測試接口* @return*/public Observable<HttpResult> testPing() {return mApiService.testPing();} }RetrofitConfig管理一些基本參數(shù)常量,服務(wù)器url、接口版本、超時、緩存大小等
3.屏幕適配方案
參照仿頭條的AndroidAutoSize
屏幕適配方案小結(jié)
4.分享框架
如果需要分享平臺過多單一集成太過麻煩,推薦大家使用Mob的shareSDK集成框架。
優(yōu)點:集成簡單、使用方便、易于管理、客服強大
使用ShareSDK集成分享框架
自定義一個簡單popWindow即可,如果對UI沒有要求直接用內(nèi)部的OneKeyShare,剩下的交給框架去完成
NormalSharePop自定義popwindow,當前制作了qq、微信、朋友圈、微信收藏、新浪微博,可以根據(jù)實際需求修改 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;/*** 分享參數(shù)詳解* 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);// 設(shè)置按鈕監(jiān)聽wechatBtn.setOnClickListener(this);wechatMomentsBtn.setOnClickListener(this);wechatFavoriteBtn.setOnClickListener(this);qqBtn.setOnClickListener(this);sinaWeiBoBtn.setOnClickListener(this);btnCancel.setOnClickListener(this);cancelView.setOnClickListener(this);btnCancel.setOnClickListener(this);//分享內(nèi)容title = shareBean.getTitle();url = shareBean.getUrl();siteUrl = shareBean.getSiteUrl();site = shareBean.getSite();text = shareBean.getText();imageUrl = shareBean.getImageUrl();imagePath = shareBean.getImagePath();titleUrl = shareBean.getTitleUrl();//設(shè)置PopupWindow的Viewthis.setContentView(mView);//設(shè)置PopupWindow彈出窗體的寬this.setWidth(ViewGroup.LayoutParams.MATCH_PARENT);//設(shè)置PopupWindow彈出窗體的高this.setHeight(ViewGroup.LayoutParams.MATCH_PARENT);//設(shè)置PopupWindow彈出窗體可點擊this.setFocusable(true);//設(shè)置SelectPicPopupWindow彈出窗體動畫效果this.setAnimationStyle(R.style.AnimationBottomFade);//實例化一個ColorDrawable顏色為半透明ColorDrawable dw = new ColorDrawable(0x60000000);//設(shè)置SelectPicPopupWindow彈出窗體的背景this.setBackgroundDrawable(dw);//全屏遮罩this.setClippingEnabled(false);//底部狀態(tài)欄計算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);//微博分享鏈接帶入描述,不設(shè)置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分享數(shù)據(jù)結(jié)構(gòu),基本都羅列了,不用修改
5.其他
utils
比較多,簡單介紹幾個吧,詳細請見項目內(nèi)代碼
dialog,封裝一套管理dialog,使用dialogFragment顯示,基本上一個項目大多數(shù)dialog都是共通的,有幾個特殊的單獨在里面定義即可
BaseDialog繼承Dialog,可以設(shè)置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 {/*** 監(jiān)聽彈出窗是否被取消*/private OnDialogCancelListener mCancelListener;/*** 回調(diào)獲得需要顯示的 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 以下的版本會出現(xiàn)白色背景邊框,若在 5.0 以上設(shè)置則會造成文字部分的背景也變成透明if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {// 目前只有這兩個 dialog 會出現(xiàn)邊框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管理類,已經(jīng)定義幾個常用dialog,可以根據(jù)實際需求再定義自己屬于自己項目的dialog 定義一個dialog需要一個tag,然后定義方法,參數(shù)必須傳入FragmentManager,剩下根據(jù)需要傳入寬高、title、點擊事件等, 然后在getDialog中創(chuàng)建dialog....大致模仿下面即可 /*** ------------------------------------------------------* 根據(jù)自己項目定義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);}未完待續(xù)
簡易集成的MVP模塊化App框架(2/3)
總結(jié)
以上是生活随笔為你收集整理的简易集成的MVP模块化App框架(1/3)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: LC滤波器原理
- 下一篇: lol查询服务器角色信息,游戏账号角色查