green 获取服务器信息,Android 捕获异常exception,GreenDao存取,上传服务器(后台)或 发送指定邮箱中...

app上线后并不能保证程序不会崩溃,为了更好的优化项目,需要获取异常日志,供开发人员修改bug,所以希望在程序崩溃时,能捕获异常,并保存异常。这些需求可以用第三方的框架,比如腾讯的bugly,github上也有些开源的等,但如果不想经过第三方的后台,或者想自己写个效果,可以考虑采用本文提供的方法---考虑网络异常时,崩溃信息不能上传后台,用数据库存储异常日志,当开启应用或有网络时,上传异常日志。同时,本文也提供了通过邮件来接收异常日志的方法。

一、捕获异常部分

第一步:定义 CrashException 模型,用于异常信息对象的创建

该类说明:注释代码是greendao的方式(不熟悉,可先去了解greendao的简单使用方法)

@Entity

public class CrashException {

@Transient

public static final int TYPE_TOSEND = 1;

@Transient

public static final int TYPE_SENDING = 2;

@Id

private Long crashId;

private String crashTime;

private String appName;

private String appVersion;

private String OSVersion;

private String mobileBrand;

private String mobileModel;

private String crashExceptionInfor;

private int sendStat = 1;//1表示待发送,2表示正在发送

@Generated(hash = 1063750127)

public CrashException(Long crashId, String crashTime, String appName,

String appVersion, String OSVersion, String mobileBrand,

String mobileModel, String crashExceptionInfor, int sendStat) {

this.crashId = crashId;

this.crashTime = crashTime;

this.appName = appName;

this.appVersion = appVersion;

this.OSVersion = OSVersion;

this.mobileBrand = mobileBrand;

this.mobileModel = mobileModel;

this.crashExceptionInfor = crashExceptionInfor;

this.sendStat = sendStat;

}

@Generated(hash = 2138202335)

public CrashException() {

}

public void setCrashExceptionInfor(String crashExceptionInfor) {

this.crashExceptionInfor = crashExceptionInfor;

}

public static Long creatCrashId(){

return (Long)((System.currentTimeMillis()+17)/3);

}

public Long getCrashId() {

return this.crashId;

}

public void setCrashId(Long crashId) {

this.crashId = crashId;

}

public String getCrashTime() {

return this.crashTime;

}

public void setCrashTime(String crashTime) {

this.crashTime = crashTime;

}

public String getAppName() {

return this.appName;

}

public void setAppName(String appName) {

this.appName = appName;

}

public String getAppVersion() {

return this.appVersion;

}

public void setAppVersion(String appVersion) {

this.appVersion = appVersion;

}

public String getOSVersion() {

return this.OSVersion;

}

public void setOSVersion(String OSVersion) {

this.OSVersion = OSVersion;

}

public String getMobileBrand() {

return this.mobileBrand;

}

public void setMobileBrand(String mobileBrand) {

this.mobileBrand = mobileBrand;

}

public String getMobileModel() {

return this.mobileModel;

}

public void setMobileModel(String mobileModel) {

this.mobileModel = mobileModel;

}

public String getCrashExceptionInfor() {

return this.crashExceptionInfor;

}

public int getSendStat() {

return this.sendStat;

}

public void setSendStat(int sendStat) {

this.sendStat = sendStat;

}

}

第二步:自定义AppUncaughtExceptionHandler 类实现Thread.UncaughtExceptionHandler 接口 此处会获得崩溃异常

该类主要:采用单例模式,初始化时,将该类设置为默认处理器,用于监听异常的回调,获取异常。获取异常后,存入数据库,并将异常信息发送给指定邮箱。

