Java 接口的加深理解

Java 接口的加深理解

bug 处理– 网络缓慢,网络未请求完数据后 ,关闭 当前 activity,系统崩溃

项目对使用 4G网络和 公司wifi 做了处理,有的接口 在 Android app 端是可以访问的,有的则不能。 昨天项目上线前测试的时候 发现,系统崩溃,搞得我好尴尬。因为点击 我的功能界面。不管这么样,自己的bug, 自己 哭着也要解决。

  1. 确定问题点,首先要问题重现

    先找 找到 bug 的妹子,问下 点击的顺序及 过程。

    发现就是 崩溃,好尴尬。。

    又出现问题 ,系统 挂了的时候,什么都没有了,什么 地方出现 bug 也不知道,又是 很尴尬。。

    还是 同事给力,提供了 bug 搜集 功能 的 工具,==该工具见最后的目录,可以在 Application 里面进行初始化操作==

    该工具 可以在 工具里面打断点,可以查看 问题点。

    该问题 报 在 我的 CustomerManagerActivity 类里面 ,dialog 展示 dialog.show()的时候 依附的 类找不到。 问题点的位置 在 Rxjava 和 Retrofit 的网络 框架里面的 onError 里面。

  2. 拿到问题后 就 分析一下 原因:

首先 我有 几个疑问?

1. 我在 返回的时候 ,关闭了 CustomerManagerActivity 这个类,对他进行 finish 操作 ,为什么后来 还会 调用 retrofit 的 网络请求 的  onError 方法, 毕竟 网络请求 是依附于 Acticity 的?
2. 我的 dialog  也是 依附于 我的 CustomerManagerActivity 这个类,为什么 他 这个对象还会 存在 ,不会回收?
3. 他们 两之间 有什么关系 呢? 都没有 回收?
4. 为什么 dialog .show 报错 ,而不是其他报错?
5. 如果网络请求 的方法中在 onsuccess 和 onComplated 方法中,其他地方会不会 也会报错?
6. ***为什么retrofit接口回调的方法中 接口回调完后,内存就释放了?***
  1. 有问题就 解决问题 ,和同事 讨论后 感觉 头脑清晰了 很多。

    1. 网络请求 肯定会 开线程去请求 ,只不过 结果可以 通过两种 形式返回 ,常见的是通过 handler 来进行 处理,还有就是 通过 接口回调 来 处理。 Rxjava 和 Retrofit 可以理解为 通过 接口 回调来处理内容。此时的 retrofit 对象为单例模式 ,可能其他地方也在用,没有 释放引用对象,所以该 对象还在。
    2. dialog 实在 activity 中初始化的,但是 其 在接口回调的 对象中被持有,所以 也是不会释放的。此时不会为空
    3. 正式由于 他们之间 有 持有 对象引用,该对象就不会 在 内存中释放,垃圾回收机制 RC 也不会回收。
    4. 因为 dialog ,popwindow 等是 依附于 内容提供者 Context ,在这里就是 Activity ,其是 一种浮层,在 Actictiy 上面展示,所以 在 初始化 dialog 等对象 的时候 需要传入 Context 上下文。如果 当前的Activity 销毁,其依附的对象 消失,就会报状态异常
      View=com.android.internal.policy.PhoneWindow$DecorView{..}not attached to window manager,系统就崩溃。
    5. 其他 如果不需要依附Context (acticity )的内容,不展示效果,其处理完后 就会释放内存。也就是还会走接口回调的方法。但是 走完就走完了。
    6. 糊涂点的 解释 是用完了之后 ,垃圾回收机制就会释放内存,因为内存不足。 稍微清醒些的解释是:
      由于 retrofit 持有 观察者和被观察者的 父类引用,其向下转型,
      当回调方法走完后,方法内部的 内容就会 被释放?。
  2. 发现问题 ,了解问题原因后就 剩下解决问题:

    1. dialog 释放,当 acticity finish 掉后,就会 将 dialog=null,释放堆内存。在 onError 中 对 dialog 判空,如果不为空,再进行展示。
    2. 同事的方案: 考虑 让 网页点击返回的时候, 网络请求 中断。(好像用shutdown ?) 网上有人说 ,在网络请求 父类中 对Context 判空,如果为空,则return ,这种可以应用在 非 单例模式 里面。
1 为什么 我接口 调用方法名 例如:a()后,就会 调用该 方法的方法体{} 里面的内容?

答: 因为 我 可以将 方法a() 理解为 一个属性 ,他 在 内存地址中是 持有 一个引用地址 ,这个 引用地址 又持有 方法体的 引用地址, 方法体中 持有 相应的对象的 引用。 所以 可以 调用方法就会 执行方法体的内容。

初步理解:

sequenceDiagram
方法a->>方法a的方法体: 持有方法a 方法体的引用
方法a的方法体->>方法a:  与方法a绑定
2. 为什么 我调用该方法体,会一步步执行 代码?

猜测是 计算机的底层机制,什么机制不清楚。。。 有响应链么?

3. 为什么 我在一个类里面 新建的类 或者单例 ,当依附的这个类 回收了之后 新建的类或者单例 可能还没有销毁?

答: 因为 当我 依附的这个类 回收之后 ,这个类 里面的属性 和 方法 的引用 地址会 置空,但是 在 其他地方 可能还会持有 该 类的引用,比如说 接口回调和 单例模式的时候。 也就是说 这个对象 垃圾回收机制是不会回收的,同样的 方法 没有释放,方法体 里面持有的 对象 也不会释放。

5. 延伸: 如果我在 子线程中 处理接口回调的逻辑,接口回调 会马上执行么?

答: 不会 。因为 cpu 在执行 线程队列的时候,是按顺序走的, 他会回调到子线程,但是 子线程什么时候 执行 就是有 线程队列 里面的顺序决定的。

4. 一些理解的内容:
    1. 类里面的 属性 和 方法 都可以理解 为 属性 ,其作为 一个 引用地址 存在 类 里面的集合里,当 用到的时候 去调用 就可以了。
    1. 面向过程和面向对象 怎么这个老生常谈,因为他很重要。面上理解 :面向对象是 对 面向过程的封装,也就是 将 过程封装。 再粗点理解 可以将 方法和方法体 理解为 一个对象—- 本来就是一个对象







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 infos = new HashMap();
//文件价名称
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 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());
}

}

“`

你可能感兴趣的:(android-网络)