Java 接口的加深理解
bug 处理– 网络缓慢,网络未请求完数据后 ,关闭 当前 activity,系统崩溃
项目对使用 4G网络和 公司wifi 做了处理,有的接口 在 Android app 端是可以访问的,有的则不能。 昨天项目上线前测试的时候 发现,系统崩溃,搞得我好尴尬。因为点击 我的功能界面。不管这么样,自己的bug, 自己 哭着也要解决。
确定问题点,首先要问题重现
先找 找到 bug 的妹子,问下 点击的顺序及 过程。
发现就是 崩溃,好尴尬。。
又出现问题 ,系统 挂了的时候,什么都没有了,什么 地方出现 bug 也不知道,又是 很尴尬。。
还是 同事给力,提供了 bug 搜集 功能 的 工具,==该工具见最后的目录,可以在 Application 里面进行初始化操作==
该工具 可以在 工具里面打断点,可以查看 问题点。
该问题 报 在 我的 CustomerManagerActivity 类里面 ,dialog 展示 dialog.show()的时候 依附的 类找不到。 问题点的位置 在 Rxjava 和 Retrofit 的网络 框架里面的 onError 里面。
拿到问题后 就 分析一下 原因:
首先 我有 几个疑问?
1. 我在 返回的时候 ,关闭了 CustomerManagerActivity 这个类,对他进行 finish 操作 ,为什么后来 还会 调用 retrofit 的 网络请求 的 onError 方法, 毕竟 网络请求 是依附于 Acticity 的?
2. 我的 dialog 也是 依附于 我的 CustomerManagerActivity 这个类,为什么 他 这个对象还会 存在 ,不会回收?
3. 他们 两之间 有什么关系 呢? 都没有 回收?
4. 为什么 dialog .show 报错 ,而不是其他报错?
5. 如果网络请求 的方法中在 onsuccess 和 onComplated 方法中,其他地方会不会 也会报错?
6. ***为什么retrofit接口回调的方法中 接口回调完后,内存就释放了?***
有问题就 解决问题 ,和同事 讨论后 感觉 头脑清晰了 很多。
- 网络请求 肯定会 开线程去请求 ,只不过 结果可以 通过两种 形式返回 ,常见的是通过 handler 来进行 处理,还有就是 通过 接口回调 来 处理。 Rxjava 和 Retrofit 可以理解为 通过 接口 回调来处理内容。此时的 retrofit 对象为单例模式 ,可能其他地方也在用,没有 释放引用对象,所以该 对象还在。
- dialog 实在 activity 中初始化的,但是 其 在接口回调的 对象中被持有,所以 也是不会释放的。此时不会为空
- 正式由于 他们之间 有 持有 对象引用,该对象就不会 在 内存中释放,垃圾回收机制 RC 也不会回收。
- 因为 dialog ,popwindow 等是 依附于 内容提供者 Context ,在这里就是 Activity ,其是 一种浮层,在 Actictiy 上面展示,所以 在 初始化 dialog 等对象 的时候 需要传入 Context 上下文。如果 当前的Activity 销毁,其依附的对象 消失,就会报状态异常
View=com.android.internal.policy.PhoneWindow$DecorView{..}not attached to window manager,系统就崩溃。 - 其他 如果不需要依附Context (acticity )的内容,不展示效果,其处理完后 就会释放内存。也就是还会走接口回调的方法。但是 走完就走完了。
- 糊涂点的 解释 是用完了之后 ,垃圾回收机制就会释放内存,因为内存不足。 稍微清醒些的解释是:
由于 retrofit 持有 观察者和被观察者的 父类引用,其向下转型,
当回调方法走完后,方法内部的 内容就会 被释放?。
发现问题 ,了解问题原因后就 剩下解决问题:
- dialog 释放,当 acticity finish 掉后,就会 将 dialog=null,释放堆内存。在 onError 中 对 dialog 判空,如果不为空,再进行展示。
- 同事的方案: 考虑 让 网页点击返回的时候, 网络请求 中断。(好像用shutdown ?) 网上有人说 ,在网络请求 父类中 对Context 判空,如果为空,则return ,这种可以应用在 非 单例模式 里面。
1 为什么 我接口 调用方法名 例如:a()后,就会 调用该 方法的方法体{} 里面的内容?
答: 因为 我 可以将 方法a() 理解为 一个属性 ,他 在 内存地址中是 持有 一个引用地址 ,这个 引用地址 又持有 方法体的 引用地址, 方法体中 持有 相应的对象的 引用。 所以 可以 调用方法就会 执行方法体的内容。
初步理解:
sequenceDiagram
方法a->>方法a的方法体: 持有方法a 方法体的引用
方法a的方法体->>方法a: 与方法a绑定
2. 为什么 我调用该方法体,会一步步执行 代码?
猜测是 计算机的底层机制,什么机制不清楚。。。 有响应链么?
3. 为什么 我在一个类里面 新建的类 或者单例 ,当依附的这个类 回收了之后 新建的类或者单例 可能还没有销毁?
答: 因为 当我 依附的这个类 回收之后 ,这个类 里面的属性 和 方法 的引用 地址会 置空,但是 在 其他地方 可能还会持有 该 类的引用,比如说 接口回调和 单例模式的时候。 也就是说 这个对象 垃圾回收机制是不会回收的,同样的 方法 没有释放,方法体 里面持有的 对象 也不会释放。
5. 延伸: 如果我在 子线程中 处理接口回调的逻辑,接口回调 会马上执行么?
答: 不会 。因为 cpu 在执行 线程队列的时候,是按顺序走的, 他会回调到子线程,但是 子线程什么时候 执行 就是有 线程队列 里面的顺序决定的。
4. 一些理解的内容:
-
- 类里面的 属性 和 方法 都可以理解 为 属性 ,其作为 一个 引用地址 存在 类 里面的集合里,当 用到的时候 去调用 就可以了。
-
- 面向过程和面向对象 怎么这个老生常谈,因为他很重要。面上理解 :面向对象是 对 面向过程的封装,也就是 将 过程封装。 再粗点理解 可以将 方法和方法体 理解为 一个对象—- 本来就是一个对象
package com.taikanglife.isalessystem.crash;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Build;
import android.os.Environment;
import android.os.Looper;
import android.util.Log;
import android.widget.Toast;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.Field;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* Created by QunCheung on 2017/1/22.
*/
public class CrashHandler implements Thread.UncaughtExceptionHandler {
//系统默认的UncaughtException处理类
private Thread.UncaughtExceptionHandler mDefaultHandler;
//CrashHandler实例
private static CrashHandler INSTANCE;
//程序的Context对象
private Context mContext;
//用来存储设备信息和异常信息
private Map<String, String> infos = new HashMap<String, String>();
//文件价名称
private static final String TAG = "crash";
//文件名称
private static final String FILE_NAME = "crashlog";
//文件后缀
private static final String FORMAT = ".txt";
//存储位置文件夹
private String logdir;
//根据时间,定期上传日志
public final static int TIME = 1;
//根据日志大小,上传日志
public final static int MEMORY = 2;
//根据,时间,日志大小,上传日志
public final static int TIME_AND_MEMORY = 3;
//type
private int TYPE = 3;
//first init
private boolean isFirstInit;
//first init string
private final static String IS_FIRST_INIT = "is_first_init";
//first init time
private final static String FIRST_INIT_TIME = "first_init_time";
//时间差
private int TIME_SIZE;
//设置的时间间隔
private int DAYS = 7;
private CrashHandler() {
}
/**
* 获取CrashHandler实例 ,单例模式
*/
public static CrashHandler getInstance() {
if (INSTANCE == null)
INSTANCE = new CrashHandler();
return INSTANCE;
}
/**
* 初始化
*
* @param context
* @param type 上传模式
*/
public void init(Context context, int type) {
mContext = context;
//获取系统默认的UncaughtException处理器
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
//设置该CrashHandler为程序的默认处理器
Thread.setDefaultUncaughtExceptionHandler(this);
//设置文件保存路径
logdir = Environment.getExternalStorageDirectory().getAbsolutePath()
+ File.separator + TAG;
this.TYPE = type;
//时间存储
saveOrGetTimeType();
}
/**
* 初始化
*
* @param context 默认按照日志大小,控制日志上传
*/
public void init(Context context) {
mContext = context;
//获取系统默认的UncaughtException处理器
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
//设置该CrashHandler为程序的默认处理器
Thread.setDefaultUncaughtExceptionHandler(this);
//设置文件保存路径
logdir = Environment.getExternalStorageDirectory().getAbsolutePath()
+ File.separator + "crash_log";
this.TYPE = 3;
//时间存储
saveOrGetTimeType();
}
/**
* 获取保存时间信息
*/
private void saveOrGetTimeType() {
SharedPreferences crash = mContext.getSharedPreferences("crash", 1);
isFirstInit = crash.getBoolean(IS_FIRST_INIT, true);
if (isFirstInit) {
SharedPreferences.Editor edit = crash.edit();
edit.putBoolean(IS_FIRST_INIT, false);
edit.putLong(FIRST_INIT_TIME, getTimeToLong());
edit.commit();
} else {
long firstTime = crash.getLong(FIRST_INIT_TIME, 0);
long currentTime = getTimeToLong();
TIME_SIZE = (int) ((currentTime - firstTime) / 86400);
}
}
/**
* 获取时间
*/
private long getTimeToLong() {
try {
long time = Calendar.getInstance().getTimeInMillis();
return time;
} catch (Exception e) {
return 0;
}
}
/**
* 当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) {
Log.i(TAG, "uncaughtException: " + e.toString());
}
//退出程序
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();
Toast.makeText(mContext, "出错了~~~", Toast.LENGTH_LONG).show();
Looper.loop();
}
}.start();
//收集设备参数信息
collectDeviceInfo(mContext);
//保存日志文件
saveCrashInfoToFile(ex);
return true;
}
/**
* 并不应该每次崩溃都进行日志上传
*
* @param file
*/
private void svaeCrashInfoToServer(File file) {
// TODO: 2017/1/22 文本大小过大,必须上传
new Thread() {
@Override
public void run() {
Looper.prepare();
Toast.makeText(mContext, "日志已经很大了,应该上传服务器", Toast.LENGTH_LONG).show();
Looper.loop();
}
}.start();
}
/**
* 收集设备参数信息
*
* @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 (NameNotFoundException e) {
Log.i(TAG, "collectDeviceInfo: " + e.toString());
}
Field[] fields = Build.class.getDeclaredFields();
for (Field field : fields) {
try {
field.setAccessible(true);
infos.put(field.getName(), field.get(null).toString());
} catch (Exception e) {
Log.i(TAG, "collectDeviceInfo: " + e.toString());
}
}
}
/**
* 保存错误信息到文件中,应该清楚过长时间的错误信息
*
* @param ex
* @return 返回文件名称, 便于将文件传送到服务器
*/
private String saveCrashInfoToFile(Throwable ex) {
StringBuffer sb = new StringBuffer();
Date date = Calendar.getInstance().getTime();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
String times = sdf.format(date);
sb.append("---------------------sta--------------------------");
sb.append("crash at time: " + times);
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);
//可以追加用户信息
sb.append(addUserInfo());
sb.append("--------------------end---------------------------");
File file = getFilePath(logdir, FILE_NAME + FORMAT);
switch (TYPE) {
case 1:
case 2:
if (checkIsTimeToPush()) {
//保存日志到服务器
svaeCrashInfoToServer(file);
}
case 3:
//检查日志是否过大
if (checkFileIsToBig(file)) {
//保存日志到服务器
svaeCrashInfoToServer(file);
//写入本地,清空文本
// TODO: 2017/2/27 如果写本地则打开注释
WriteContentTypt(file,sb,true);
} else {
//写入本地,追加文本
WriteContentTypt(file,sb,false);
}
break;
default:
break;
}
//放开以上注释,请删除此行
// Log.i(TAG, “WriteContentTypt: ” + sb.toString());
return null;
}
/**
* 按照时间周期上传日志
*/
private boolean checkIsTimeToPush() {
if (TIME_SIZE >= DAYS) {
return true;
} else {
return false;
}
}
private String addUserInfo() {
return "UserInfo:\n";
}
private boolean checkFileIsToBig(File file) {
FileInputStream fis = null;
try {
fis = new FileInputStream(file);
int fileSize = fis.available();
if (fileSize > 1024 * 1024 * 5) {
return true;
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
/**
* 创建文件
*
* @param filePath 文件路径
* @param fileName 文件名称
* @return
*/
public static File getFilePath(String filePath,
String fileName) {
File file = new File(filePath, fileName);
if (file.exists()) {
return file;
} else {
file = null;
makeRootDirectory(filePath);
try {
file = new File(filePath + File.separator + fileName);
} catch (Exception e) {
// Auto-generated catch block
e.printStackTrace();
}
return file;
}
}
/**
* 创建根目录
*
* @param filePath
*/
public static void makeRootDirectory(String filePath) {
File file = null;
try {
file = new File(filePath);
if (!file.exists()) {
file.mkdir();
}
} catch (Exception e) {
}
}
/**
* 写入本地文件
*
* @param file 文件
* @param sb 内容
* @param isClean 是否清空,true:清空,false:保留
*/
public void WriteContentTypt(File file, StringBuffer sb, boolean isClean) {
try {
FileOutputStream fos = new FileOutputStream(file, !isClean);
fos.flush();
for (char c : sb.toString().toCharArray()) {
fos.write(c);
}
fos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
Log.i(TAG, "WriteContentTypt: " + sb.toString());
}
}
“`