Amon's Blog

猛猛如玉

cover
众所周知,iOS 10 以后,苹果官方推出了Notification Service Extension,查看文档,UNNotificationServiceExtension的说明是,An object that modifies the content of a remote notification before it's delivered to the user.,也就是在一个远程通知展示给用户之前,可以通过UNNotificationServiceExtension来修改这个通知。

废话少说,直接开干吧。

一、如何监听收到推送

  1. 在原有的项目上新建一个 Target,如图,选择创建
    image-20190304210307444

  2. 在服务端推送的内容中新增一个字段mutable-content": "1",与alert/badge/sound处于同一层级。例如在推送平台中测试推送时,设置如下:image-20190304212029192

  3. 先运行项目的主Target,然后再运行Notification Service Extension,手动选择主Target

  4. 发送测试推送,可以看到执行进入 Target 项目中的- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler

二、如何统计

  1. 如果是接入第三方推送平台,可以查看是否支持。如 极光的 Notification Service Extension 相关接口
  2. 如果自己实现的话,有以下两种方案
    1. 在通知扩展项目中写网络请求,将推送到达数据发送到后端服务器
    2. 将推送到达数据通过App Group保存到 App 的本地,在主 Target 中再处理

三、如何使用 App Groud 实现数据共享

  1. https://developer.apple.com 登录,创建 App Group

  2. 在项目中配置,target - Capabilites - App groups

  3. 代码中使用

    1. NSUserDefaults 中使用

      1
      2
      3
      4
      5
      6
      NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.company.appGroupName"];
      // write data
      [userDefaults setValue:@"value" forKey:@"key"];

      //read data
      NSLog(@"%@", [userDefaults valueForKey:@"key"]);
    2. NSFileManager 中使用

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      // write data
      NSURL *groupURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.domain.groupName"];
      NSURL *fileURL = [groupURL URLByAppendingPathComponent:@"fileName"];

      NSString *text = @"Go amonxu.com";
      if (![[NSFileManager defaultManager] fileExistsAtPath:fileURL.path]) {
      [text writeToURL:fileURL atomically:YES encoding:NSUTF8StringEncoding error:nil];
      } else {
      NSFileHandle *fileHandle = [NSFileHandle fileHandleForUpdatingURL:fileURL error:nil];
      [fileHandle seekToEndOfFile];
      [fileHandle writeData:[text dataUsingEncoding:NSUTF8StringEncoding]];
      [fileHandle closeFile];
      }


      // read data
      NSURL *groupURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.domain.groupName"];
      NSURL *fileURL = [groupURL URLByAppendingPathComponent:@"fileName"];
      NSString *fileContent = [NSString stringWithContentsOfURL:fileURL encoding:NSUTF8StringEncoding error:nil];

早高峰的地铁里,
人群席卷了站台,
十秒后空无一人,
除了那只孤独的鸟。
它瘦削的躯干,
高挑的气质,
两腿交叉在那,
静静地立着,
仿佛不属于这个世界。


本是,
林中鸟,
溪中水,
山涧清风,
田中野兔,
你在这里,
带来谁的问候,
带走谁的孤独?

列车越渐快速的远行,
它的身影被拉的愈发修长,
它仍低头兀自欣赏自己,
列车没能带走了她,
它却带走了路人的心。

原文链接:https://my.oschina.net/feichexia/blog/196575

JVM 性能调优参考资料:

《Java虚拟机规范》

《Java Performance》

《Trouble Shooting Guide for JavaSE 6 with HotSpot VM》: http://www.oracle.com/technetwork/java/javase/tsg-vm-149989.pdf

《Effective Java》

VisualVM: http://docs.oracle.com/javase/7/docs/technotes/guides/visualvm/

jConsole: http://docs.oracle.com/javase/1.5.0/docs/guide/management/jconsole.html

Monitoring and Managing JavaSE 6 Applications: http://www.oracle.com/technetwork/articles/javase/monitoring-141801.html

BTrace:https://kenai.com/projects/btrace

英文原文:
https://engineering.linkedin.com/blog/2016/02/eliminating-large-jvm-gc-pauses-caused-by-background-io-traffic

译文原文:
https://www.jianshu.com/p/ce9b3f0a90f2

这里就只贴出结论总结吧:

有低延迟要求的Java应用程序需要极短的JVM GC停顿。但是,当磁盘IO压力很大时,JVM可能被阻塞一段较长的时间。

