Android 类似未读短信图标显示数字效果的分析

时间:2020-12-01 16:40:46

之前一直以为是应用本身在对图标进行修改,看了源码之后发现其实主要的工作并不是应用自己完成的,主要的工作在是launcher里面完成的.

关于系统里面类似未读短信的具体处理流程如下,
Android 类似未读短信图标显示数字效果的分析 原理
一个应用要实现这个效果,就要在自己有未读的消息的时候发送一个广播告诉系统我有未处理的事件了(例如:短信,电话和邮件等),同时将相关的信息进行保存,比如应用的名称(这里指的是ComponentName)和未处理事件的数量.系统将提升用户有待处理的事件交给Launcher去处理,launcher会接收到对应的广播,不是随便一个应用都有这个待遇的,launcher接到广播之后会先解析数据,看当前的应用有没有这个待遇也就是是否支持显示这个待处理事件的通知.如果通过launcher就会调用相关方法去重绘应用的icon.
效果图: Android 类似未读短信图标显示数字效果的分析 实现过程分析
Intent.java(系统增加3个常量)
frameworks/base/core/java/android/content/Intent.java
  1. //状态
  2. public static final String MTK_ACTION_UNREAD_CHANGED = "com.mediatek.action.UNREAD_CHANGED";
  3. //应用名称
  4. public static final String MTK_EXTRA_UNREAD_COMPONENT = "com.mediatek.intent.extra.UNREAD_COMPONENT";
  5. //数量
  6. public static final String MTK_EXTRA_UNREAD_NUMBER = "com.mediatek.intent.extra.UNREAD_NUMBER";
系统里面有这个功能几个应用相关的代码路径
日历 packages/apps/Calendar/src/com/mediatek/calendar/MTKUtils.java
邮件 :packages/apps/Email/src/com/android/email/NotificationController.java
电话 :packages/providers/ContactsProvider/src/com/android/providers/contacts/CallLogProvider.java 
短信 :packages/providers/TelephonyProvider/src/com/android/providers/telephony/MmsSmsProvider.java
 
这里以电话为例:
假设当前有人打电话进来,系统会保存改记录到数据库,根据事件是否已经处理来判断是否发送广播
 
CallLogProvider.java(发送有待处理事件的广播)
packages/providers/ContactsProvider/src/com/android/providers/contacts/CallLogProvider.java
  1. /** M: send new Calls broadcast to luancher to update unread icon @{ */
  2. public static final void notifyNewCallsCount(SQLiteDatabase db, Context context) {
  3. Cursor c = null;
  4. …..
  5. //send count=0 to clear the unread icon
  6. if (newCallsCount >= 0) { //有新的来电数量
  7. Intent newIntent = new Intent(Intent.MTK_ACTION_UNREAD_CHANGED);
  8. newIntent.putExtra(Intent.MTK_EXTRA_UNREAD_NUMBER, newCallsCount);
  9. newIntent.putExtra(Intent.MTK_EXTRA_UNREAD_COMPONENT, new ComponentName(Constants.CONTACTS_PACKAGE,
  10. Constants.CONTACTS_DIALTACTS_ACTIVITY));
  11. context.sendBroadcast(newIntent); //发送对应的广播
  12. android.provider.Settings.System.putInt(context.getContentResolver(), Constants.CONTACTS_UNREAD_KEY, Integer
  13. .valueOf(newCallsCount));
  14. }
  15. }

MTKUnreadLoader.java(Launcher接收到应用发送的广播,进行判断改应用是否可以显示有未处理事件的图标)

