来自 广播 2019-12-25 12:54 的文章
当前位置: AG真人游戏平台 > 广播 > 正文

他突然想起来他说过的这句话,Launcher会对快捷方式进行修改

第一章:逃亡

2023年9月8日x国机场

他隐在熙熙攘攘的人群中静静的看着检票登机口,这也许是最后一次见面了。“各位旅客请注意,您乘坐的飞往北京的CA8136次航班现在开始登机,请您从22号登机口上飞机。 ”机场的广播响起,人们变得骚动起来,他敏锐的目光搜索着人群,他希望她会来,他也希望她不要来。

广播声音响起,她慢慢的跟随着人群朝登机口走去。她知道他一定在人群里但是却不会再跟她见面。一点一滴的过往又涌上脑海,她走得很慢很慢,静静地品味着他曾带给她的喜怒哀乐。

如果你曾爱过一个人,那些有他的过去总会让你狼狈不堪,因为对于你来说,他走了那些便是一切。

“小姐您好,请出示您的机票。”不知不觉她还是走到了检票口,这段不是很长的路她走的很疲惫。“还是要离开了,他说过要来送我的,一定要这样吗?”她回头对着人群挥了挥手。她知道,他一定在某一个角落默默地看着她,这就算是最后的告别吧。

他看着她对着人群默默的挥手,他看着她把手中的机票递给检票员,他看着她拿着行李消失在登机口。她走了,可是他却只能静静的看着。“雪儿,如果有一天我哭了,那一定是因为你。”他突然想起来他说过的这句话,苦涩的泪水似乎总会在分别的时候落下。我原来很爱你,我现在也很爱你,可有些爱只能藏在心里。故人已不在,留下也只是徒增伤感,他整了整衣领,消失在人群……

有些故事从刚开始就是错误,如果最初两不相扰,是否最后就能两不相欠。

2022年6月15日  x国边境

“轰隆隆……”一阵刺耳的雷声过后,大雨倾盆而至。“查普!我让你看着的那个国际刑警呢?”

“老大,我……他明明刚才还在这里的。我找了两个兄弟在看着他呢!”一个瘦高的亚洲人吞吞吐吐的回答着。

“看着?你自己看看,哪还有人!赶紧去给我找,如果让他逃回华夏,他手里的那些文件足够我们死几百次了!不把他抓回来,你也不用回来了!”瘦高的男人旁边那个满身肌肉,脸上布满刀疤的黑人愤怒的说到。

“是,老大。你放心,他身上都是伤,外面还下着这么大的雨,他跑不远的!”瘦高的男人赶紧答应到,他紧紧的握着双拳,好像这样才能为他冲淡一点恐惧。

“滚!赶紧把他给我带回来!”“轰隆隆……”“是……是,是。走,兄弟们!”窗外的惊雷好像吓到了那个瘦高的男人,他颤颤巍巍的答到。

雨林深处,他忍着满身的伤痛,匆匆的赶走路。终于趁他们不备逃了出来。5年了,他在x国卧底了五年,这些年他收集了“查咔”贩毒集团的所有交易记录。罪人终有一天会接受制裁,财富背后的犯罪也总有一天会大白于天下。他一定要想办法把这些文件送回华夏!

“兄弟们,赶紧找。那小子一定没跑远,谁先找着他,我赏谁一块金子!居然敢跑,抓回来我一定废了他!”远处,嘈杂的叫嚣声进入他的耳朵。“是查普!”五年的时间,他早已把这里所有的人都看了透彻。查普这个人,阴险狡诈,两面三刀,贪财图利,而且极会笼络人心。他总有一天会杀了那个一身横肉的刀疤脸,取代他的位置……

声音越来越近,他不敢有片刻的停留,尽最大的力气跑着。身上的伤口因为剧烈的运动又撕裂了,雨水夹杂着血水浸透了他的衣服。他的身体不自主的颤抖着,长时间滴水未进,遍布伤痕的身体早就精疲力尽了。只是那一点点的信念还在让他坚持着。人最大的能力就是逼迫自己,死亡也许并不可怕,可怕的是一直在心中残留的那份执念。