我们对该问题进行了调查,并且发现如下原因:

  1. JVM GC需要通过发起系统调用write(),来记录GC行为。
  2. write()调用可以被后台磁盘IO所阻塞。
  3. 记录GC日志属于JVM停顿的一部分,因此write()调用的时间也会被计算在JVM STW的停顿时间内。
    我们提出了一系列解决该问题的方案。重要的是,我们的发现可以帮助JVM实现来改进该问题。对于低延迟应用程序来说,最简单有效的措施是将GC日志文件放到单独的HDD或者高性能磁盘(例如SSD)上,来避免IO竞争。


🍺 show-busy-java-threads

用于快速排查JavaCPU性能问题(top us值过高),自动查出运行的Java进程中消耗CPU多的线程,并打印出其线程栈,从而确定导致性能问题的方法调用。
目前只支持Linux。原因是MacWindowsps命令不支持列出进程的线程id,更多信息参见#33,欢迎提供解法。

PS,如何操作可以参见@bluedavy《分布式Java应用》的【5.1.1 CPU消耗分析】一节,说得很详细:

  1. top命令找出有问题Java进程及线程id
    1. 开启线程显示模式(top -H,或是打开top后按H
    2. CPU使用率排序(top缺省是按CPU使用降序,已经合要求;打开top后按P可以显式指定按CPU使用降序)
    3. 记下Java进程id及其CPU高的线程id
  2. 用进程id作为参数,jstack有问题的Java进程
  3. 手动转换线程id成十六进制(可以用printf %x 1234
  4. 查找十六进制的线程id(可以用vim的查找功能/0x1234,或是grep 0x1234 -A 20
  5. 查看对应的线程栈,以分析问题

查问题时,会要多次上面的操作以分析确定问题,这个过程太繁琐太慢了

用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
show-busy-java-threads
# 从所有运行的Java进程中找出最消耗CPU的线程(缺省5个),打印出其线程栈

# 缺省会自动从所有的Java进程中找出最消耗CPU的线程,这样用更方便
# 当然你可以手动指定要分析的Java进程Id,以保证只会显示出那个你关心的那个Java进程的信息
show-busy-java-threads -p <指定的Java进程Id>

show-busy-java-threads -c <要显示的线程栈数>

show-busy-java-threads <重复执行的间隔秒数> [<重复执行的次数>]
# 多次执行;这2个参数的使用方式类似vmstat命令

show-busy-java-threads -a <运行输出的记录到的文件>
# 记录到文件以方便回溯查看

show-duplicate-java-classes -S <存储jstack输出文件的目录>
# 指定jstack输出文件的存储目录,方便记录以后续分析

##############################
# 注意:
##############################
# 如果Java进程的用户 与 执行脚本的当前用户 不同,则jstack不了这个Java进程
# 为了能切换到Java进程的用户,需要加sudo来执行,即可以解决:
sudo show-busy-java-threads

show-busy-java-threads -s <指定jstack命令的全路径>
# 对于sudo方式的运行,JAVA_HOME环境变量不能传递给root,
# 而root用户往往没有配置JAVA_HOME且不方便配置,
# 显式指定jstack命令的路径就反而显得更方便了

# -m选项:执行jstack命令时加上-m选项,显示上Native的栈帧,一般应用排查不需要使用
show-busy-java-threads -m
# -F选项:执行jstack命令时加上 -F 选项(如果直接jstack无响应时,用于强制jstack),一般情况不需要使用
show-busy-java-threads -F
# -l选项:执行jstack命令时加上 -l 选项,显示上更多相关锁的信息,一般情况不需要使用
# 注意:和 -m -F 选项一起使用时,可能会大大增加jstack操作的耗时
show-busy-java-threads -l

# 帮助信息
$ show-busy-java-threads -h
Usage: show-busy-java-threads [OPTION]... [delay [count]]
Find out the highest cpu consumed threads of java, and print the stack of these threads.

Example:
show-busy-java-threads # show busy java threads info
show-busy-java-threads 1 # update every 1 second, (stop by eg: CTRL+C)
show-busy-java-threads 3 10 # update every 3 seconds, update 10 times

Output control:
-p, --pid <java pid> find out the highest cpu consumed threads from the specified java process,
default from all java process.
-c, --count <num> set the thread count to show, default is 5.
-a, --append-file <file> specifies the file to append output as log.
-S, --store-dir <dir> specifies the directory for storing intermediate files, and keep files.
default store intermediate files at tmp dir, and auto remove after run.
use this option to keep files so as to review jstack/top/ps output later.
delay the delay between updates in seconds.
count the number of updates.
delay/count arguments imitates the style of vmstat command.

jstack control:
-s, --jstack-path <path> specifies the path of jstack command.
-F, --force set jstack to force a thread dump.
use when jstack <pid> does not respond (process is hung).
-m, --mix-native-frames set jstack to print both java and native frames (mixed mode).
-l, --lock-info set jstack with long listing. Prints additional information about locks.

cpu usage calculation control:
-d, --top-delay specifies the delay between top samples, default is 0.5 (second).
get thread cpu percentage during this delay interval.
more info see top -d option. eg: -d 1 (1 second).
-P, --use-ps use ps command to find busy thread(cpu usage) instead of top command,
default use top command, because cpu usage of ps command is expressed as
the percentage of time spent running during the entire lifetime of a process,
this is not ideal.

Miscellaneous:
-h, --help display this help and exit.

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
$ show-busy-java-threads
[1] Busy(57.0%) thread(23355/0x5b3b) stack of java process(23269) under user(admin):
"pool-1-thread-1" prio=10 tid=0x000000005b5c5000 nid=0x5b3b runnable [0x000000004062c000]
java.lang.Thread.State: RUNNABLE
at java.text.DateFormat.format(DateFormat.java:316)
at com.xxx.foo.services.common.DateFormatUtil.format(DateFormatUtil.java:41)
at com.xxx.foo.shared.monitor.schedule.AppMonitorDataAvgScheduler.run(AppMonitorDataAvgScheduler.java:127)
at com.xxx.foo.services.common.utils.AliTimer$2.run(AliTimer.java:128)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
at java.lang.Thread.run(Thread.java:662)

[2] Busy(26.1%) thread(24018/0x5dd2) stack of java process(23269) under user(admin):
"pool-1-thread-2" prio=10 tid=0x000000005a968800 nid=0x5dd2 runnable [0x00000000420e9000]
java.lang.Thread.State: RUNNABLE
at java.util.Arrays.copyOf(Arrays.java:2882)
at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:100)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:572)
at java.lang.StringBuffer.append(StringBuffer.java:320)
- locked <0x00000007908d0030> (a java.lang.StringBuffer)
at java.text.SimpleDateFormat.format(SimpleDateFormat.java:890)
at java.text.SimpleDateFormat.format(SimpleDateFormat.java:869)
at java.text.DateFormat.format(DateFormat.java:316)
at com.xxx.foo.services.common.DateFormatUtil.format(DateFormatUtil.java:41)
at com.xxx.foo.shared.monitor.schedule.AppMonitorDataAvgScheduler.run(AppMonitorDataAvgScheduler.java:126)
at com.xxx.foo.services.common.utils.AliTimer$2.run(AliTimer.java:128)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
at java.lang.Thread.run(Thread.java:662)

......

上面的线程栈可以看出,CPU消耗最高的2个线程都在执行java.text.DateFormat.format,业务代码对应的方法是shared.monitor.schedule.AppMonitorDataAvgScheduler.run。可以基本确定:

  • AppMonitorDataAvgScheduler.run调用DateFormat.format次数比较频繁。
  • DateFormat.format比较慢。(这个可以由DateFormat.format的实现确定。)

多执行几次show-busy-java-threads,如果上面情况高概率出现,则可以确定上面的判定。
因为调用越少代码执行越快,则出现在线程栈的概率就越低。
脚本有自动多次执行的功能,指定 重复执行的间隔秒数/重复执行的次数 参数。

分析shared.monitor.schedule.AppMonitorDataAvgScheduler.run实现逻辑和调用方式,以优化实现解决问题。

声明

转载自 https://github.com/oldratlee/useful-scripts
致谢 oldratlee

cover

故事从孙少平开始,少平在原西县城上高中,贫困的他每天等别人吃完饭后才去偷偷拿他的两个非洲黑馒头,后来和同拿非洲馒头的郝红梅同病相怜,成为知心朋友。在那个物质匮乏,思想贫瘠的时代,给予两个可怜的人莫大鼓励。

孙少安是生产队长,尽管他干农活在村里数一数二,但父母年迈,尚有老奶奶,弟弟妹妹上学,拖着一个贫困的家庭,在黄土地里的劳作最终也不能盈余一点。县城里有他青梅竹马的润叶,润叶叫他去县城见面,她必须要和他倾诉感情。

少安最后主动选择了消失,他认清了自己是个农民,并且以后也是农民,农民和小资产阶级是没有未来的。后来他娶了同是农民的贺秀莲,这辈子也算枕边有个相互照料的人。

田润叶是县城里的教师,寄托在二爸田福军的屋檐下,她和少安一样,无法摆脱所处阶级的禁锢,和舔狗李向前结婚。她根本不爱李向前,被李向前羞辱后一个人独过,直到李向前成为残疾后莫名其妙地圣母心发作,回去和他一起生活。

少平高中毕业后,去了黄原揽活,和田晓霞渐生情愫。原以为他会陷入到安排落户的曹书记的安排里,但他后面还是去了铜城煤矿,结识了可能相处一辈子的慧英嫂一家。

田晓霞和少平相互倾心,他们跨越了彼此的阶级鸿沟,尽管都不知未来会如何,但人生苦短及时享乐。路遥索性让他们没有未来,不管现实还是故事,这个阶级鸿沟谁也跨越不了。因为这不是爽文,是平凡的世界。平凡的世界谁也不是超级英雄,顶多是个平民英雄。少平洪水中救了侯玉英当了一回英雄,晓霞洪水中救小女孩当了烈士。

王满银,一个自以为读了点书不愿务农的二流子,一个敢革命时代搞资本主义的老鼠药贩子,一个不愿打柴干脆去田沟里晒太阳取暖的二傻子。打从放开资本主义和市场经济后,满银同志便走南闯北的逛,黄原、省城、上海,甚至南中国的深圳沙头角,他都能逛了去。如果不是海关拦着,他可能逛到香港去了,现在说不定就是个商业巨鳄、XX大王。忽然有一天他照了下镜子,岁月蹉跎容颜衰老,他意识到已经不是那个靠骚就勾引到兰花的年轻王满银,他火烧屁股般赶到家里,再也不愿意离开。

田福堂,双水村权力最大的人,他控制了村里上上下下,唯独家里两个孩子脱离了他的控制。润叶在政治婚姻的巅峰时如同离婚,在婚姻跌入谷底时又选择回去。润生前半生是个乖乖仔,在重遇寡妇郝红梅时,义无反顾的在一起。田福堂一辈子没做什么大事,唯一一件开山辟岭的大事件,除了去金家的那一跪和炸山那一炮外,再无动静。农村翻天覆地的变化时,他躺在石碾上晒太阳,等到卸去了权力后,他一人带着继孙子和亲孙子,来看热闹。

田福军,平凡的世界少有的不平凡人。从县里到市里,从市里到省里,虽然偶有政敌和障碍,但也算是步步高升。他没有想到女儿和一个挖煤工人在一起,也没有想到女儿会在洪灾来临时第一时间赶往前线,更没想到深夜会接到女儿的噩耗。在那一刻,田福军也是平凡人,众生皆苦。

孙少安的事业几经波折,但早已双水村第一农民企业家,但在他接手的乡镇砖瓦厂,事业新巅峰时,秀莲得了肺癌,当然很可能是长期的劳作,加上在砖厂里。

少平在师父拯救别人意外身亡后承担了师父家里男人的角色,同样在自己拯救别人后,破相了。尽管金秀向他表示了爱意,但自尊心强的他,永远也不会答应。故事的最后,他独自坐车回去铜城,如无意外,将成为下一个王世才。别忘了王世才出场时的介绍,他背驼的厉害,镶着两颗金牙,这是煤矿留给他的纪念,也是颁给他的荣耀勋章。少平领取了勋章后,也是真正的成为煤矿的人,他和慧英嫂再无隔阂。

路遥是一个出身上世纪黄土地的作家,对于那个时代和黄土地,写起来得心应手,一方面是他本身生活在那个时代那个地方,另一方面他写作时也回到黄土地,查阅了很多资料,咨询了很多人,为此他耗尽了生命的力量。他真实的刻画了双水村,和双水村的人,几十年的历史变迁,几代人的利害纠葛。尤其是农村的那种既能看到些许未来的盼望,又能一眼看到尽头的绝望。

尽管文笔略显稚嫩,部分人物太脸谱化,官场描述太苍白,但这部书仍不失为一个乡土文学的经典之作。作为一个生于农村长于农村的乡下孩子,即使相隔几十年,仍然能找到许多共鸣。

我爱农村,我恨农村,有时想回到那里,有时想离开那里。我们都在平凡的世界,众生皆苦。

111

像路遥这样通过努力摆脱农村走进城市的作家,在达成愿望之后又自觉不自觉地返回了农村,并打算一辈子写农村。“在农村的时候,总有一种本能要摆脱,等到出去了,又觉得离不开这里,又会回来。这是很多从农村走去出的人共同的心情。”王天笑说。

刚炒股时:老子都上雪球了,手握百十万资产,分分钟几千几万上下,看得上你这破100元购物券?
炒股后:哪里还有这样的活动?真香!

说起股票,我算是接触的比较晚的。就连最近的2015年大牛市与大熊市都错过了,5000点是什么?李大霄是谁?那时候的我正值事业的剧烈变动中,一心只想在事业上再攀高峰。

2017年事业稍显稳定,加上手里有点资产,开始膨胀了。因缘际会之下,我接触了股市,然后开了个户,先实盘买入第一支股票:驰宏锌锗。几天后,我成功亏损了几十元。

我开始怀疑自己了,我也许不适合做这个,我的资产亏损了几十元,好像亏损了几十万,因为这几十块的差别,导致我差几十元登上福布斯排行榜,追悔莫及。

接下来,在每天的上上下下几元十几元几十元后,我厌烦了这种几块钱的买卖,准备开始加大投入。

彼时,新能源一片火热。火热的市场环境,清晰可见的未来,加上身边随处可见的比亚迪和绿色的新能源牌照,我感觉可以在此领域开启我的巴菲特投资之路。

在深入了解新能源行业的上下游情况和相关企业后,以及查看对应龙头的相关信息,我购入了第二支股票:赣锋锂业,价格60多元。

当时赣锋锂业一路上行,炙手可热。有人觉得到顶了,有人觉得还有上升空间,各种鱼龙混杂的投资者和假先知们大行其道。站在风口上,看着每天大红的持仓和不断翻动的数字,我又膨胀地觉得自己也许就是投资的料。

赣锋锂业继续向上突破,市场依然各种情绪在波动,在泛滥,而每一天的各种情绪,都是前一天的情绪在复制,在延续。

都说懂得什么时候买入的是高手,懂得什么时候卖的是大神。而我既不懂得什么时候买,也不懂得什么时候卖。买入担心下一刻就跌,卖出又担心下一刻会涨。犹豫不决之下,买入的一直在手里憋着,且时不时忍不住又贪婪地买了一些。

站在风口,猪也能飞,看着财富一天天不断的在放大,我终于有一天开始觉得坐立不安了。我咨询了一些业界前辈和专家,问他们怎么看。他们众口纷纭,有的说涨幅已经透支了未来的潜力,接下来会回调,有的说市场长期看好。当他们问我的看好的价位是多少,我随口说:120。

鬼才信呢!我默默地把心理价位定为100,一定有很多个和我一样这样想的。那么到底是永远都到不了100呢,还是不到100就故意不到100,还是到了100也不停步继续攀登,还是100压根是个无所谓的节点?

墨菲定律说如果一件事情可能发生,那么它一定会发生。2017年9月11号,赣锋锂业最高价100元整,收盘价100元整,所有人都在盯着100元整这个价位。明天好看了!

2017年9月12日,赣锋锂业开盘突破100元,交易市场剧烈变动,最高达到103.49元,唱多者和唱空者还没来及相互打脸,然后一路下行,最低90元整,最后收盘92.3元。多军和空军只能互相抚摸肿胀的脸。

至于我?我的那些憋在手里的怎么处理的?留给大家猜想吧。不过我想说的是,你们每想到的一种操作,这个市场都有人做到了。

这就是A股,一个普通的小股民的故事,它是每一个人。

手机浏览器打开 http://chls.pro/ssl,安装相关证书即可。

部分设备安装证书后还有问题,请看下面:

On iOS 10.3: Settings > General > About (logically…) > Certificate Trust Settings > Enable Full Trust for Root Certificates

参考:

https://forums.developer.apple.com/message/212408#212408

https://forums.developer.apple.com/thread/71789

https://github.com/bumaociyuan/ios-ipa-server/issues/23#issuecomment-293828854

0%