1.安卓6.0權(quán)限處理機(jī)制绣溜、android 7.0讀寫(xiě)
M的權(quán)限慷彤、N外部文件讀寫(xiě),相信坑了不少同學(xué)怖喻〉谆框架里必須得有!
1.1權(quán)限問(wèn)題
權(quán)限問(wèn)題之前看到的一個(gè)很好的封裝方法锚沸,真正做到一個(gè)方法解決問(wèn)題跋选,而且我試驗(yàn)下來(lái),確實(shí)很完美哗蜈。
依賴(lài):
compile 'pub.devrel:easypermissions:0.1.9'
封裝:
在第三方基礎(chǔ)上前标,重新封裝,新建
EasyPermission距潘、PermissionCallBackM炼列、PermissionUtils。因?yàn)槠鶈?wèn)題音比,而且這部分代碼并不是我本人寫(xiě)的俭尖,就不貼出來(lái)了,小伙伴可以到源碼中查看洞翩。下面重點(diǎn)看一下在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基類(lèi)
* butterKnife的綁定 初始方法的設(shè)定 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;
//權(quán)限處理
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(); //初始化參數(shù)
protected abstract void initViews(); //初始化控件
protected abstract void createPresenter(); //創(chuàng)建presenter
/**
* @param toolbar toolbar 控件
* @param title 標(biāo)題
*/
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右側(cè)menu點(diǎn)擊事件
@Override
public boolean onMenuItemClick(MenuItem item) {
return false;
}
//統(tǒng)一處理錯(cuò)誤信息
public void handleError(BaseResultBean errResult) {
if (errResult == null) return;
if (this == null) return;
//可以分門(mén)別類(lèi)的處理 錯(cuò)誤消息稽犁,如session過(guò)期,跳轉(zhuǎn)到登錄頁(yè)面骚亿。其他情況提示即可
ToastUtils.showToast(mContext, errResult.getMsg());
}
//權(quán)限處理開(kāi)始
//rationale: 申請(qǐng)授權(quán)理由
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界面跳轉(zhuǎn)回來(lái)已亥,標(biāo)準(zhǔn)代碼,就這么寫(xiě)
*/
if (requestCode == EasyPermission.SETTINGS_REQ_CODE) {
if (EasyPermission.hasPermissions(this, mPermissions)) {
//已授權(quán)循未,處理業(yè)務(wù)邏輯
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, "您已關(guān)閉" + PermissionUtils.getRationale(perms) + "陷猫,部分功能將不能正常使用秫舌,點(diǎn)擊設(shè)置進(jìn)入設(shè)置頁(yè)面", 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);
}
}
//權(quán)限處理結(jié)束
@Override
protected void onDestroy() {
if (mPresenter != null) mPresenter.detachView();
if (mUnbinder != null) mUnbinder.unbind();
super.onDestroy();
}
}
使用:
封裝很徹底的妖,使用的時(shí)候也很簡(jiǎn)單,下面是我在LoadingActivity里的代碼足陨,獲取到讀寫(xiě)權(quán)限后再進(jìn)入Mainactivity嫂粟,否則退出應(yīng)用:
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讀寫(xiě)外部文件
封裝:
a,在res下新建xml文件夾,xml內(nèi)新建file_paths.xml,內(nèi)容如下
<?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,清單文件注冊(cè):
<!--解決安卓N 調(diào)用相冊(cè)異常-->
<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工具類(lèi)中的viewFile()內(nèi):
//解決安卓7.0的問(wèn)題
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.圖片處理工具
為啥要說(shuō)它呢宽涌。因?yàn)槲覀円郧坝胕mageloader比較多些平夜。但是picasso是基于okhttp的。因?yàn)橥ㄐ攀腔趏khttp的卸亮,所以可以無(wú)縫對(duì)接忽妒。它可以真正的一句代碼搞定加載圖片。
依賴(lài):
compile 'com.squareup.picasso:picasso:2.5.2'
封裝:
封裝比較簡(jiǎn)易兼贸,可根據(jù)項(xiàng)目調(diào)整
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.
* 圖片顯示類(lèi)
*/
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 我想也是不可或缺的一環(huán),想著把之前的用到的封裝起來(lái)溶诞。突然已有大牛完美的封裝了它鸯檬。還能這干嘛。直接依賴(lài)進(jìn)來(lái)螺垢。效果也挺不錯(cuò)
依賴(lài):
compile 'com.just.agentweb:agentweb:2.0.1'
封裝:
這個(gè)agentweb本身就是給webView作的封裝喧务,詳細(xì)介紹請(qǐng)看https://github.com/Justson/AgentWeb
使用:
下面的代碼取自原博客,我也在Activity和Fragment中測(cè)試驗(yàn)證了枉圃。效果很不錯(cuò)蹂楣,重點(diǎn)是不需要寫(xiě)webView相關(guān)的設(shè)置了。
mAgentWeb = AgentWeb.with(this)//傳入Activity or Fragment
.setAgentWebParent(mLinearLayout, new LinearLayout.LayoutParams(-1, -1))//傳入AgentWeb 的父控件 讯蒲,如果父控件為 RelativeLayout 痊土, 那么第二參數(shù)需要傳入 RelativeLayout.LayoutParams ,第一個(gè)參數(shù)和第二個(gè)參數(shù)應(yīng)該對(duì)應(yīng)。
.useDefaultIndicator()// 使用默認(rèn)進(jìn)度條
.defaultProgressBarColor() // 使用默認(rèn)進(jìn)度條顏色
.setReceivedTitleCallback(mCallback) //設(shè)置 Web 頁(yè)面的 title 回調(diào)
.createAgentWeb()//
.ready()
.go("http://www.jd.com");
4.錯(cuò)誤日志保存編寫(xiě)
錯(cuò)誤日志的保存很重要墨林,有利于項(xiàng)目的優(yōu)化和升級(jí)赁酝。后面可以根據(jù)情況上傳到服務(wù)器!
這部分的代碼,已經(jīng)很成熟了旭等。我只貼一下:
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;
/**錯(cuò)誤日志保存到本地
* Created by burro on 2017/10/20.
*/
public class CrashHandler implements UncaughtExceptionHandler {
public static final String TAG = "CrashHandler";
//系統(tǒng)默認(rèn)的UncaughtException處理類(lèi)
private Thread.UncaughtExceptionHandler mDefaultHandler;
//CrashHandler實(shí)例
private static CrashHandler INSTANCE = new CrashHandler();
//程序的Context對(duì)象
private Context mContext;
//用來(lái)存儲(chǔ)設(shè)備信息和異常信息
private Map<String, String> infos = new HashMap<String, String>();
//用于格式化日期,作為日志文件名的一部分
private DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
/** 保證只有一個(gè)CrashHandler實(shí)例 */
private CrashHandler() {
}
/** 獲取CrashHandler實(shí)例 ,單例模式 */
public static CrashHandler getInstance() {
return INSTANCE;
}
/**
* 初始化
*
* @param context
*/
public void init(Context context) {
mContext = context;
//獲取系統(tǒng)默認(rèn)的UncaughtException處理器
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
//設(shè)置該CrashHandler為程序的默認(rèn)處理器
Thread.setDefaultUncaughtExceptionHandler(this);
}
/**
* 當(dāng)UncaughtException發(fā)生時(shí)會(huì)轉(zhuǎn)入該函數(shù)來(lái)處理
*/
@Override
public void uncaughtException(Thread thread, Throwable ex) {
if (!handleException(ex) && mDefaultHandler != null) {
//如果用戶(hù)沒(méi)有處理則讓系統(tǒng)默認(rèn)的異常處理器來(lái)處理
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);
}
}
/**
* 自定義錯(cuò)誤處理,收集錯(cuò)誤信息 發(fā)送錯(cuò)誤報(bào)告等操作均在此完成.
*
* @param ex
* @return true:如果處理了該異常信息;否則返回false.
*/
private boolean handleException(Throwable ex) {
if (ex == null) {
return false;
}
//使用Toast來(lái)顯示異常信息
new Thread() {
@Override
public void run() {
Looper.prepare();
ToastUtils.showToast(mContext,"很抱歉,程序出現(xiàn)異常,即將退出");
Looper.loop();
}
}.start();
//收集設(shè)備參數(shù)信息
collectDeviceInfo(mContext);
//保存日志文件
saveCrashInfo2File(ex);
return true;
}
/**
* 收集設(shè)備參數(shù)信息
* @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);
}
}
}
/**
* 保存錯(cuò)誤信息到文件中
*
* @param ex
* @return 返回文件名稱(chēng),便于將文件傳送到服務(wù)器
*/
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類(lèi)里要做初始化:
CrashHandler.getInstance().init(this);
5.數(shù)據(jù)庫(kù)GreenDao的封裝使用
GreenDao的使用并非三言?xún)烧Z(yǔ)就能說(shuō)明白,推薦大家仔細(xì)讀讀下面這篇文章篓吁。然后寫(xiě)個(gè)demo測(cè)試下滑臊。
http://blog.csdn.net/njweiyukun/article/details/51893092
為了保證框架的純凈度,就不在內(nèi)部寫(xiě)相關(guān)測(cè)試了
6.使用到的的常用工具類(lèi)也是必不可少的弃榨。因?yàn)槎际且恍┙?jīng)典且大家熟知的代碼菩收。我就不一一詳細(xì)介紹。有興趣的同學(xué)可以看看源碼 或者進(jìn)行補(bǔ)充
好啦鲸睛。至此告一段落娜饵。下面會(huì)花一些時(shí)間好好研究daggar2,因?yàn)樗哪康氖墙怦罟ぷ鞴俦病⒋蟠髢?yōu)化我們的程序結(jié)構(gòu)箱舞。提高代碼質(zhì)量遍坟!