“轰隆隆……”惊雷又在耳边炸响。x国临近赤道,属于热带雨林气候,这里全年高温多雨,四季不分明。潮湿,高温,多雨。尽管他的身上已经全部湿透了,可是身体里的燥热却始终压不下去。伤口处好像趴着几千只嗜血的蚂蚁,痛痒感时刻席卷着他的每一根神经。如果不是经历过那些专业的忍耐力训练,他可能早就抵挡不住想要昏倒的欲望了。雨水太大,阻了他的视线。他不得不一次次的擦去脸上的雨水。他早就不确定自己走的方向了,只是心中一直有一个声音总是在他想要放弃的时候响起“你要为了自己好好活着!”

“噗通……”他好像跌进了一个湖里,他太累了,湖水很暖,那种感觉很舒服。他的意识越来越模糊,都说人最后的那个时刻,会回想到自己的一生。可是他的一生,好像真的没有什么值得回味的,18岁那年参军,因为身体素质比较好通过了考核做了特种兵,后来机缘巧合成为了国际刑警,到今年26岁,8年的时间都交给了国家。军人也许就是这么可悲,他们付出了自己的一切却没有人记得他们是谁。战场上鲜血淋漓,伤痕累累的是他们,我们这些听故事的人永远不痛不痒,不闻不问。

  “咦……她,是谁?”脑海深处突然出现一个姑娘,很模糊的一张脸,他记不起这是属于谁的花容月貌。18岁以前是什么样子,他好像很久都没有去回忆了……

图【网络】

【都市】寄梦(02)

【都市】寄梦(03)

【都市】寄梦(04)

这篇文章算是对【译】对RxJava中.repeatWhen()和.retryWhen()操作符的思考的一个简单的补充,建议没看过的先看看上面这篇。

Badge分析

所谓Badge,原本是iOS上的一个效果,但是被Android抄的多了,也就成了Android的标配。图就不上了,大家都懂的。

应用icon显示角标实际上是在Launcher中实现的,其实不管是角标还是其他对快捷方式的增删改查,都是需要Launcher支持的,应用在增删改查快捷方式和安装、卸载时,都会发出相应的广播,通过这个广播,Launcher会对快捷方式进行修改。

很庆幸,Android原生ROM的Launcher并不具有给icon添加角标的功能,因为Android的设计思想是把所有消息中心都放置在Notification通知栏中,只有iOS这种通知栏半残废的,才会使用角标。这玩意儿,让强迫症患者,完全不能自理,每日陷落在清除小红点的生活中。

很悲剧,Android的AOSP代码被国内各大ROM厂商改的不能自理。很多被修改的ROM都可以支持这种角标的功能,甚至是很多第三方Launcher,也提供了这种功能。其基本原理也是天下一大抄,都是监听发出的广播来进行快捷方式的修改,但是,关键是没有Google亲爹的支持,所有的实现都不统一,大家自己做自己的,没有统一的接口,导致各种碎片化非常严重。

现在原理很清晰了,关键就是要尽可能多的找到这些ROM、Launcher的修改icon的广播。

在调查该问题时,我找到了https://github.com/leolin310148/ShortcutBadger 这个库,很多地方参考了这个库,但是该库由于很久没有维护了,所以我提取了里面收集的一些Badge的方法,并做了完善,这里对作者表示感谢。

前言

才学RxJava的时候还是挺困惑的,感觉有特别多的对『时间』的操作符,比如timer()interval()delay()defer()等等……
总之功能太强大,直接吓跑了一群初学者。身边有朋友这样跟我说:

RxJava为了省点代码,把逻辑弄这么复杂,看着都晕了,我宁愿多写点 if-else

我只能回复:用RxJava逻辑肯定是变简单了,一旦用上手了,再也离不开了。现在让我写个Thread + Handler我都觉得麻烦。

各种ROM角标分析

正题

先看timer()interval()delay()的小球图吧

delay

interval

timer

MIUI6&7 Badge

以下内容来自MUI开发者平台:

一、基本介绍

1、默认的情况

当app 向通知栏发送了一条通知 (通知不带进度条并且用户可以删除的),那么桌面app icon角标就会显示1.此时app显示的角标数是和通知栏里app发送的通知数对应的,即向通知栏发送了多少通知就会显示多少角标。

2、通知可以定义角标数

例如 有5封未读邮件,通知栏里只会显示一条通知,但是想让角标显示5. 可以在发通知时加个标示。

修改MIUI的原理是通过反射拿到Notification的私有属性extraNotification,但是这个extraNotification在MIUI系统中重定义了,这个类就是MIUI系统中的android.app.MiuiNotification这个类,这个类里面有个私有属性messageCount,我们只要改变这个messageCount值就能显示的改变app icon的角标数了。

