diff --git a/.github/ISSUE_TEMPLATE/issue_template_bug.md b/.github/ISSUE_TEMPLATE/issue_template_bug.md index 99aabc3..4e5d06d 100644 --- a/.github/ISSUE_TEMPLATE/issue_template_bug.md +++ b/.github/ISSUE_TEMPLATE/issue_template_bug.md @@ -8,33 +8,35 @@ assignees: getActivity ## 问题描述 -* 框架版本:XXX +* 框架版本【必填】:XXX -* 问题描述:XXX +* 问题描述【必填】:XXX -* 复现步骤:XXX +* 复现步骤【必填】:XXX -* 是否必现:填是/否 +* 是否必现【必填】:填是/否 -* 手机信息:例如某米 9 / Android 10 +* 出现问题的手机信息【必填】:请填写出现问题的品牌和机型 + +* 出现问题的安卓版本【必填】:请填写出现问题的 Android 版本 ## 请回答 -* 是部分机型还是所有机型都会出现:部分/全部(例如:某为,某 Android 版本会出现) +* 是部分机型还是所有机型都会出现【必答】:部分/全部(例如:某为,某 Android 版本会出现) -* 框架最新的版本是否存在这个问题:是/否(如果用的是旧版本的话,建议升级看问题是否还存在) +* 框架最新的版本是否存在这个问题【必答】:是/否(如果用的是旧版本的话,建议升级看问题是否还存在) -* 是否已经查阅框架文档还未能解决的:是/否(文档会提供最常见的问题解答,可以看看是否有自己想要的) +* 是否已经查阅框架文档还未能解决的【必答】:是/否(文档会提供最常见的问题解答,可以看看是否有自己想要的) -* issue 是否有人曾提过类似的问题:是/否(看看曾经有人提过类似的问题,先参考一下别人是怎么解决的) +* issue 是否有人曾提过类似的问题【必答】:是/否(看看曾经有人提过类似的问题,先参考一下别人是怎么解决的) -* 是否可以通过 Demo 来复现该问题:是/否(排查一下是不是自己的项目代码写得有问题导致的) +* 是否可以通过 Demo 来复现该问题【必答】:是/否(排查一下是不是自己的项目代码写得有问题导致的) -* 使用原生的 Toast 是否也会出现该问题:是/否(排查一下是不是框架的代码存在问题导致的) +* 使用原生的 Toast 是否也会出现该问题【必答】:是/否(排查一下是不是框架的代码存在问题导致的) ## 其他 -* 提供报错堆栈(如果有报错的话必填) +* 提供报错堆栈(如果有报错的话必填,注意不要拿被混淆过的代码堆栈上来) * 提供截图或视频(根据需要提供,此项不强制) diff --git a/.github/ISSUE_TEMPLATE/issue_template_suggest.md b/.github/ISSUE_TEMPLATE/issue_template_suggest.md index 9c343fc..4901b6a 100644 --- a/.github/ISSUE_TEMPLATE/issue_template_suggest.md +++ b/.github/ISSUE_TEMPLATE/issue_template_suggest.md @@ -8,8 +8,8 @@ assignees: getActivity ## 建议收集 -* issue 是否有人曾提过类似的问题?(必答项,一旦出现重复提问我将不会再次解答) +* issue 是否有人曾提过类似的问题?【必答】(一旦出现重复提问我将不会再次解答) -* 你觉得框架有什么不足之处?(必答项,你可以描述框架有什么令你不满意的地方) +* 你觉得框架有什么不足之处?【必答】(你可以描述框架有什么令你不满意的地方) -* 你觉得该怎么去完善会比较好?(非必答项,你可以提供一下自己的想法或者做法供作者参考) \ No newline at end of file +* 你觉得该怎么去完善会比较好?【非必答】(你可以提供一下自己的想法或者做法供作者参考) \ No newline at end of file diff --git a/README.md b/README.md index 0b9a4b6..09a58ad 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ * 博客地址:[只需体验三分钟,你就会跟我一样,爱上这款 Toast](https://www.jianshu.com/p/9b174ee2c571) -* 可以扫码下载 Demo 进行演示或者测试,如果扫码下载不了的,[点击此处可直接下载](https://github.com/getActivity/ToastUtils/releases/download/10.3/ToastUtils.apk) +* 可以扫码下载 Demo 进行演示或者测试,如果扫码下载不了的,[点击此处可直接下载](https://github.com/getActivity/ToastUtils/releases/download/10.5/ToastUtils.apk) ![](picture/demo_code.png) @@ -47,7 +47,7 @@ android { dependencies { // 吐司框架:https://github.com/getActivity/ToastUtils - implementation 'com.github.getActivity:ToastUtils:10.3' + implementation 'com.github.getActivity:ToastUtils:10.5' } ``` @@ -140,9 +140,9 @@ ToastUtils.init(this, new ToastStrategy() { | 功能或细节 | [ToastUtils](https://github.com/getActivity/ToastUtils) | [AndroidUtilCode](https://github.com/Blankj/AndroidUtilCode) | [Toasty](https://github.com/GrenderG/Toasty) | | :----: | :------: | :-----: | :-----: | -| 对应版本 | 10.3 | 1.30.6 | 1.5.0 | +| 对应版本 | 10.5 | 1.30.6 | 1.5.0 | | issues 数 | [![](https://img.shields.io/github/issues/getActivity/ToastUtils.svg)](https://github.com/getActivity/ToastUtils/issues) | [![](https://img.shields.io/github/issues/Blankj/AndroidUtilCode.svg)](https://github.com/Blankj/AndroidUtilCode/issues) | [![](https://img.shields.io/github/issues/GrenderG/Toasty.svg)](https://github.com/GrenderG/Toasty/issues) | -| **aar 包大小** | 28 KB | 500 KB | 50 KB | +| **aar 包大小** | 29 KB | 500 KB | 50 KB | | **调用代码定位** | ✅ | ❌ | ❌ | | 支持在子线程中调用显示 | ✅ | ✅ | ❌ | | 支持全局设置统一 Toast 样式 | ✅ | ❌ | ❌ | @@ -158,13 +158,15 @@ ToastUtils.init(this, new ToastStrategy() { #### Toast 在 Android 7.1 崩溃的问题介绍 +> [Toast 在 Android 7.1 崩溃排查及修复](https://www.jianshu.com/p/437f473017d6) + * 这个问题是由于 Android 7.1 加入 WindowToken 校验机制导致的,而这个 WindowToken 是 NotificationManagerService 生成的,这个 WindowToken 是存在一定时效性的,而当应用的主线程被阻塞时,WindowManager 在 addView 时会对 WindowToken 进行校验,但是 WindowToken 已经过期了,这个时候 addView 就会抛出异常。 -* 谷歌在 Android 8.0 就修复了这个问题,修复方式十分简单粗暴,就是直接捕获这个异常,而框架的修复思路跟谷歌类似,只不过修复方式不太一样,因为框架无法直接修改系统源码,所以是直接通过 Hook 的方式对异常进行捕获,大家如果对修复过程感兴趣可以看一下我写的这篇文章[Toast 在 Android 7.1 崩溃排查及修复](https://www.jianshu.com/p/437f473017d6)。 +* 谷歌在 Android 8.0 就修复了这个问题,修复方式十分简单粗暴,就是直接捕获这个异常,而框架的修复思路跟谷歌类似,只不过修复方式不太一样,因为框架无法直接修改系统源码,所以是直接通过 Hook 的方式对异常进行捕获。 #### 通知栏权限关闭后 Toast 显示不出来的问题介绍 -* [Toast通知栏权限填坑指南](https://www.jianshu.com/p/1d64a5ccbc7c) +> [Toast通知栏权限填坑指南](https://www.jianshu.com/p/1d64a5ccbc7c) * 这个问题的出现是因为原生 Toast 的显示要通过 NMS(NotificationManagerService) 才会 addView 到 Window 上面,而在 NMS 中有一个 `static final boolean ENABLE_BLOCKED_TOASTS = true` 的字段,当这个常量值为 true 时,会触发 NMS 对应用通知栏权限的检查,如果没有通知栏权限,那么这个 Toast 将会被 NMS 所拦截,并输出 `Suppressing toast from package` 日志信息,而小米手机没有这个问题是因为它是将 `ENABLE_BLOCKED_TOASTS` 字段值修改成 `false`,所以就不会触发对通知栏权限的检查,另外我为什么会知道有这个事情?因为我曾经和一名 MIUI 工程师一起确认过这个事情。 @@ -186,7 +188,7 @@ ToastUtils.init(this, new ToastStrategy() { * 兼容性强:[处理原生 Toast 在 Android 7.1 产生崩溃的历史遗留问题](https://www.jianshu.com/p/437f473017d6) -* 功能强大:不分主次线程都可以弹出Toast,自动区分资源 id 和 int 类型 +* 功能强大:不分主次线程都可以弹出Toast,自动识别资源 id 和 int 类型 * 使用简单:只需传入文本,会自动根据文本长度决定吐司显示的时长 @@ -201,7 +203,7 @@ ToastUtils.init(this, new ToastStrategy() { * 在项目中右击弹出菜单,Replace in path,勾选 Regex 选项,点击替换 ```text -Toast\.makeText\([^,]+,\s*(.+{1}),\s*[^,]+\)\.show\(\) +Toast\.makeText\([^,]+,\s*(.+),\s*[^,]+\)\.show\(\) ``` ```text @@ -251,6 +253,8 @@ new Toast * Android 代码规范:[AndroidCodeStandard](https://github.com/getActivity/AndroidCodeStandard) ![](https://img.shields.io/github/stars/getActivity/AndroidCodeStandard.svg) ![](https://img.shields.io/github/forks/getActivity/AndroidCodeStandard.svg) +* Android 开源排行榜:[AndroidGithubBoss](https://github.com/getActivity/AndroidGithubBoss) ![](https://img.shields.io/github/stars/getActivity/AndroidGithubBoss.svg) ![](https://img.shields.io/github/forks/getActivity/AndroidGithubBoss.svg) + * Studio 精品插件:[StudioPlugins](https://github.com/getActivity/StudioPlugins) ![](https://img.shields.io/github/stars/getActivity/StudioPlugins.svg) ![](https://img.shields.io/github/forks/getActivity/StudioPlugins.svg) * 表情包大集合:[EmojiPackage](https://github.com/getActivity/EmojiPackage) ![](https://img.shields.io/github/stars/getActivity/EmojiPackage.svg) ![](https://img.shields.io/github/forks/getActivity/EmojiPackage.svg) @@ -261,7 +265,7 @@ new Toast ![](https://raw.githubusercontent.com/getActivity/Donate/master/picture/official_ccount.png) -#### Android 技术分享 QQ 群:78797078 +#### Android 技术 Q 群:10047167 #### 如果您觉得我的开源库帮你节省了大量的开发时间,请扫描下方的二维码随意打赏,要是能打赏个 10.24 :monkey_face:就太:thumbsup:了。您的支持将鼓励我继续创作:octocat: diff --git a/app/build.gradle b/app/build.gradle index a57d691..5ab94b7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -7,8 +7,8 @@ android { applicationId "com.hjq.toast.demo" minSdkVersion 16 targetSdkVersion 31 - versionCode 1030 - versionName "10.3" + versionCode 1050 + versionName "10.5" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } @@ -65,7 +65,7 @@ dependencies { implementation 'com.github.getActivity:TitleBar:9.3' // 权限请求框架:https://github.com/getActivity/XXPermissions - implementation 'com.github.getActivity:XXPermissions:13.2' + implementation 'com.github.getActivity:XXPermissions:13.6' // 悬浮窗框架:https://github.com/getActivity/XToast implementation 'com.github.getActivity:XToast:8.5' diff --git a/app/src/main/java/com/hjq/toast/demo/MainActivity.java b/app/src/main/java/com/hjq/toast/demo/MainActivity.java index f83063d..f219571 100644 --- a/app/src/main/java/com/hjq/toast/demo/MainActivity.java +++ b/app/src/main/java/com/hjq/toast/demo/MainActivity.java @@ -80,7 +80,7 @@ public void show6(View v) { } public void show7(View v) { - Snackbar.make(getWindow().getDecorView(), "正在准备跳转到手机桌面,请系好安全带", Snackbar.LENGTH_SHORT).show(); + Snackbar.make(getWindow().getDecorView(), "温馨提示:安卓 10 在后台显示 Toast 需要有通知栏权限或者悬浮窗权限的情况下才可以显示", Snackbar.LENGTH_SHORT).show(); v.postDelayed(new Runnable() { @Override @@ -98,7 +98,7 @@ public void run() { if (XXPermissions.isGranted(MainActivity.this, Permission.SYSTEM_ALERT_WINDOW)) { ToastUtils.show("我是在后台显示的 Toast(有悬浮窗权限真的可以为所欲为)"); } else { - ToastUtils.show("我是在后台显示的 Toast(Android 11 及以上在后台显示 Toast 只能使用系统样式)"); + ToastUtils.show("我是在后台显示的 Toast(安卓 11 及以上在后台显示只能使用系统样式)"); } } else { ToastUtils.show("我是在后台显示的 Toast"); diff --git a/library/build.gradle b/library/build.gradle index 7ad9dd1..f8784c2 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -5,8 +5,8 @@ android { defaultConfig { minSdkVersion 14 - versionCode 1030 - versionName "10.3" + versionCode 1050 + versionName "10.5" } // 使用 JDK 1.8 diff --git a/library/src/main/java/com/hjq/toast/ToastImpl.java b/library/src/main/java/com/hjq/toast/ToastImpl.java index 5078e02..271ff5f 100644 --- a/library/src/main/java/com/hjq/toast/ToastImpl.java +++ b/library/src/main/java/com/hjq/toast/ToastImpl.java @@ -1,5 +1,6 @@ package com.hjq.toast; +import android.annotation.SuppressLint; import android.app.Activity; import android.app.Application; import android.content.Context; @@ -7,7 +8,10 @@ import android.os.Build; import android.os.Handler; import android.os.Looper; +import android.view.View; import android.view.WindowManager; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; import android.widget.Toast; /** @@ -98,8 +102,28 @@ private boolean isMainThread() { return Looper.myLooper() == Looper.getMainLooper(); } + /** + * 发送无障碍事件 + */ + private void trySendAccessibilityEvent(View view) { + final Context context = view.getContext(); + AccessibilityManager accessibilityManager = + (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE); + if (!accessibilityManager.isEnabled()) { + return; + } + // 将 Toast 视为通知,因为它们用于向用户宣布短暂的信息 + AccessibilityEvent event = AccessibilityEvent.obtain( + AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED); + event.setClassName(Toast.class.getName()); + event.setPackageName(context.getPackageName()); + view.dispatchPopulateAccessibilityEvent(event); + accessibilityManager.sendAccessibilityEvent(event); + } + private final Runnable mShowRunnable = new Runnable() { + @SuppressLint("WrongConstant") @Override public void run() { @@ -142,7 +166,8 @@ public void run() { mWindowLifecycle.register(ToastImpl.this); // 当前已经显示 setShow(true); - + // 发送无障碍事件 + trySendAccessibilityEvent(mToast.getView()); } catch (IllegalStateException | WindowManager.BadTokenException e) { // 如果这个 View 对象被重复添加到 WindowManager 则会抛出异常 // java.lang.IllegalStateException: View android.widget.TextView has already been added to the window manager. diff --git a/library/src/main/java/com/hjq/toast/ToastStrategy.java b/library/src/main/java/com/hjq/toast/ToastStrategy.java index fbe626a..0a90c92 100644 --- a/library/src/main/java/com/hjq/toast/ToastStrategy.java +++ b/library/src/main/java/com/hjq/toast/ToastStrategy.java @@ -1,5 +1,6 @@ package com.hjq.toast; +import android.annotation.SuppressLint; import android.app.Activity; import android.app.AppOpsManager; import android.app.Application; @@ -65,12 +66,13 @@ public void bindStyle(IToastStyle style) { public IToast createToast(Application application) { Activity foregroundActivity = mActivityStack.getForegroundActivity(); IToast toast; - if (foregroundActivity != null) { - toast = new ActivityToast(foregroundActivity); - } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && Settings.canDrawOverlays(application)) { // 如果有悬浮窗权限,就开启全局的 Toast toast = new WindowToast(application); + } else if (foregroundActivity != null) { + // 如果没有悬浮窗权限,就开启一个依附于 Activity 的 Toast + toast = new ActivityToast(foregroundActivity); } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.N_MR1) { // 处理 Android 7.1 上 Toast 在主线程被阻塞后会导致报错的问题 toast = new SafeToast(application); @@ -105,7 +107,7 @@ public void showToast(CharSequence text, long delayMillis) { mLatestText = text; HANDLER.removeCallbacks(mShowRunnable); // 延迟一段时间之后再执行,因为在没有通知栏权限的情况下,Toast 只能显示当前 Activity - // 如果当前 Activity 在 ToastUtils.show 之后进行 finish 了,那么这个时候 Toast 可能会显示不出来 + // 如果当前 Activity 在 showToast 之后立马进行 finish 了,那么这个时候 Toast 可能会显示不出来 // 因为 Toast 会显示在销毁 Activity 界面上,而不会显示在新跳转的 Activity 上面 HANDLER.postDelayed(mShowRunnable, delayMillis + DELAY_TIMEOUT); } @@ -173,6 +175,7 @@ protected int getToastDuration(CharSequence text) { * 是否有通知栏权限 */ @SuppressWarnings("ConstantConditions") + @SuppressLint("PrivateApi") protected boolean areNotificationsEnabled(Context context) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { return context.getSystemService(NotificationManager.class).areNotificationsEnabled(); diff --git a/library/src/main/java/com/hjq/toast/style/BlackToastStyle.java b/library/src/main/java/com/hjq/toast/style/BlackToastStyle.java index fed2848..bedadc5 100644 --- a/library/src/main/java/com/hjq/toast/style/BlackToastStyle.java +++ b/library/src/main/java/com/hjq/toast/style/BlackToastStyle.java @@ -40,12 +40,12 @@ public TextView createView(Context context) { textView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); - Drawable background = getBackgroundDrawable(context); + Drawable backgroundDrawable = getBackgroundDrawable(context); // 设置背景 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - textView.setBackground(background); + textView.setBackground(backgroundDrawable); } else { - textView.setBackgroundDrawable(background); + textView.setBackgroundDrawable(backgroundDrawable); } // 设置 Z 轴阴影 @@ -53,8 +53,6 @@ public TextView createView(Context context) { textView.setZ(getTranslationZ(context)); } - // 设置最大显示行数 - textView.setMaxLines(getMaxLines(context)); return textView; } @@ -90,8 +88,4 @@ protected Drawable getBackgroundDrawable(Context context) { protected float getTranslationZ(Context context) { return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3, context.getResources().getDisplayMetrics()); } - - protected int getMaxLines(Context context) { - return 5; - } } \ No newline at end of file