public class AppUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {

//用于输出异常信息

private PrintWriter pw;

private StringWriter sw;

//构造方法私有,防止外部构造多个实例,即采用单例模式

private AppUncaughtExceptionHandler() {

}

//程序的Context对象

private Context applicationContext;

private volatile boolean crashing;

/**

* 日期格式器

*/

private DateFormat mFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

/**

* 系统默认的UncaughtException处理类

*/

private Thread.UncaughtExceptionHandler mDefaultHandler;

/**

* 单例

*/

private static AppUncaughtExceptionHandler sAppUncaughtExceptionHandler;

public static synchronized AppUncaughtExceptionHandler getInstance() {

if (sAppUncaughtExceptionHandler == null) {

synchronized (AppUncaughtExceptionHandler.class) {

if (sAppUncaughtExceptionHandler == null) {

sAppUncaughtExceptionHandler = new AppUncaughtExceptionHandler();

}

}

}

return sAppUncaughtExceptionHandler;

}

/**

* 初始化

* @param context

*/

public void init(Context context) {

applicationContext = context.getApplicationContext();

crashing = false;

sw = new StringWriter();

pw = new PrintWriter(sw);

//获取系统默认的UncaughtException处理器

mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();

//设置该CrashHandler为程序的默认处理器

Thread.setDefaultUncaughtExceptionHandler(this);

}

//捕获异常

@Override

public void uncaughtException(Thread thread, Throwable ex) {

if (crashing) {

return;

}

crashing = true;

// 打印异常信息

ex.printStackTrace();

// 我们没有处理异常 并且默认异常处理不为空 则交给系统处理

if (!handlelException(ex) && mDefaultHandler != null) {

// 系统处理

mDefaultHandler.uncaughtException(thread, ex);

}

byebye();

}

private void byebye() {

SystemClock.sleep(2000);

android.os.Process.killProcess(android.os.Process.myPid());

System.exit(0);

}

private boolean handlelException(Throwable ex) {

if (ex == null) {

return false;

}

try {

//处理异常信息,并存入数据库,然后发送至服务器

handleCrashReport(ex);

// 提示对话框

showPatchDialog();

} catch (Exception e) {

return false;

}

return true;

}

/**

*处理异常信息,并存入数据库,然后发送至邮箱

* @param ex

* @return

*/

private String handleCrashReport(Throwable ex) {

String exceptionStr = "";

PackageInfo pinfo = CrashApp.getInstance().getLocalPackageInfo();

CrashException crashException = null;

if (pinfo != null) {

if (ex != null) {

//得到异常全部信息

if(sw==null){

sw = new StringWriter();

}

if(pw==null){

pw = new PrintWriter(sw);

}

ex.printStackTrace(pw);

String errorStr = sw.toString();

if (TextUtils.isEmpty(errorStr)) {

errorStr = ex.getMessage();

}

if (TextUtils.isEmpty(errorStr)) {

errorStr = ex.toString();

}

exceptionStr = errorStr;

//存入数据库中

crashException = new CrashException(CrashException.creatCrashId(),

mFormatter.format(new Date()),getApplicationName(applicationContext),pinfo.versionName,

Build.VERSION.RELEASE,Build.MANUFACTURER,Build.MODEL,errorStr,CrashException.TYPE_TOSEND);

Log.e("print", "异常信息: "+ crashException.getCrashExceptionInfor());

//发送到邮箱

SendExceptionManager.getInstance().sendToEmail(crashException);

} else {

exceptionStr = "no exception. Throwable is null";

}

return exceptionStr;

} else {

return "";

}

}

//崩溃后弹出提示框,是否取消还是重启

private void showPatchDialog() {

Intent intent = PatchDialogActivity.newIntent(applicationContext, getApplicationName(applicationContext), null);

applicationContext.startActivity(intent);

}

//获取应用名称

private String getApplicationName(Context context) {

PackageManager packageManager = context.getPackageManager();

ApplicationInfo applicationInfo = null;

String name = null;

try {

applicationInfo = packageManager.getApplicationInfo(

context.getApplicationInfo().packageName, 0);

name = (String) packageManager.getApplicationLabel(applicationInfo);

} catch (final PackageManager.NameNotFoundException e) {

String[] packages = context.getPackageName().split(".");

name = packages[packages.length - 1];

}

return name;

}

}

二、定义CrashApp继承Application(注意需在AndroidManifest.xml引用此App)

该类主要是完成GreenDao 和AppUncaughtExceptionHandler初始化工作

public class CrashApp extends Application {

private static CrashApp mInstance = null;

private static DaoSession daoSession;

public static CrashApp getInstance() {

if (mInstance == null) {

throw new IllegalStateException("Application is not created.");

}

return mInstance;

}

public static DaoSession getDaoInstance(){

if (daoSession == null) {

throw new IllegalStateException("GreenDao is not created.");

}

return daoSession;

}

@Override

public void onCreate() {

super.onCreate();

mInstance = this;

// 捕捉异常初始化

AppUncaughtExceptionHandler crashHandler = AppUncaughtExceptionHandler.getInstance();

crashHandler.init(getApplicationContext());

//初始化greendao数据库

setupDatabase();

}

/**

* 获取自身App安装包信息

* @return

*/

public PackageInfo getLocalPackageInfo() {

return getPackageInfo(getPackageName());

}

/**

* 获取App安装包信息

* @return

*/

public PackageInfo getPackageInfo(String packageName) {

PackageInfo info = null;

try {

info = getPackageManager().getPackageInfo(packageName, 0);

} catch (PackageManager.NameNotFoundException e) {

e.printStackTrace();

}

return info;

}

private void setupDatabase() {

//创建数据库myDB.db

DaoMaster.DevOpenHelper devOpenHelper = new DaoMaster.DevOpenHelper(mInstance,"myDB.db");

SQLiteDatabase db = devOpenHelper.getWritableDatabase();

//获取数据库对象

DaoMaster daoMaster = new DaoMaster(db);

//获取Dao对象的管理者

daoSession = daoMaster.newSession();

}

}

