首页 > 其他 > 详细

难解BUG记录

时间:2021-02-21 23:30:10      阅读:32      评论:0      收藏:0      [点我收藏+]

注:本博客不定期更新

  1. Window失去焦点,导致点击事件无法分发。进而导致点击界面无反应。操作方式是:点击进入下个 Activity 时,迅速按下电源键息屏(下个 Activity 刚走到 onCreate),然后再次打开手机,下个 Activity 就失去焦点了,所有点击事件都无效了(系统的回退键还能用)。
  2. 裁剪方式不一致,图片大小不一致。导致转场动画后的View显示闪动
  3. activity 主题不同,导致状态栏和虚拟导航栏未记录在内,导致的测量有误差,出现的 BUG
  4. 繁体的标点一般字体是默认居中的。并且引号不是 "",而是「」(或者『』)
  5. Dialog 和 Activity 的生命周期关系,以及焦点问题,导致的键盘隐藏问题(需要梳理源码)
  6. EditText 的焦点问题与系统软键盘的隐藏、显示
  7. 从当前聊天界面,进入另一个聊天界面(会话不同,Activity 是一样的),如果是走通知栏,并且是 SingleTask,则不会走 onCreate 方法。相关初始化操作需要放在 onNewIntent 中执行。
  8. 对于标准的 Android 系统的通知栏,通常左边(或者左上角)是通知栏的大图标,右边(或右下角)是通知栏的小图标。但是对于国内的厂商,通常定制了系统,通知栏的样式有所区别。比如华为手机的通知栏,图标是小图标。而小米手机的图标就是大图标。Android 原生系统的设计中,小图标默认不支持彩色(官方建议使用灰色),大图标随意。而国内的厂商,因为定制的原因,小图标可能也支持彩色,此时如果设置灰色的话,可能就会有用户投诉。这点应注意。同时,同一手机的不同系统版本,通知栏样式也有可能不同。这也是值得注意的一点,不太会引起 BUG,但是可能会有用户投诉。
  9. 系统不返回 UP 事件,在 Activity 的事件分发方法上加监听,发现的。
  10. SortedList 方法缺少如 contains, replace, removeAll 等一般列表都有的方法。导致集合的交集、并集、差集等的处理有付出额外的精力。
  11. 排查权限申请的不合规之处(合规调整)。发现百度的语音转文字 SDK,在不授予录音权限的情况下,无法将语音文件中的内容转成文字。经过日志分析,发现是在未授权的情况下,直接结束了转译的过程。遂明白百度 SDK 虽没有申请录音权限,但仍然有检查权限的操作。这个问题导致了用户反馈。经过讨论,确定了修改方案。下面先讲思路与原理。
  • 首先我们应该知道,Context 有检查权限的方法。其中最主要的就是 checkPermissioncheckSelfPermission。而我们检查权限,通常都会通过传入的 Context 检查。这就给了我们操作的空间。
  • 思路如下。创建一个假的 Context,命名为 BDFakeContext,继承自 ContextWrapper,从名称便可知,是专门用来处理百度的这个问题的。此处也建议专人专事。其他 SDK 有问题,也一样建个新类,而不是重复使用一个类。
  • 下面贴上完整代码:
public class FakeContext extends ContextWrapper {
    public FakeContext(Context base) {
        super(base);
    }

    @Override
    public Context getApplicationContext() {
        return this;
    }

    @Override
    public int checkPermission(String permission, int pid, int uid) {
        // 如果是检测音频权限,则不管有没有授权,直接返回已授权
        if(Manifest.permission.RECORD_AUDIO.equals(permission)) {
            return PackageManager.PERMISSION_GRANTED;
        }
        return super.checkPermission(permission, pid, uid);
    }
}

然后用上这个类:

public class BDManager {
    private BDSDKManager m;
    public BDManager(Context context) {
        // 传入 SDK 的地方包装一次,就是这么简单
        m = new BDSDKManager(new FakeContext(context));
    }
}
  • 但是,很不幸的是,没改好。反编译了源码,发现百度检查权限,调用的不是 checkPermission 方法,而是另一个 checkCallingOrSelfPermission 方法,这就好办了。依葫芦画瓢。
// 省略部分代码
 @Override
public int checkCallingOrSelfPermission(String permission) {
    // 如果是检测音频权限,则不管有没有授权,直接返回已授权
    if(Manifest.permission.RECORD_AUDIO.equals(permission)) {
        return PackageManager.PERMISSION_GRANTED;
    }
    return super.checkCallingOrSelfPermission(permission);
}

