(七)安卓框架搭建之其他必备要素的封装或引入
2017-11-26 13:24
211 查看
1.安卓6.0权限处理机制、android 7.0读写
M的权限、N外部文件读写,相信坑了不少同学。框架里必须得有!1.1权限问题
权限问题之前看到的一个很好的封装方法,真正做到一个方法解决问题,而且我试验下来,确实很完美。依赖:
compile ‘pub.devrel:easypermissions:0.1.9’
封装:
在第三方基础上,重新封装,新建
EasyPermission、PermissionCallBackM、PermissionUtils。因为篇幅问题,而且这部分代码并不是我本人写的,就不贴出来了,小伙伴可以到源码中查看。下面重点看一下在BaseActivity里的封装:
package com.example.burro.demo.appframework.ui; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.view.MenuItem; import android.view.View; import com.afollestad.materialdialogs.DialogAction; import com.afollestad.materialdialogs.MaterialDialog; import com.example.burro.demo.appframework.BaseApplication; import com.example.burro.demo.appframework.mvp.presenter.BasePresenter; import com.example.burro.demo.appframework.mvp.view.BaseView; import com.example.burro.demo.appframework.permission.EasyPermission; import com.example.burro.demo.appframework.permission.PermissionCallBackM; import com.example.burro.demo.appframework.permission.PermissionUtils; import com.example.burro.demo.appframework.util.ToastUtils; import com.example.burro.demo.dataframework.model.BaseResultBean; import butterknife.ButterKnife; import butterknife.Unbinder; /** * BaseActivity Activity基类 * butterKnife的绑定 初始方法的设定 presentet和view的绑定 * Created by burro on 2017/9/23. */ public abstract class BaseActivity<T extends BasePresenter> extends AppCompatActivity implements BaseView,Toolbar.OnMenuItemClickListener,EasyPermission.PermissionCallback { protected T mPresenter; protected Activity mContext; private Unbinder mUnbinder; //权限处理 private int mRequestCode; private String[] mPermissions; private PermissionCallBackM mPermissionCallBack; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(initLayoutInflater()); mUnbinder = ButterKnife.bind(this); mContext = this; createPresenter(); if (mPresenter != null) mPresenter.attachView(this); BaseApplication.getInstance().addActivity(this); initParams(); initViews(); } protected abstract int initLayoutInflater(); //初始化布局 protected abstract void initParams(); //初始化参数 protected abstract void initViews(); //初始化控件 protected abstract void createPresenter(); //创建presenter /** * @param toolbar toolbar 控件 * @param title 标题 */ protected void setToolBar(Toolbar toolbar, String title) { if (toolbar != null) { if (title != null) toolbar.setTitle(title); setSupportActionBar(toolbar); toolbar.setOnMenuItemClickListener(this); getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayShowHomeEnabled(true); toolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { onBackPressed(); } }); } } //toolbar右侧menu点击事件 @Override public boolean onMenuItemClick(MenuItem item) { return false; } //统一处理错误信息 public void handleError(BaseResultBean errResult) { if (errResult == null) return; if (this == null) return; //可以分门别类的处理 错误消息,如session过期,跳转到登录页面。其他情况提示即可 ToastUtils.showToast(mContext, errResult.getMsg()); } //权限处理开始 //rationale: 申请授权理由 protected void requestPermission(int requestCode, String[] permissions, String rationale, PermissionCallBackM permissionCallback) { this.mRequestCode = requestCode; this.mPermissionCallBack = permissionCallback; this.mPermissions = permissions; EasyPermission.with(this) .addRequestCode(requestCode) .permissions(permissions) //.nagativeButtonText(android.R.string.ok) //.positveButtonText(android.R.string.cancel) .rationale(rationale) .request(); } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); EasyPermission.onRequestPermissionsResult(this, requestCode, permissions, grantResults); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); /* 从Settings界面跳转回来,标准代码,就这么写 */ if (requestCode == EasyPermission.SETTINGS_REQ_CODE) { if (EasyPermission.hasPermissions(this, mPermissions)) { //已授权,处理业务逻辑 onEasyPermissionGranted(mRequestCode, mPermissions); } else { onEasyPermissionDenied(mRequestCode, mPermissions); } } } @Override public void onEasyPermissionGranted(int requestCode, String... perms) { if (mPermissionCallBack != null) { mPermissionCallBack.onPermissionGrantedM(requestCode, perms); } } @Override public void onEasyPermissionDenied(final int requestCode, final String... perms) { //rationale: Never Ask Again后的提示信息 if (EasyPermission.checkDeniedPermissionsNeverAskAgain(this, "您已关闭" + PermissionUtils.getRationale(perms) + ",部分功能将不能正常使用,点击设置进入设置页面", android.R.string.ok, android.R.string.cancel, new MaterialDialog.SingleButtonCallback() { @Override public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { if (mPermissionCallBack != null) { mPermissionCallBack.onPermissionDeniedM( requestCode, perms); } } }, perms)) { return; } if (mPermissionCallBack != null) { mPermissionCallBack.onPermissionDeniedM(requestCode, perms); } } //权限处理结束 @Override protected void onDestroy() { if (mPresenter != null) mPresenter.detachView(); if (mUnbinder != null) mUnbinder.unbind(); super.onDestroy(); } }
使用:
封装很彻底,使用的时候也很简单,下面是我在LoadingActivity里的代码,获取到读写权限后再进入Mainactivity,否则退出应用:
requestPermission( 0, new String[]{PermissionUtils.PERMISSION_WRITE_EXTERNAL_STORAGE}, "", new PermissionCallBackM() { @Override public void onPermissionGrantedM(int requestCode, String... perms) { toMainActivity(); } @Override public void onPermissionDeniedM(int requestCode, String... perms) { finish(); } });
1.2 N读写外部文件
封装:a,在res下新建xml文件夹,xml内新建file_paths.xml,内容如下
<?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <!--表示Environment.getExternalStorageDirectory()目录或者其子目录。--> <external-path name="external-paths" path="."/> <!--表示Context.getFilesDir()目录或者其子目录。--> <files-path name="files-paths" path="." /> <!--表示Context.getExternalFilesDir(null)目录或者其子目录。--> <external-files-path name="external_files_paths" path="." /> <!--表示Context.getExternalCacheDir()目录或者其子目录。--> <external-cache-path name="external-cache-paths" path="." /> </paths>
b,清单文件注册:
<!--解决安卓N 调用相册异常--> <provider android:name="android.support.v4.content.FileProvider" android:authorities="com.example.burro.demo.provider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider>
使用:
在FileUtils工具类中的viewFile()内:
//解决安卓7.0的问题 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { mUri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", new File(filePath)); } else { mUri = Uri.fromFile(new File(filePath)); }
2.图片处理工具
为啥要说它呢。因为我们以前用imageloader比较多些。但是picasso是基于okhttp的。因为通信是基于okhttp的,所以可以无缝对接。它可以真正的一句代码搞定加载图片。依赖:
compile ‘com.squareup.picasso:picasso:2.5.2’
封装:
封装比较简易,可根据项目调整
package com.example.burro.demo.appframework.util; import android.content.Context; import android.graphics.drawable.Drawable; import android.widget.ImageView; import com.squareup.picasso.Picasso; /** * Created by burro on 2017/8/21. * 图片显示类 */ public class PicassoUtils { public static void loadImage(Context mContext, ImageView imageView, String url, int defaultImage) { if (mContext != null && imageView != null && !StringUtils.isStrEmpty(url) && defaultImage > 0) Picasso.with(mContext).load(url).error(defaultImage).placeholder(defaultImage) .fit() .centerCrop() .into(imageView); } public static void loadImage(Context mContext, ImageView imageView, String url, Drawable defaultImage) { if (mContext != null && imageView != null && !StringUtils.isStrEmpty(url)) if (defaultImage != null) { Picasso.with(mContext).load(url).error(defaultImage).placeholder(defaultImage) .fit() .centerCrop() .into(imageView); } else { Picasso.with(mContext).load(url) .fit() .centerCrop() .into(imageView); } } }
使用:
PicassoUtils.loadImage(this,imgLogo,movieInfoBean.images.large,null);
3webView的引入。webView 我想也是不可或缺的一环,想着把之前的用到的封装起来。突然已有大牛完美的封装了它。还能这干嘛。直接依赖进来。效果也挺不错
依赖:
compile ‘com.just.agentweb:agentweb:2.0.1’
封装:
这个agentweb本身就是给webView作的封装,详细介绍请看https://github.com/Justson/AgentWeb
使用:
下面的代码取自原博客,我也在Activity和Fragment中测试验证了。效果很不错,重点是不需要写webView相关的设置了。
mAgentWeb = AgentWeb.with(this)//传入Activity or Fragment .setAgentWebParent(mLinearLayout, new LinearLayout.LayoutParams(-1, -1))//传入AgentWeb 的父控件 ,如果父控件为 RelativeLayout , 那么第二参数需要传入 RelativeLayout.LayoutParams ,第一个参数和第二个参数应该对应。 .useDefaultIndicator()// 使用默认进度条 .defaultProgressBarColor() // 使用默认进度条颜色 .setReceivedTitleCallback(mCallback) //设置 Web 页面的 title 回调 .createAgentWeb()// .ready() .go("http://www.jd.com");
4.错误日志保存编写
错误日志的保存很重要,有利于项目的优化和升级。后面可以根据情况上传到服务器!这部分的代码,已经很成熟了。我只贴一下:
package com.example.burro.demo.appframework.util; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.os.Build; import android.os.Environment; import android.os.Looper; import java.io.File; import java.io.FileOutputStream; import java.io.PrintWriter; import java.io.StringWriter; import java.io.Writer; import java.lang.reflect.Field; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.lang.Thread.UncaughtExceptionHandler; /**错误日志保存到本地 * Created by burro on 2017/10/20. */ public class CrashHandler implements UncaughtExceptionHandler { public static final String TAG = "CrashHandler"; //系统默认的UncaughtException处理类 private Thread.UncaughtExceptionHandler mDefaultHandler; //CrashHandler实例 private static CrashHandler INSTANCE = new CrashHandler(); //程序的Context对象 private Context mContext; //用来存储设备信息和异常信息 private Map<String, String> infos = new HashMap<String, String>(); //用于格式化日期,作为日志文件名的一部分 private DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss"); /** 保证只有一个CrashHandler实例 */ private CrashHandler() { } /** 获取CrashHandler实例 ,单例模式 */ public static CrashHandler getInstance() { return INSTANCE; } /** * 初始化 * * @param context */ public void init(Context context) { mContext = context; //获取系统默认的UncaughtException处理器 mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler(); //设置该CrashHandler为程序的默认处理器 Thread.setDefaultUncaughtExceptionHandler(this); } /** * 当UncaughtException发生时会转入该函数来处理 */ @Override public void uncaughtException(Thread thread, Throwable ex) { if (!handleException(ex) && mDefaultHandler != null) { //如果用户没有处理则让系统默认的异常处理器来处理 mDefaultHandler.uncaughtException(thread, ex); } else { // try { // Thread.sleep(3000); // } catch (InterruptedException e) { // LogUtils.e(TAG, "error : ", e); // } //退出程序 android.os.Process.killProcess(android.os.Process.myPid()); System.exit(1); } } /** * 自定义错误处理,收集错误信息 发送错误报告等操作均在此完成. * * @param ex * @return true:如果处理了该异常信息;否则返回false. */ private boolean handleException(Throwable ex) { if (ex == null) { return false; } //使用Toast来显示异常信息 new Thread() { @Override public void run() { Looper.prepare(); ToastUtils.showToast(mContext,"很抱歉,程序出现异常,即将退出"); Looper.loop(); } }.start(); //收集设备参数信息 collectDeviceInfo(mContext); //保存日志文件 saveCrashInfo2File(ex); return true; } /** * 收集设备参数信息 * @param ctx */ public void collectDeviceInfo(Context ctx) { try { PackageManager pm = ctx.getPackageManager(); PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(), PackageManager.GET_ACTIVITIES); if (pi != null) { String versionName = pi.versionName == null ? "null" : pi.versionName; String versionCode = pi.versionCode + ""; infos.put("versionName", versionName); infos.put("versionCode", versionCode); } } catch (PackageManager.NameNotFoundException e) { LogUtils.e(TAG, "an error occured when collect package info", e); } Field[] fields = Build.class.getDeclaredFields(); for (Field field : fields) { try { field.setAccessible(true); infos.put(field.getName(), field.get(null).toString()); LogUtils.d(TAG, field.getName() + " : " + field.get(null)); } catch (Exception e) { LogUtils.e(TAG, "an error occured when collect crash info", e); } } } /** * 保存错误信息到文件中 * * @param ex * @return 返回文件名称,便于将文件传送到服务器 */ private String saveCrashInfo2File(Throwable ex) { StringBuffer sb = new StringBuffer(); for (Map.Entry<String, String> entry : infos.entrySet()) { String key = entry.getKey(); String value = entry.getValue(); sb.append(key + "=" + value + "\n"); } Writer writer = new StringWriter(); PrintWriter printWriter = new PrintWriter(writer); ex.printStackTrace(printWriter); Throwable cause = ex.getCause(); while (cause != null) { cause.printStackTrace(printWriter); cause = cause.getCause(); } printWriter.close(); String result = writer.toString(); sb.append(result); try { long timestamp = System.currentTimeMillis(); String time = formatter.format(new Date()); String fileName = "crash-" + time + "-" + timestamp + ".log"; if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { String path = FileUtils.getInstance().getCrashLogPath(); File dir = new File(path); if (!dir.exists()) { dir.mkdirs(); } FileOutputStream fos = new FileOutputStream(path + fileName); fos.write(sb.toString().getBytes()); fos.close(); } return fileName; } catch (Exception e) { LogUtils.e(TAG, "an error occured while writing file...", e); } return null; } }
需要注意的是,在application类里要做初始化:
CrashHandler.getInstance().init(this);
5.数据库GreenDao的封装使用
GreenDao的使用并非三言两语就能说明白,推荐大家仔细读读下面这篇文章。然后写个demo测试下。http://blog.csdn.net/njweiyukun/article/details/51893092
为了保证框架的纯净度,就不在内部写相关测试了
6.使用到的的常用工具类也是必不可少的。因为都是一些经典且大家熟知的代码。我就不一一详细介绍。有兴趣的同学可以看看源码 或者进行补充
好啦。至此告一段落。下面会花一些时间好好研究daggar2,因为它的目的是解耦工作。将大大优化我们的程序结构。提高代码质量!相关链接
github源码地址相关文章推荐
- (二)安卓框架搭建之项目Butterknife引入,Application的简单封装
- 安卓中引入GreenDAO数据库框架,包括封装好的数据库操作工具类
- 框架搭建之一------引入ASIHttp框架
- 用Python搭建自动化测试框架,我们需要组织用例以及测试执行,这里博主推荐Python的标准库——unittest。 unittest是xUnit系列框架中的一员,如果你了解xUnit的其他成员,那
- 不容错过,最全的安卓架构合集【从零开始搭建android框架系列(2)】
- 安卓网络请求框架okHttp的使用与封装
- km知识库项目中jar包详解补充(ssh3大框架的其他引入包的用处)
- (三)安卓框架搭建之MVP+Retrofit+RxJava基础
- 安卓市场--框架搭建5
- (一)安卓框架搭建之项目分层、主题、gradle基本配置
- thinkphp(tp)框架封装一个自己写的方法到类里面.可以在其他控制器里面调用
- 不容错过,最全的安卓架构合集【从零开始搭建android框架系列(2)】
- 不容错过,最全的安卓架构合集【从零开始搭建android框架系列(2)】
- android MVP + dagger2 + Retrofit + Rxjava+okhttp android基础项目框架搭建(2)--之MVP引入
- (四)安卓框架搭建之MVP+Retrofit+RxJava优化
- 安卓LitePal数据库框架初始化问题及其他异常
- 安卓开发工程师必备技能——框架,看看你都掌握了哪些
- 手把手教你如何搭建一个自己的安卓快速开发框架之带你做自己的APP(二)
- 基于springboot+bootstrap+mysql+redis搭建一套完整的权限架构【六】【引入bootstrap前端框架】
- android MVP + dagger2 + Retrofit + Rxjava+okhttp android基础项目框架搭建(1)--之Dagger2引入