三、在应用的启动界面,监听网络状态,当网络可用时,检查是否有需要发送的异常日志,如果有,就发送,发送成功后,删除对应日志

public class MainActivity extends AppCompatActivity {

private NetworkBroadcastReceiver mReceiver;

private boolean doing;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

registerNetworkReceiver();

}

public void onClick(View view){

if(view.getId()== R.id.tv1){

Intent intent = new Intent(this,SecondActivity.class);

startActivity(intent);

}

if(view.getId()==R.id.tv2){

String test = null;

test.length();

}

}

private void registerNetworkReceiver() {

mReceiver = new NetworkBroadcastReceiver();

IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);

registerReceiver(mReceiver,filter);

mReceiver.setOnNetChange(new NetworkBroadcastReceiver.NetEvent() {

@Override

public void onNetChange(int netMobile) {

if(netMobile!=-1){

Log.e("print", "网络变化:可用! ");

//网络可以用的时候,看看数据库中是否有需要发送的错误日志

if(doing){

return;

}

doing = true;

List crashExceptionList = CrashExceptionHelper.getCrashExceptionList();

if(crashExceptionList!=null && crashExceptionList.size()!=0){

for(CrashException crashException : crashExceptionList){

crashException.setSendStat(CrashException.TYPE_SENDING);

SendExceptionManager.getInstance().sendToServer(crashException);

}

}

doing = false;

}else {

Log.e("print", "网络变化:不可用! ");

}

}

});

}

@Override

protected void onDestroy() {

super.onDestroy();

if(mReceiver!=null){

unregisterReceiver(mReceiver);

}

}

}

四、补充细节部分:(1)发送异常信息的管理类;(2)发送邮件需要有三个依赖包(在提供的github项目源码中有)

发送异常信息的管理类

public class SendExceptionManager {

/**

* 单例

*/

private static SendExceptionManager mSendExceptionManager;

private static ScheduledExecutorService scheduledThreadPool;

public static Context applicationContext;

private SendExceptionManager() {

//创建定长的5个线程

scheduledThreadPool = Executors.newScheduledThreadPool(5);

}

public static synchronized SendExceptionManager getInstance() {

if (mSendExceptionManager == null) {

synchronized (SendExceptionManager.class) {

if (mSendExceptionManager == null) {

mSendExceptionManager = new SendExceptionManager();

}

}

}

return mSendExceptionManager;

}

//发送到邮箱

public void sendToEmail(final CrashException crashException) {

if (scheduledThreadPool == null) {

scheduledThreadPool = Executors.newScheduledThreadPool(5);

}

scheduledThreadPool.execute(new Runnable() {

@Override

public void run() {

//账号密码

//用此邮箱来发送邮件(账号:********@qq.com , 密码:(开启POP3/SMTP服务的授权码))

Mail mail = new Mail("填账号", "填授权码");

//接受者邮箱 可以是多个

mail.set_to(new String[]{"******@qq.com","******@hotmail.com"});

//邮件来源

mail.set_from("******@qq.com");

//设置主题标题

mail.set_subject(getApplicationName(CrashApp.getInstance()) + "错误日志");

mail.setBody(crashException.toString());

try {

if (mail.send()) {

Log.e("crashInfor: ", "发送邮件成功");

} else {

Log.e("crashInfor:", "发送邮件失败");

}

} catch (Exception e) {

e.printStackTrace();

}

}

});

}

//发送到服务器(Ion)

public void sendToServer(final CrashException crashException) {

String device = crashException.getMobileBrand()+"--"+crashException.getMobileModel()+"--"+crashException.getOSVersion();

Ion.with(CrashApp.getInstance())

.load("url")//填写后台的的地址.

.setTimeout(3000)

.setBodyParameter("note", crashException.getCrashExceptionInfor())

.setBodyParameter("device",device)

.asJsonObject()

.setCallback(new FutureCallback() {

@Override

public void onCompleted(Exception e, JsonObject result) {

crashException.setSendStat(CrashException.TYPE_TOSEND);

if (e != null) {

CrashExceptionHelper.addCrashException(crashException);

}

if (result != null) {

Log.e("crashInfor:", "崩溃信息上传成功:"+result);

Boolean stat = result.get("success").getAsBoolean();

if(stat){

CrashExceptionHelper.deleteCrashException(crashException);

}else {

CrashExceptionHelper.addCrashException(crashException);

}

} else {

Log.e("crashInfor:", "崩溃信息上传失败");

CrashExceptionHelper.addCrashException(crashException);

}

}

});

}

private static String getApplicationName(Context context) {

PackageManager packageManager = context.getPackageManager();

ApplicationInfo applicationInfo = null;

String name = null;

try {

applicationInfo = packageManager.getApplicationInfo(

context.getApplicationInfo().packageName, 0);

name = (String) packageManager.getApplicationLabel(applicationInfo);

} catch (final PackageManager.NameNotFoundException e) {

String[] packages = context.getPackageName().split(".");

name = packages[packages.length - 1];

}

return name;

}

}