这次的结果就很 OK 了。又学到了一招,专门对付第三方 SDK 的权限检查。

  1. 列表布局,最好不要加入什么特殊的头部布局,因为指不定中间又要插入什么东西。单独添加头布局,不利于扩展。当然视情况而论,下拉刷新啥的是可以加的。
  2. 设想有个场景,有个 Activity A,如果某个外设操作,导致 Activity 之上,弹出了一个 dialog,此时没有 View 消费事件。那么事件会被 dialog 消费,而走不到 Activity A 那里,此时需要给 dialog 添加标志位,则可以让 dialog 不消费事件,事件继续下传。并且设置了以下代码之后,弹出 dialog,并不会引起 Activity 的焦点变化,即onWindowFocusChanged方法不会调用。代码如下:
getWindow().addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL);
  1. 对于不同尺寸的设备,不同屏幕大小的设备。要想图片缩放不失真,而又至少一边填充 ImageView,则可以用下面代码计算宽高,并给 ImageView 设置计算出来的宽高,然后在显示图片时给图片重设大小(不重设也行,ImageView 的裁剪方式可设置成 FIT_XY)。
private void resizeImageView(ImageView imageView, String path) {
    // 只解码图片大小,不解码图片数据
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeFile(path, options);
    // 屏幕宽高
    int screenWidth = ScreenUtil.getScreenWidth(imageView.getContext());
    int screenHeight = ScreenUtil.getScreenHeight(imageView.getContext());
    // 屏幕与图片的宽高比例
    float widthRatio = (float)screenWidth / options.outWidth;
    float heightRation = (float)screenHeight / options.outHeight;
    
    int finalWidth = options.outWidth;
    int finalHeight = options.outHeight;
    // 1. 如果图片小于屏幕尺寸,则取较小值
    if(widthRatio > 1 && heightRation > 1) {
        float minRatio = Math.min(widthRatio, heightRation);
        // 图片长宽扩大这个倍数
        finalWidth = (int)(options.outWidth * minRatio);
        finalHeight = (int)(options.outHeight * minRatio);
    } else if (widthRatio < 1 || heightRation < 1) {
        // 2. 如果图片大于屏幕尺寸,则取较大值
        float maxRatio = Math.max(widthRatio, heightRation);
        // 图片长宽缩小这个倍数
        finalWidth = (int)(options.outWidth / maxRatio);
        finalHeight = (int)(options.outHeight / maxRatio);
    }
    // 有相等的情况则不管,view 的尺寸,glide 的 resize,赋值finalWidth,finalHeight
    ViewGroup.LayoutParams layoutParams = imageView.getLayoutParams();
    layoutParams.width = finalWidth;
    layoutParams.height = finalHeight;
    imageView.setLayoutParams(layoutParams);
}
  1. 头条 SDK 引入后,可能会被检测出含有广告插件,引入需慎重。
  2. EditText 有焦点,并且键盘被隐藏,在退出界面重新进入时,会再次弹出焦点,可能会造成布局异常。Android 端微信在退出时,会清除 EditText 的焦点。可以参考这种做法。
  3. 现有一个语音按钮,功能是按钮区域内松开,即发送语音,区域外松开是取消发送。测试两个手指按下,一个在区域内,一个在区域外,松开区域内的手指,文本不更新。经查,是因为松开手指时,ACTION_POINTER_UP 仍然会带两个手指的信息,而区域内的手指如果 Index 是 0,则按照老的逻辑,判断 rawX,rawY 的位置,则手指仍在区域内,导致更新错误,解决办法是遍历所有按下的时候,判断按下的手指是否在区域内,代码如下:
case MotionEvent.ACTION_POINTER_UP:
    // 多指事件,只判断 rawX,rawY 不准确,判断所有手指的区域
    boolean isInRecordArea = false;
    for(int i = 0; i < motionEvent.getPointerCount(); i++) {
        if(motionEvent.getActionIndex() == i) {
            // 跳过抬起的手指的计算
            continue;
        }
        if(isInRecordArea(recordView, motionEvent.getX(i), motionEvent.getY(i))) {
            // 有手指在区域内,就够了
            isInRecordArea = true;
            break;
        }
    }
    return getRecordCallback().updateRecordState(isInRecordArea, DeviceType.DEFAULT);
  1. APP的混淆主要包括以下几个方面:

    • Android 系统的四大组件/View/自定义 View/Manifest文件不能被混淆,但 Android Studio 会帮我们处理,我们不需要单独配置。
    • 在反射中用到的类、方法、字段,不能被混淆
    • Native 方法必须和 JNI 中的方法同名,不能被混淆
    • 可以序列化的类型、方法、字段,不能被混淆
    • 枚举不能被混淆
    • WebView 和 JS 接口,不能混淆
    • 回调的相关监听类、方法、字段,建议不要混淆。
    • 资源文件不能被混淆
    • 注解、泛型不能混淆
    • 被打上 android.support.annotation.Keep 注解的内容,不能被混淆
    • 三方库指定的混淆规则
  2. canvas 画圆时,需要注意圆的实际半径是 radius + strokeWidth,即需要额外加上画笔的宽度。画任何图形,计算尺寸时,都需要考虑 画笔的宽度 是否有影响。

  3. Android 系统中,屏幕触摸事件和键盘按键事件是两个不同的事件流。前者是 MotionEvent,后者是 KeyEvent。如果前者的事件流还没有结束,就来了后者的事件,则中间会被插入一个 cancel 事件。即 MotionEvent ---> CancelEvent ---> KeyEvent。如果要交叉两个事件流,需要忽略掉 Cancel 事件(Cancel 事件无法判断事件源,只能忽略),这可能会导致其他很多的异常场景无法处理(比如三指按下后,系统下发了 Cancel 事件啥的,当然也与系统魔改有关)。需要注意。

  4. Android 文件系统的目录结构大致如下:
    技术分享图片

  5. Home 键虽然无法被onKeyDownonKeyUp监听到,但是可以通过广播知道 Home 键被按下了,代码如下:

private void initHomeKeyReceiver() {
    IntentFilter homeKeyFilter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);

    BroadcastReceiver homeKeyEventReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            judgeAndDealHomeKeyEvent(intent);
        }
    };

    registerReceiver(homeKeyEventReceiver, homeKeyFilter);
}

private void judgeAndDealHomeKeyEvent(Intent intent) {
    if(presenter.isCurrentConversationNull()) {
        return;
    }

    String action = intent.getAction();
    LogUtil.d(TAG, "action: " + action);
    if(!Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) {
        return;
    }

    String reason = intent.getStringExtra("reason");

    if(reason == null) {
        return;
    }
    if(reason.equals("homekey") // 点击 Home 键
       || reason.equals("recentapps") // 长按 Home 键
      ) {
        doSomething();
    }
}
  1. 当按键事件触发时(KeyEvent),如果此时按下 Home 键,则系统会自动触发按键的 UP 动作,即使按键并没有被松开。这是因为按下 Home 键之后,窗口的焦点发生变化,焦点变化导致事件触发,即 KeyEvent(Action up) ---> Home 键生效 ---> Activity onPause ---> Home 键广播生效 ---> Activity.onWindowFocusChanged() ---> Activity onStop。经过确认,有 UP 事件是因为 KeyEvent 没有 CANCEL 事件,所以失去焦点时,如果 KeyEvent 事件未结束,会发出 UP 事件,来结束掉 KeyEvent 事件流。这种情况下,系统工程师(Framework 层工程师)无法将 UP 事件替换成 CANCEL 事件(至少我求助的工程师不能)。
  2. ScrollView 嵌套 ListView 会导致 ListView 只显示 1 行数据。解决方法之一是将需要和 ListView 一起滚动的上方或者下方布局,作为 header/footer 添加到 ListView 中,而不是一起塞入 ScrollView 中。
  3. ListView 添加头布局时,会忽略 Margin,因为 ListView 的 LayoutParam 中并没有定义 margin,所以如果需要实现 Margin 效果,可以添加空白的 View 作为 Header,用来表示为 margin。或者在实际布局外再套一层布局(内层布局就可以加 margin 了)。
  4. ListView 的动画也是个大坑,建议不要用 ListView。
  5. Android 11(API 30) 读取网络状态(getDataNetworkType()/getNetworkType()),需要 READ_PHONE_STATE 权限。
  6. 自己应用的各种文件,最好存在自己的目录下,存在系统目录下,可能会导致系统系统判定你删除系统文件(如拼多多被 VIVO 警告擅自删除系统文件)。
  7. 应用的内部目录,会根据机型设备的不同而不同,并不一定是 "/data/data/包名" 目录。这个会根据系统支不支持多用户而变化,如果支持多用户,则可能不是这个目录,如果是单用户,可能厂商会内部处理,创建软连接,"/data/data/包名" 指向特定的目录。
  8. 一个线程占 1040KB(1 M),创建过多线程,可能导致 OOM。
  9. bindService 失败的一个原因是包名不一致。

难解BUG记录

原文:https://www.cnblogs.com/wellcherish/p/13502594.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!