二、实现代码

第三方app需要用反射来调用,参考代码:

/**
 * 设置MIUI的Badge
 *
 * @param context context
 * @param count   count
 */
private static void setBadgeOfMIUI(Context context, int count) {
    Log.d("xys", "Launcher : MIUI");
    NotificationManager mNotificationManager = (NotificationManager) context
            .getSystemService(Context.NOTIFICATION_SERVICE);
    Notification.Builder builder = new Notification.Builder(context)
            .setContentTitle("title").setContentText("text").setSmallIcon(R.mipmap.ic_launcher);
    Notification notification = builder.build();
    try {
        Field field = notification.getClass().getDeclaredField("extraNotification");
        Object extraNotification = field.get(notification);
        Method method = extraNotification.getClass().getDeclaredMethod("setMessageCount", int.class);
        method.invoke(extraNotification, count);
    } catch (Exception e) {
        e.printStackTrace();
    }
    mNotificationManager.notify(0, notification);
}

timer()

这里说的是新版本的timer(),而老版本的timer()已经跟interval()合并了。
timer():创建一个Observable,它在一个给定的延迟后发射一个特殊的值
这里需要注意,定义里面说的是『一个』,所以有别于之前用的TimerTasktimer()只是用来创建一个Observable,并延迟发送一次的操作符timer()并不会按周期执行。

Sony Badge

https://forsberg.ax/en/blog/android-notification-badge-app-icon-sony/

interval()

interval():创建一个按固定时间间隔发射整数序列的Observable
这个比较好理解,interval()也是用来创建Observable的,并且也可以延迟发送。但interval()是按周期执行的,所以可以这么认为:interval()是一个可以指定线程的TimerTask(威力加强版……)

Samsung Badge

delay()

delay():延迟一段指定的时间再发送来自Observable的发送结果
语文没学好肯定读不懂这一段,我才看到这句话的时候也懵了……
其实delay()的常规使用跟timer()一致,那区别在哪呢?delay()是用于流中的操作,跟map()flatMap()的级别是一样的。而timer()是用于创建Observable,跟just()from()的级别是一样的。

方法一

通过三星Launcher自己的广播,来给应用添加角标:

/**
 * 设置三星的Badge
 *
 * @param context context
 * @param count   count
 */
private static void setBadgeOfSumsung(Context context, int count) {
    // 获取你当前的应用
    String launcherClassName = getLauncherClassName(context);
    if (launcherClassName == null) {
        return;
    }
    Intent intent = new Intent("android.intent.action.BADGE_COUNT_UPDATE");
    intent.putExtra("badge_count", count);
    intent.putExtra("badge_count_package_name", context.getPackageName());
    intent.putExtra("badge_count_class_name", launcherClassName);
    context.sendBroadcast(intent);
}

此方法不需要任何权限,只需要知道App的包名和类名。因此,你当然可以在程序里面给其它任意一个App设置任意数量的角标,而且没有任何提示,是的,很流氓,谁说不是呢,当然别说是我告诉你的,你就所你是百度的。例如:

intent.putExtra("badge_count_package_name", "com.tencent.mobileqq");
    intent.putExtra("badge_count_class_name", "com.tencent.mobileqq.activity.SplashActivity");

将包名和类名用QQ的替换下,然后你就可以随心所欲、为所欲为了。

总结

timer():用于创建Observable,延迟发送一次。
interval():用于创建Observable,跟TimerTask类似,用于周期性发送。
delay():用于事件流中,可以延迟发送事件流中的某一次发送。

方法二

https://github.com/shafty023/SamsungBadger

网络连接失败的处理

看过最前面那篇文章的应该很清楚retryWhen()是什么了。
我再来总结一下,retryWhen()的直面意思就是:发生错误了,接下来该做什么。
retryWhen()RxJava的一种错误处理机制,当遇到错误时,将错误传递给另一个Observable来决定是否要重新给订阅这个Observable

LG Badge

Samsung好基友,三星能用的,LG几乎都可以用,连Bug都一样。

延迟重试

来想象一个场景:用户用的2G网络或者WiFi信号不稳定,导致网络经常连接失败,其实这个时候只要多努力一下就可以连接成功了,如果此时弹出错误提示,体验肯定不好,所以这里就要用到重试机制