发送邮件:注意在Mail类中按实际 修改 _host = "smtp.qq.com"

public class Mail extends javax.mail.Authenticator{

private String _user;

private String _pass;

private String[] _to;

private String _from;

private String _port;

private String _sport;

private String _host;

private String _subject;

private String _body;

private boolean _auth;

private boolean _debuggable;

private Multipart _multipart;

public Mail() {

//以下三个才有可能需要改动,我是用qq邮箱发送,就是如下设置,比如你也可以用126邮箱 则改为smtp.126.com

// default smtp server

_host = "smtp.qq.com";

// default smtp port

_port = "465";

// default socketfactory port

_sport = "465";

_user = ""; // username

_pass = ""; // password

_from = ""; // email sent from

_subject = ""; // email subject

_body = ""; // email body

_debuggable = false; // debug mode on or off - default off

_auth = true; // smtp authentication - default on

_multipart = new MimeMultipart();

// There is something wrong with MailCap, javamail can not find a handler for the multipart/mixed part, so this bit needs to be added.

MailcapCommandMap mc = (MailcapCommandMap) CommandMap.getDefaultCommandMap();

mc.addMailcap("text/html;; x-java-content-handler=com.sun.mail.handlers.text_html");

mc.addMailcap("text/xml;; x-java-content-handler=com.sun.mail.handlers.text_xml");

mc.addMailcap("text/plain;; x-java-content-handler=com.sun.mail.handlers.text_plain");

mc.addMailcap("multipart/*;; x-java-content-handler=com.sun.mail.handlers.multipart_mixed");

mc.addMailcap("message/rfc822;; x-java-content-handler=com.sun.mail.handlers.message_rfc822");

CommandMap.setDefaultCommandMap(mc);

}

public Mail(String user, String pass) {

this();

_user = user;

_pass = pass;

}

public boolean send() throws Exception {

Properties props = _setProperties();

if(!_user.equals("") && !_pass.equals("") && _to.length > 0 && !_from.equals("") && !_subject.equals("") && !_body.equals("")) {

Session session = Session.getInstance(props, this);

MimeMessage msg = new MimeMessage(session);

msg.setFrom(new InternetAddress(_from));

InternetAddress[] addressTo = new InternetAddress[_to.length];

for (int i = 0; i < _to.length; i++) {

addressTo[i] = new InternetAddress(_to[i]);

}

msg.setRecipients(MimeMessage.RecipientType.TO, addressTo);

msg.setSubject(_subject);

msg.setSentDate(new Date());

// setup message body

BodyPart messageBodyPart = new MimeBodyPart();

messageBodyPart.setText(_body);

_multipart.addBodyPart(messageBodyPart);

// Put parts in message

msg.setContent(_multipart);

// send email

Transport.send(msg);

return true;

} else {

return false;

}

}

public void addAttachment(String filename) throws Exception {

BodyPart messageBodyPart = new MimeBodyPart();

DataSource source = new FileDataSource(filename);

messageBodyPart.setDataHandler(new DataHandler(source));

messageBodyPart.setFileName(filename);

_multipart.addBodyPart(messageBodyPart);

}

@Override

public PasswordAuthentication getPasswordAuthentication() {

return new PasswordAuthentication(_user, _pass);

}

private Properties _setProperties() {

Properties props = new Properties();

props.put("mail.smtp.host", _host);

if(_debuggable) {

props.put("mail.debug", "true");

}

if(_auth) {

props.put("mail.smtp.auth", "true");

}

props.put("mail.smtp.port", _port);

props.put("mail.smtp.socketFactory.port", _sport);

props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");

props.put("mail.smtp.socketFactory.fallback", "false");

return props;

}

// the getters and setters

public String getBody() {

return _body;

}

public void setBody(String _body) {

this._body = _body;

}

public void set_to(String[] _to) {

this._to = _to;

}

public void set_from(String _from) {

this._from = _from;

}

public void set_subject(String _subject) {

this._subject = _subject;

}

// more of the getters and setters …..

}

五、详情请参考 github Demo 欢迎star

水滴石穿!---Steve,从基础做起!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值