packages/apps/Launcher2/src/com/android/launcher2/MTKUnreadLoader.java
  1. public void onReceive(final Context context, final Intent intent) {
  2. final String action = intent.getAction();
  3. //过滤广播
  4. if (Intent.MTK_ACTION_UNREAD_CHANGED.equals(action)) {
  5. final ComponentName componentName = (ComponentName) intent.getExtra(Intent.MTK_EXTRA_UNREAD_COMPONENT);
  6. final int unreadNum = intent.getIntExtra(Intent.MTK_EXTRA_UNREAD_NUMBER, -1);
  7. if (mCallbacks != null && componentName != null && unreadNum != -1) {
  8. //判断是否支持该功能
  9. final int index = supportUnreadFeature(componentName);
  10. if (index >= 0) { //支持
  11. boolean ret = setUnreadNumberAt(index, unreadNum);
  12. if (ret) {
  13. final UnreadCallbacks callbacks = mCallbacks.get();
  14. if (callbacks != null) {
  15. callbacks.bindComponentUnreadChanged(componentName, unreadNum);
  16. }
  17. .........
  18. }

LauncherApplication.java(Launcher注册对应的广播接收器)

packages/apps/Launcher2/src/com/android/launcher2/LauncherApplication.java
  1. public void onCreate() {
  2. ........
  3. /// M: register unread broadcast.
  4. if (FeatureOption.MTK_LAUNCHER_UNREAD_SUPPORT) {
  5. mUnreadLoader = new MTKUnreadLoader(getApplicationContext());
  6. // Register unread change broadcast.
  7. filter = new IntentFilter();
  8. filter.addAction(Intent.MTK_ACTION_UNREAD_CHANGED);
  9. registerReceiver(mUnreadLoader, filter); //注册对应的广播接收器
  10. }
  11. ..............
  12. }

MTKUnreadLoader.java(处理应用的图标显示未处理事件的数字)

packages/apps/Launcher2/src/com/android/launcher2/MTKUnreadLoader.java
  1. static void drawUnreadEventIfNeed(Canvas canvas, View icon) {
  2. ItemInfo info = (ItemInfo)icon.getTag();
  3. if (info != null && info.unreadNum > 0) { //判断未处理事件数量
  4. Resources res = icon.getContext().getResources();
  5. ..........
  6. if (info.unreadNum > Launcher.MAX_UNREAD_COUNT) {
  7. unreadTextNumber = String.valueOf(Launcher.MAX_UNREAD_COUNT);
  8. unreadTextPlusPaint.getTextBounds(unreadTextPlus, 0, unreadTextPlus.length(), unreadTextPlusBounds);
  9. } else {
  10. unreadTextNumber = String.valueOf(info.unreadNum);
  11. }
  12. unreadTextNumberPaint.getTextBounds(unreadTextNumber, 0, unreadTextNumber.length(), unreadTextNumberBounds);
  13. int textHeight = unreadTextNumberBounds.height();
  14. int textWidth = unreadTextNumberBounds.width() + unreadTextPlusBounds.width();
  15. // 数字的背景图
  16. NinePatchDrawable unreadBgNinePatchDrawable = (NinePatchDrawable)res.getDrawable(R.drawable.ic_newevents_numberindication);
  17. .........
  18. Rect unreadBgBounds = new Rect(0, 0, unreadBgWidth, unreadBgHeight);
  19. unreadBgNinePatchDrawable.setBounds(unreadBgBounds);
  20. int unreadMarginTop = 0;
  21. int unreadMarginRight = 0;
  22. if (info instanceof ShortcutInfo) { //workspace 里面的快捷方式
  23. if (info.container == (long)LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
  24. unreadMarginTop = (int)res.getDimension(R.dimen.hotseat_unread_margin_top);
  25. unreadMarginRight = (int)res.getDimension(R.dimen.hotseat_unread_margin_right);
  26. } else if (info.container == (long)LauncherSettings.Favorites.CONTAINER_DESKTOP) {
  27. unreadMarginTop = (int)res.getDimension(R.dimen.workspace_unread_margin_top);
  28. unreadMarginRight = (int)res.getDimension(R.dimen.workspace_unread_margin_right);
  29. } else {
  30. unreadMarginTop = (int)res.getDimension(R.dimen.folder_unread_margin_top);
  31. unreadMarginRight = (int)res.getDimension(R.dimen.folder_unread_margin_right);
  32. }
  33. } else if (info instanceof FolderInfo) { //文件夹
  34. if (info.container == (long)LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
  35. unreadMarginTop = (int)res.getDimension(R.dimen.hotseat_unread_margin_top);
  36. unreadMarginRight = (int)res.getDimension(R.dimen.hotseat_unread_margin_right);
  37. } else if (info.container == (long)LauncherSettings.Favorites.CONTAINER_DESKTOP) {
  38. unreadMarginTop = (int)res.getDimension(R.dimen.workspace_unread_margin_top);
  39. unreadMarginRight = (int)res.getDimension(R.dimen.workspace_unread_margin_right);
  40. }
  41. }
  42. else if (info instanceof ApplicationInfo) { //all app 里面的应用icon
  43. unreadMarginTop = (int)res.getDimension(R.dimen.app_list_unread_margin_top);
  44. unreadMarginRight = (int)res.getDimension(R.dimen.app_list_unread_margin_right);
  45. }
  46. int unreadBgPosX = icon.getScrollX() + icon.getWidth() - unreadBgWidth - unreadMarginRight;
  47. int unreadBgPosY = icon.getScrollY() + unreadMarginTop;
  48. canvas.save();
  49. canvas.translate(unreadBgPosX, unreadBgPosY);
  50. unreadBgNinePatchDrawable.draw(canvas);
  51. /// M: Draw unread text.
  52. Paint.FontMetrics fontMetrics = unreadTextNumberPaint.getFontMetrics();
  53. if (info.unreadNum > Launcher.MAX_UNREAD_COUNT) {
  54. canvas.drawText(unreadTextNumber,
  55. (unreadBgWidth - unreadTextPlusBounds.width()) / 2,
  56. (unreadBgHeight + textHeight) / 2,
  57. unreadTextNumberPaint);
  58. canvas.drawText(unreadTextPlus,
  59. (unreadBgWidth + unreadTextNumberBounds.width()) / 2,
  60. (unreadBgHeight + textHeight) / 2 + fontMetrics.ascent / 2,
  61. unreadTextPlusPaint);
  62. } else {
  63. .....
  64. }
  65. }

unread_support_shortcuts.xml(配置哪些应用可以显示待处理的事件)

packages/apps/Launcher2/res/xml/unread_support_shortcuts.xml
Xml代码  Android 类似未读短信图标显示数字效果的分析
  1. <unreadshortcuts xmlns:launcher="http://schemas.android.com/apk/res/com.android.launcher">
  2. <!--电话-->
  3. <shortcut
  4. launcher:unreadPackageName="com.android.contacts"
  5. launcher:unreadClassName="com.android.contacts.activities.DialtactsActivity"
  6. launcher:unreadType="0"
  7. launcher:unreadKey="com_android_contacts_mtk_unread"
  8. />
  9. <!--短信-->
  10. <shortcut
  11. launcher:unreadPackageName="com.android.mms"
  12. launcher:unreadClassName="com.android.mms.ui.BootActivity"
  13. launcher:unreadType="0"
  14. launcher:unreadKey="com_android_mms_mtk_unread"
  15. />
  16. <!--邮件-->
  17. <shortcut
  18. launcher:unreadPackageName="com.android.email"
  19. launcher:unreadClassName="com.android.email.activity.Welcome"
  20. launcher:unreadType="0"
  21. launcher:unreadKey="com_android_email_mtk_unread"
  22. />
  23. ................
  24. </unreadshortcuts>