.retryWhen(new Func1<Observable<? extends Throwable>, Observable<?>>() {
                @Override
                public Observable<?> call(Observable<? extends Throwable> observable) {
                    return observable.flatMap(new Func1<Throwable, Observable<?>>() {
                        @Override
                        public Observable<?> call(Throwable throwable) {
                            return Observable.timer(5, TimeUnit.SECONDS);
                        }
                    });
                }

这里就是一个最简单的错误重试机制(别看字母多就复杂,其实逻辑只有两句,用lambda表达式的知道我不是乱说…),如果网络连接失败那么会每隔5秒进行一次重试,直到连接成功为止。
刚刚我说了timer()delay()很像,只是用的时机不同,所以上面的代码最后的return部分还可以这样写:

return Observable.just(throwable).delay(5, TimeUnit.SECONDS);

华为EMUI Badge

目前华为的ROM只支持给内置的App添加角标,华为本身没有给出相应的接口。

判断错误类型

再模拟一个场景:用户不是手机信号不好,而是根本就没打开网络,此时还傻傻的重试只是浪费电量而已,所以我们可以加一个判断,打开了网络才重试,没有打开网络就继续发送失败的消息。

.retryWhen(new Func1<Observable<? extends Throwable>, Observable<?>>() {
                @Override
                public Observable<?> call(Observable<? extends Throwable> observable) {
                    return observable.flatMap(new Func1<Throwable, Observable<?>>() {
                        @Override
                        public Observable<?> call(Throwable throwable) {
                            if (throwable instanceof UnknownHostException) {
                                return Observable.error(throwable);
                            }
                            return Observable.timer(5, TimeUnit.SECONDS);
                        }
                    });

酷派 Badge

简单粗暴,不支持。我喜欢,类原生。

加入重试超时

继续想,重试也不可能永远进行,一般都会设置一个重试超时的机制。这里我借用了上面那篇文章的代码:

.retryWhen(new Func1<Observable<? extends Throwable>, Observable<?>>() {
                @Override
                public Observable<?> call(Observable<? extends Throwable> observable) {
                    return observable.flatMap(new Func1<Throwable, Observable<?>>() {
                        @Override
                        public Observable<?> call(Throwable throwable) {
                            if (throwable instanceof UnknownHostException) {
                                return Observable.error(throwable);
                            }
                            return Observable.just(throwable).zipWith(Observable.range(1, 5), new Func2<Throwable, Integer, Integer>() {
                                @Override
                                public Integer call(Throwable throwable, Integer i) {

                                    return i;
                                }
                            }).flatMap(new Func1<Integer, Observable<? extends Long>>() {
                                @Override
                                public Observable<? extends Long> call(Integer retryCount) {

                                    return Observable.timer((long) Math.pow(5, retryCount), TimeUnit.SECONDS);
                                }
                            });
                        }
                    });
                }

代码变得越来越多和难读了……试一下lambda表达式看看:

.retryWhen(observable -> observable.flatMap((Throwable throwable) -> {
                if (throwable instanceof UnknownHostException) {
                    return Observable.error(throwable);
                }
                return Observable.just(throwable)
                        .zipWith(Observable.range(1, 5), (throwable1, i) -> i)
                        .flatMap(retryCount -> Observable
                                .timer((long) Math.pow(5, retryCount), TimeUnit.SECONDS));
            }));

这样清爽多了,不过还是先用上面那种吧,毕竟更多人还没用到lambda

ZUK ZUI Badge

ZUK作为一个非常小众的手机厂商,居然在网上官方给出了详细的开发者文档,就这一点,很多大厂都该好好打打自己的耳光。

由于实在找不到ZUK的测试机,所以这里给出ZUK的开发者文档,有需要的自己看看吧:

http://developer.zuk.com/detail/12

重试的复用

想一下,没有哪个APP只有一个接口地址吧 - -#,如果你用的Retrofit那么每一个接口返回的Observable都要手动加上上面的重试代码,如果是我,我肯定报警了……所以我们必须把刚刚写的重试代码封装成一个类:

public class RetryWhenProcess implements Func1<Observable<? extends Throwable>, Observable<?>> {

private long mInterval;

    public RetryWhenProcess(long interval) {

        mInterval = interval;
    }

    @Override
    public Observable<?> call(Observable<? extends Throwable> observable) {
        return observable.flatMap(new Func1<Throwable, Observable<?>>() {
            @Override
            public Observable<?> call(Throwable throwable) {
                return observable.flatMap(new Func1<Throwable, Observable<?>>() {
                        @Override
                        public Observable<?> call(Throwable throwable) {
                            if (throwable instanceof UnknownHostException) {
                                return Observable.error(throwable);
                            }
                            return Observable.just(throwable).zipWith(Observable.range(1, 5), new Func2<Throwable, Integer, Integer>() {
                                @Override
                                public Integer call(Throwable throwable, Integer i) {

                                    return i;
                                }
                            }).flatMap(new Func1<Integer, Observable<? extends Long>>() {
                                @Override
                                public Observable<? extends Long> call(Integer retryCount) {

                                    return Observable.timer((long) Math.pow(mInterval, retryCount), TimeUnit.SECONDS);
                                }
                            });
                        }
                    });
            }
        });
    }
}

使用时,只需要加上:

.retryWhen(new RetryWhenProcess(5))

即可。

HTC Badge

HTC虽然没落了,但好歹是第一只Android的寄生兽,好歹也支持下。

Intent intentNotification = new Intent("com.htc.launcher.action.SET_NOTIFICATION");
ComponentName localComponentName = new ComponentName(context.getPackageName(),
        AppInfoUtil.getLauncherClassName(context));
intentNotification.putExtra("com.htc.launcher.extra.COMPONENT", localComponentName.flattenToShortString());
intentNotification.putExtra("com.htc.launcher.extra.COUNT", count);
context.sendBroadcast(intentNotification);

Intent intentShortcut = new Intent("com.htc.launcher.action.UPDATE_SHORTCUT");
intentShortcut.putExtra("packagename", context.getPackageName());
intentShortcut.putExtra("count", count);
context.sendBroadcast(intentShortcut);

其原理同样是使用广播,不解释了。

终极网络处理方案

最后,终极的处理方案肯定是这样的:能监听网络连接的广播自动重试,对网络无连接的情况不进行重试,并且重试有超时机制与重试间隔。
RxJava可以比较方便的做到,请看这篇文章Improving UX with RxJava

锤子

锤子很遗憾,使用的是原生Launcher进行的修改,只有System App具有获得角标的权限。

Nova Badge

Nova是一款非常赞的Launcher,作为第三方Launcher,它的使用率非常高(当然是在国外)。该Launcher作为业界良心,提供了content provider供外界调用。与ZUK手机一样,良心大大的好,代码如下:

ContentValues contentValues = new ContentValues();
contentValues.put("tag", context.getPackageName() + "/" +
        AppInfoUtil.getLauncherClassName(context));
contentValues.put("count", count);
context.getContentResolver().insert(Uri.parse("content://com.teslacoilsw.notifier/unread_count"),
        contentValues);

一些好玩的

在知道了一些ROM的生成角标的原理,我们可以做一些好玩的东西。前面在说LG三星Sony的ROM的时候,已经提到了,广播只需要传人包名和启动Activity名就可以给任意一个icon添加角标,因此。。。直接看代码吧:

/**
 * Bug利用测试,请勿滥用
 *
 * @param view view
 */
public void madMode(View view) {
    madMode(99);
}

/**
 * 清除Bug角标
 *
 * @param view view
 */
public void cleanMadMode(View view) {
    madMode(0);
}

/**
 * 获取所有App的包名和启动类名
 *
 * @param count count
 */
private void madMode(int count) {
    Intent intent = new Intent(Intent.ACTION_MAIN, null);
    intent.addCategory(Intent.CATEGORY_LAUNCHER);
    List<ResolveInfo> list = getPackageManager().queryIntentActivities(
            intent, PackageManager.GET_ACTIVITIES);
    for (int i = 0; i < list.size(); i++) {
        ActivityInfo activityInfo = list.get(i).activityInfo;
        String activityName = activityInfo.name;
        String packageName = activityInfo.applicationInfo.packageName;
        BadgeUtil.setBadgeOfMadMode(getApplicationContext(), count, packageName, activityName);
    }
}

非常简单的代码,就是通过PM找出具有启动Intent的Activity,再取出其包名,通过设置来添加角标。效果如图:

device-2015-12-07-141255.png

AG真人游戏平台,device-2015-12-07-141314.png

device-2015-12-07-141337.png

OK,丧心病狂,逼死强迫症处女座。

请勿滥用,由此引起的一切问题,不要找我

请不要提桌面背景!!!

Github

忘记发地址了
https://github.com/xuyisheng/ShortcutHelper

本文由AG真人游戏平台发布于广播,转载请注明出处:他突然想起来他说过的这句话,Launcher会对快捷方式进行修改

关键词: