GC

今天,我们来探讨一下JVM里面的GC算法吧,这也是JAVA用户所不能避开的问题。

JAVA相比之C++的一大的区别是,JAVA能够基于JVM对内存进行自动的分配与释放,而不像C++一样需要工程师手动的注意各个变量的内存分配以及释放,时时刻刻得小心是否已经造成了内存泄漏这些问题,比较麻烦。

那么,到底会有哪几种GC算法呢?我们就按照一个时间层层递进的顺序来大致了解一下吧。

引用计数法

引用计数法是最经典最古老的一种垃圾收集方法。

引用计数法的基本思路是:对于一个对象A,只要有任何一个对象引用了A,则A的引用计数器就会+1,反之,当引用失效时,引用计数器就-1.只要对象A的引用计数器的值为0,则对象A就不可能在被使用,此时可以被回收了。

实现也是比较简单的,就是为每一个对象创建一个整型的计数器即可。但是,这个方法有个致命缺点,那就是它无法处理循环引用的问题。因此,JAVA的垃圾回收器中,没有采用这种算法。

而所谓的循环引用是,有两个对象A和B,A对象中含有B的引用,B对象含有A的引用,此时,A和B的引用计数器的值都不是0,但是在系统中,又没有第三个对象引用了A或B,此时,A和B应该是被回收的对象,是垃圾对象,但是由于垃圾对象之间相互引用,从而是垃圾回收器无法识别,引发内存泄漏。

标记-清除算法(Mark-Sweep)

既然引用计数法有着缺陷,那么我们就换个思路,给每一个还存活的对象进行标记,而没有标记的就是垃圾对象,这样就很方便于垃圾回收器回收清除垃圾对象了。也就是说,标记-清除算法将垃圾回收分为标记、清除两个阶段。

其基本思路是:在标记阶段,首先通过根节点,标记所有从根节点开始的可达对象。然后在清除阶段,清除所有未被标记的对象。

但是,标记-清除算法最大的问题是内存空间碎片化的问题。回收后的内存空间是不连续的。在对象的堆空间分配过程中,尤其是大对象的内存分配,不连续的内存空间的效率要低于连续的空间。

复制算法

复制算法,算是从标记-清除算法中演变而来的一种算法。

首先,我们知道现在JVM中的JAVA堆大体分为三个区,分别是年轻代(YYoung Generation)、年老代(Old Generation),以及持久代(Permanent Generation)。在年轻代中又有一个Eden区,两个Survivor区(一般而言)。大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个)。而我们的复制算法便是使用在这两个Survivor区之间的。

接着,我们假定在这个年轻代中,只有两个Survivor区,每次只使用其中的一个Survivor区。好,那么我们把这两个Survivor区命名为S1和S2。假设这次只使用S1区。

在S1中使用标记-清除算法,对还存活的对象进行标记,然后将被标记的还存活的对象复制到S2中,最后清除S1中所有的对象。这样便做到了防止内存碎片化的情况发生。

那么总结一下,复制算法的基本思路是:将原有的内存空间分为两块,每次只使用其中的一块,在垃圾回收的时候,将正在使用的内存中的存活对象复制到未使用的内存块之中,之后清除正在使用的内存块中的所有对象。然后交换两个内存块的角色,完成垃圾回收。

优点是:

1、高效。在系统中垃圾对象较多的时候,也就是真正需要垃圾回收的时候,复制算法需要复制的存活对象并不多,所以复制算法的效率很高。

2、确保回收后的内存空间没有碎片。

缺点是,需要将系统的内存空间折半。

另外,在垃圾回收的时候,eden空间中的存活对象会被复制到未使用的survivor空间中,正在使用的survivor空间中的年轻对象也会被复制到未使用的survivor中(大对象,或者年老对象会直接进入年老代,如果未使用的survivor空间已满,则对象也会直接进入年老代)。

标记-压缩算法(Mark-Compact)

我们知道,复制算法的高效性是建立在存活对象少、垃圾对象很多的前提下。这种情况在年轻代是经常发生的,但是在年老代,更常见的是大部分对象是存活对象,如果依然使用复制算法的话,由于存活对象较多,复制成本便变得很大。

而标记-压缩算法正是一种年老代的回收算法。

标记-压缩算法是在标记-清除算法上进行改进过后的算法。基本思路是:首先跟标记-清除算法一样,需要从根结点开始,对所有可达的对象进行标记,之后,不是简单的清除,而是将所有的存活对象压缩到内存的一端,接着才是清除边界以外的所有的内存空间。

这样,既避免了内存的碎片化,也不需要两块相同的内存空间。性价比较高。

增量算法

增量算法是专门对垃圾回收时Stop the World状态的一种优化算法。

对于大部分的垃圾回收算法来说,在垃圾回收的过程中,软件会处于一种Stop the World 的状态。在这个状态下,应用程序所有的线程都会挂起,暂停一切正常的工作,等待垃圾回收的完成。如果垃圾回收时间过长,应用程序会被挂起很长时间,将会严重影响用户体验和系统的稳定性。

而增量算法的基本思路是:如果一次性的将所有的垃圾进行处理,需要造成系统长时间的停顿暂停,那么可以让垃圾收集线程和应用程序线程交替执行。这样,每次垃圾回收只收集一小片区域的内存空间,接着又切换到应用程序线程。以此反复,知道垃圾回收完成。这样,便可以减少系统的停顿时间。

缺点是,因为线程切换和上下文转换的消耗,会使得垃圾回收的总体成本上升,造成系统吞吐量的下降。

后记

这些便是一些简单的GC算法了。如若有错,还请斧正。多谢。

大鱼·海棠

2016年7月8日,期待已久的《大鱼·海棠》终于上映了。然而,这部中国唯美动画电影的本土化尝试,却引起网民的撕逼大战。有的认为这是12年等待的落空,有的认为中国动漫的崛起又要往后延了一年,在某乎上黑《大鱼·海棠》更是成为了政治正确的选择。

而因为疲于工作的Yale我,今天终于抽出时间毫不犹豫掏出口袋里仅有的几个钢镚买了电影票,圆了我整整6年的期待。

可以说,《大鱼·海棠》是继《宝莲灯》以来代表着中国最高实力的国产动画电影,对,我封的,也包括《大圣归来》。因为《大圣归来》更多是依赖于齐天大圣孙悟空这一国民英雄,而不是剧情等等,这是一个巨大的IP。

当初没有机会给《大鱼·海棠》众筹以期给国产动漫事业做贡献,那么,今天我就为她做一期推送,表达一下我的看法吧。

我是在高一的一次去网吧,看到的《大鱼·海棠》的。那时候看的是她最初版本的预告片,真是瞬间爆炸,一下子就被她唯美的画风、奇妙的想法所深深吸引。没错,就这样我成为了她的粉丝。

但是那时候毕竟还是个高中生,哪有时间上网,我可是个三好学生,高中学习压力大,根本没有时间去继续跟进《大·海》的情况,甚至都没有搞清楚为啥这个电影只有一个预告片正片到底在哪的问题。只是有时候在闲暇之余会想起这个惊艳我一时的《大·海》。

后来,来到了大学,学的是信息方面的专业,学会了上网,还特地去了解《大鱼·海棠》的状况,才解开了当年的困惑。虽然,时间漫长,但是我还是愿意去等待的。

一直很是佩服梁旋张春的勇气与洒脱,仅仅是因为一个梦,便不惜耗费12年的光阴去实现。这可是12年啊,还是真值青春年华啊。人生有几个12年,这不是说说就能够做得到的,期间的心酸坎坷也不是常人所能度之的。这是勇士之为。

也许,他们是幸运的,知道自己人来所为何事,油然而生的使命感令其不可不为,身先士卒。
人生难得知己。在梁旋决定要为一个梦创办彼岸天,将其制作成电影时,张春不离不弃,投入全身尽力支持他。好基友一被子啊。

而这两人也没有让我失望,再加上我的偶像Eason为《大·海》献唱了《在这个世界相遇》,更是让我为制作方点赞。可以说,整部电影画面唯美,设定惊奇充满着中国风,细节处理的很到位(尤其是客家土楼、门窗纹理、人物考究)、配音流畅,在剧情方面,逻辑比较流畅,前期铺垫也做得很足,就是在湫向椿表白的时候,略显尴尬,但是毕竟是动画、人物设定也是十几岁的少年少女,情窦初开,很难说怎么处理才适合,这不难理解。我真的想不通为啥剧情会被大家所如此诟病。

虽然,我很想在剧情方面展开讨论,但是秉着剧透一时爽的原则,便在此不表。
在画风方面,有人说很像《千与千寻》,或者是说抄袭《千与千寻》,但是我说,说像《千与千寻》,那是谬赞,毕竟能够拿来与异步世界级的作品进行对比,是《大·海》的荣幸啊,说明这中国动漫的进步,但是说是抄袭的,我就只能怀疑这个人的智商了,到底是哪里抄了。不管是剧情方面、画风方面、设定方面,真是一毛钱都扯不上啊。

整个电影,处处充满着良心,考究、考究、真是十分的考究。我认为,不失为一部佳作。这部作品,我们虽不能说国漫崛起,但是可以清晰的看出来,我们在进步。这是一次很好的经验。尽管现在国产动漫圈很小,但是在这一次又一次的尝试之后,积累经验、吸引培育人才,我们定能培育出真正能够做到国漫崛起的沃土。

现如今,中国动漫产业正在不断的成长,不少投资人也在把目光投向这里,加上东家腾讯、B站、A站、有妖气等等公司的大力支持,涌现出不少好的作品,比如动漫堂米二的《一人之下》、夏天岛的《狐妖小红娘》、老壁的《端脑》等等,而中国特色的动画产业商业模式也在逐渐的形成,产业链也在艰辛地构筑当中。我也算是看动漫看了十多年的人了吧,经历过《漫画世界》、《知音漫客》、《龙漫》、有妖气等等的兴落,听说更加之前的时候国漫更是一片死寂。所以,这真是一个很棒的时代。或许,现在还不如人意,但是这是一个曲折上升的过程,毕竟一口吃不出个大胖子,但是只要不放弃努力,国漫崛起不是梦。

大学者,蜕变之谓也

总是有很多的借口给自己开脱不更公众号,慢慢的便会错过很多。

蓝杰游学已经过去了一个多周,近来才有时间总结,毕竟实习考核在即。很难得的有机会接触这么多的学弟学妹,发现现在蓝杰的人数真是暴涨,自己已然成为了其中的老字辈?!很多人都不认识了。看着这些年轻的面孔,这些手握着大学时光的大一大二生们,有点唏嘘,想起了自己当年的囧样,啥都不懂,屁颠屁颠的到处闹腾。

很敬佩胡哥,又做了件了不起的事情,竟想着带着六七十号人从长沙跑来深圳,让这帮初出茅庐的小子们见识何所谓互联网公司,见识见识这腾讯,这华为,这京东和深信服。仿佛每年都在实现一个小愿望。

自己还不是个人物,也不是很会讲些严肃的话题,所以分享的时候也只能给学弟学妹们打了个马虎眼,毕竟自己体会才是硬道理。不过要想说的话,我觉得,大学,就是个蜕变之地,没人知道你将会蜕变成什么样的巨龙。

一直很信奉的一句话是,“挣扎跳出自己的舒适区”。私以为,人只有觉得困惑不解、窘迫不安、不知所措、无所依靠的时候,才会想到成长,变强。因为那个时候,你的所学所得已经无法适应着变化的环境,你只得不断的学习新的知识,新的领域,新的技能来化解这燃眉之急。

大学就是个舒适区。中午起床,然后对着手机、看着电脑,就是撸个一天,抽空订个外卖便解决了一天的口活,偶尔老师点名了无奈就去教室里边,或是瞌睡,或是玩手机,或是想着某某院某某系某某班的妹子好好看,谈论着谁谁谁排位又上了黄金,下课了该吃什么啊附近都没什么新店了,作业是什么,大不了就是网上一抄,考试来了怎么办快快快去图书馆占个位子学一下,不行也显得自己有好好学习了,卧槽这题怎么这么难我玩会手机先压压惊,参加个什么活动啊还要睡觉呢除非有妹子,看到某某某又拿了全年第一国奖国励某某比赛金奖体育全能出国留学创业拿了好几百万的投资也不会有个什么波澜毕竟与自己远着呢。舒适的生活,没有一滴波澜,日复一日,仿佛时间就是用来挥霍的,只愿今日醉生梦死。

当初我为什么不在省内而跑到这么远的长沙读书,不就是图了个新鲜,不被家人给束缚在省内,无法见识这世界的奇妙,无法品味这多样的中华美食,无法体验着多彩的生活吗!那么这样的安逸生活,是自己想要的吗?不是。记得大一的时候,协会会长说过自己不想去自己母亲的事业单位,因为在那里他已经看到了自己老年的模样。突然就有点心惊。

所以我一直在变,为的只是将过去的自己给甩的远远地,为的是未来的很多很多的不一样的事物。

当然,每次见到老同学,他们都说我没变。确实,有所为有所不为,情义是不能变的。

可惜的是,自己挣扎跳出舒适区,到现在,还是有很多的地方没有改变,还是一样的不够果断,还是一样的不会吉他画画,还是一样的单身,还是一样的怯懦。幸运的是,我学会了编程,学会了独自生活,学会了独自旅行,学会了怎么快速与当地人聊天,学会了尝试,学会了记录,学会了挑战,还有庆幸认识了这么多的很棒的朋友。

大学者,蜕变之谓也。这几年里,我见识了有人从学渣到学霸的成长,立志科研的奋斗,为了追求爱情而形象的巨变,为了寻找而各地的旅行,为了汉服崛起的努力,为了梦想毅然奔赴边疆的军旅,为了出国深造的艰苦准备,为了世界第一而不惜耗光大学四年,为了青春而创业,为了改变而主持演话剧学轮滑跳舞画画学琴唱歌登台表演与人辩论编程。每个人都有欲望,都有梦想,都有自己喜欢的爱做的事情。不知道以后还有没有机会,但现在肯定是有机会的,所以趁这个机会不如得到蜕变,做一些自己以前想过没有想过的事情。这,才是青春。

未来,我还要认识更多不一样的人,见识更多好玩的事物,学习好多好多的好玩的,走遍青山绿水,跨越山河,要有诗,也要有远方。

扩展属性Extended Attributes

目录

1、EA的概述

所谓扩展属性,Extended Attributes,以下简称EA,是区分于文件属性、文件的扩展出来的属性。

EA是目前流行的POSIX系统中文件系统具有的一项特殊功能,可以给文件、文件夹添加额外的key/value的键值对,可以以键值对地形式将任意元数据与文件i节点关联起来。键和值都是字符串并且有一定长度地限制,是完全自定义的属性。Linux自版本2.6起,开始支持EA。

我们可利用EA去实现访问列表和文件能力、记录文件地版本号、与文件地MIME类型/字符集有关的信息,或是指向图符的指针。

EA需要底层文件系统来支撑,包括btrfs、ext2、ext3、ext4、JFS、Reiserfs以及XFS等文件系统都支持EA。各类文件系统对EA的支持都属可选项,受内核配置选项中的“File systems”菜单控制。Reiserfs文件系统自Linux 2.6.7开始支持EA。

2、EA的命名空间

EA的命名格式是namespace.name。其中namespace是用来把EA从功能上划分为截然不同地几大类。而name是用来在指定命名空间内唯一标识某一EA。

可供namespace使用的值有4个:user、trusted、system以及security。这4类EA的用途可参考如下:

User EA

  1. 将在文件系统检查地制约下由非特权级进程操控。要想获取User EA值,需要由文件地读权限;要想改变User EA值,则需要写权限。若无所需权限,将会导致EACCES错误。

  2. 在ext2、ext3、ext4或Reiserfs文件系统上,如要将User EA与一文件关联,需要在装配底层文件系统时候带选上user_xattr选项。
    $ mount –o user_xattr device directory

Trusted EA

  1. 也可以由用户进程驱使,跟User EA类似。
    
  2. 与User EA的区别是,要操纵trusted EA,必须是特权进程。
    

System EA:供内核使用,将系统对象与一文件关联。目前仅支持访问控制列表。

Security EA

  1. 用来存储服务于操作系统安全模块地文件安全标签;
    
  2. 将可执行文件与能力关联起来;
    
  3. 设计初衷是为了支持安全强化版地Linux(SELinux)。
    

一个i节点可以拥有多个相关EA,其所属地命名空间可以相同,也可不同。各个命名空间里,EA是自成一体的,即是说这些命名空间是互不排斥的。另外,在user和trusted命名空间内,EA名可以是任意字符串。在system内,只有经过内核明确认可地命名才可使用,比如访问控制列表。

3、EA的Shell指令

在Shell中,可执行setfattr(1)和getfattr(1)命令来设置和查看文件的EA。

1
2
3
4
5
6
7

touchtfile
setfattr -n user.x -v "The past is not dead." tfile

getfattr -n user.xtfile
getfattr -d tfile

另外需要注意的是,EA值可以为空字符串,这不同于未定义的EA值。

  1. setfattr

EA1.png

  1. getfattr

EA2.png

默认情况下,getfattr只会列出User EA值。可利用-m选项指定一个正则表达式,来筛选要显示地EA名

1
2
3
4
5
6
7
8
9
10
11

$ getfattr -m - tfile
# file: tfile
user.x

$ getfattr -m x tfile
# file: tfile
user.x

$ getfattr -m y tfile

  1. attr

EA3.png

这个shell指令涵盖了setfattr和getfattr的功能,是两者的集合。

4、EA的限制

1) 对User EA的限制

User EA只能施之于文件或目录,之所以将其他文件类型排除在外,原因如下:

1、因为符号链接会对所有用户开启所有权限,而且这是不可更改的。然而,对于User EA,是需要考虑权限地。这是相互矛盾地。如果使用符号链接,系统会吧链接去除掉。

2、对于设备文件、套接字以及FIFO而言,授予用户权限,意在对其针对底层对象所执行的i/o操作加以控制,如欲操控这些权限,转而求取对创建user EA地控制,二者会出现权限冲突。

3、若某一目录启用了粘性位(sticky位),且为其他用户所拥有,则非特权用户不能将一User EA置于该目录之上。惟其如此,才能防止任一用户将EA附于诸如/tmp之类的目录之上,由于其可写权限对所有用户开放,导致任意用户均可操纵此目录的EA,而设置粘性位,意在防止用户删除该目录下为其他用户所拥有的文件。

2)EA实现上的限制

1、EA名称地长度不能超过255个字节

2、EA值的容量为64KB

此外,某些文件系统对可与文件挂钩的EA数量及其大小还有更为严格的限制。

3、在ext2、ext3以及ext4文件系统上,与一文件关联地所有EA命名和EA值地总字节数不会超过单个逻辑磁盘块的大小:1024字节、2048字节或4096字节

4、在JFS上,为某一文件系统所使用的所有EA名和EA值地总字节数上限为128KB

5、EA的系统调用

1) 创建和修改EA

系统可调用以下方法来设置EA值。

EA4.png

setxattr:通过pathname来标识文件,如果文件名是符号链接,则对其解引用;

lsetxattr:通过pathname来标识文件,如果文件名是符号链接,不会对其解引用;

fsetxattr:通过打开文件描述符fd来标识文件。

以上3者之间的差异同样适用于本文下面介绍的其他各组系统调用。

参数name是一个以空字符串结尾地字符串,定义了EA地名称。

参数value是一个指向缓冲区地指针,包含了为EA定义地新值。

参数size是指明缓冲区地大小。

默认情况,指定名称地EA不存在,系统调用会创建一个新的EA。已经存在地话,就会进行替换。

参数flags是可对这一行为进行控制,将这参数指定为0,设定为默认情况。或者可将其指定为如下常量之一:

EA5.png

2) 获取EA值

可利用以下方法来获取EA值。

EA6.png

参数name是一个以空字符串结尾地字符串,定义了EA地名称。用来标识要取值地EA。

返回的EA值保存于参数value所指向地缓冲区中,该缓冲区必须由调用者分配,其大小由size来指定。若调用成功,会返回复制到value所指向地缓冲区中地字节数

若文件不含name的EA,系统调用失败,会返回错误ENODATA。

若size过小,系统调用也会失败,并返回错误ERANGE。

可设size为0,将会忽略value,系统调用仍会返回EA值地大小,可用来获取EA值所需value缓冲区大小。

3) 删除EA

可利用以下方法删除文件的EA。

EA7.png

Name所含以空字符串结尾的字符串,用于标识打算删除的EA。若试图删除不存在的EA,调用将失败,并会返回错误ENODATA。

4) 获取文件相关的所有EA

以下方法返回的列表会包含与某文件关联的所有EA的名称。

EA8.png

将EA地名称列表以一系列以空字符结尾地字符形式置于list所指向地缓冲区中。缓冲区大小由size指定。

与getxattr一样,可以将size指定为0,系统调用将忽略list,并返回后续调用实际获取EA名称列表时所需的缓冲区大小。

想要获取与某文件相关的EA列表,只需对文件拥有访问权限,对文件本身则无需任何权限。

处于安全堪虑,list中返回的EA名称可能不包含调用进程无权访问的属性名。比如,在非特权进程中调用listxattr()时,大多数文件系统都会忽略trusted EA。

Activity on Android

前言

Activity是Android组件中最基本也是最为常见用的四大组件(Activity,Service服务,Content Provider内容提供者,BroadcastReceiver广播接收器)之一。Activity是一个应用程序组件,提供一个屏幕,用户可以用来交互为了完成某项任务。在一个Android应用中,一个Activity通常就是一个单独的屏幕,它上面可以显示一些控件也可以监听并处理用户的事件做出响应。

目录

  1. Activity的状态
  2. Activity的生命周期
  3. Activity栈
  4. Activity的加载模式以及区别
  5. Activity 之间通信
  6. Activity 的 Intent Filter

一、Activity的状态

在 Android 中,Activity 拥有四种基本状态:

活动态Active/Runing:当一个Activity在Activity栈顶,它处于可视的、有焦点、可接受用户输入的激活状态。Android试图尽最大可能保持它活动状态,杀死其它Activity来确保当前活动Activity有足够的资源可使用。当另外一个Activity被激活,这个将会被暂停。

暂停态Paused:当 Activity 被另一个透明或者 Dialog 样式的 Activity 覆盖时的状态。此时它依然与窗口管理器保持连接,系统继续维护其内部状态,所以它仍然可见,但它已经失去了焦点故不可与用户交互。当被暂停,一个Activity仍会被当成活动状态,只不过是不可以接受用户输入。在极特殊的情况下,Android将会杀死一个暂停的Activity来为活动的Activity提供充足的资源。当一个Activity变为完全隐藏,它将会变成停止。

停止态Stoped:当 Activity 被另外一个 Activity 覆盖、失去焦点并不可见时处于 Stoped状态。当一个Activity不是可视的,它“停止”了。这个Activity将仍然在内存中保存它所有的状态和会员信息。尽管如此,当其它地方需要内存时,它将是最有可能被释放资源的。当一个Activity停止后,一个很重要的步骤是要保存数据和当前UI状态。一旦一个Activity退出或关闭了,它将变为待用状态。

待用态Killed:在一个Activity被杀死后和被装在前,它是待用状态的。待用Acitivity被移除Activity栈,并且需要在显示和可用之前重新启动它。

当一个 Activity 实例被创建、销毁或者启动另外一个 Activity时,它在这四种状态之间进行转换,这种转换的发生依赖于用户程序的动作。下图说明了Activity在不同状态间转换的时机和条件:

ActivityStates.jpg

如上所示,Android 程序员可以决定一个 Activity 的“生”,但不能决定它的“死”,也就时说程序员可以启动一个 Activity,但是却不能手动的“结束”一个 Activity。当你调用 Activity.finish()方法时,结果和用户按下 BACK 键一样:告诉 Activity Manager 该 Activity 实例完成了相应的工作,可以被“回收”。随后 Activity Manager 激活处于栈第二层的 Activity 并重新入栈,同时原 Activity 被压入到栈的第二层,从 Active 状态转到 Paused 状态。例如:从 Activity1 中启动了 Activity2,则当前处于栈顶端的是 Activity2,第二层是 Activity1,当我们调用 Activity2.finish()方法时,Activity Manager 重新激活 Activity1 并入栈,Activity2 从 Active 状态转换 Stoped 状态,Activity1. onActivityResult(int requestCode, int resultCode, Intent data)方法被执行,Activity2 返回的数据通过 data参数返回给 Activity1。

二、 Activity的生命周期

Activty的生命周期的也就是它所在进程的生命周期。

ActivityLoop.jpg

一个Activity的启动顺序:
onCreate()——>onStart()——>onResume()

当另一个Activity启动时:
第一个Activity onPause()——>第二个Activity onCreate()——>onStart()——> onResume() ——>第一个Activity onStop()

当返回到第一个Activity时:
第二个Activity onPause() ——> 第一个Activity onRestart()——>onStart()——>onResume() ——>第二个Activity onStop()——>onDestroy()

一个Activity的销毁顺序:
(情况一)onPause()——>
(情况二)onPause()——>onStop()——>
(情况三)onPause()——>onStop()——>onDestroy()

每一个活动( Activity )都处于某一个状态,对于开发者来说,是无法控制其应用程序处于某一个状态的,这些均由系统来完成。但是当一个活动的状态发生改变的时候,开发者可以通过调用 onXX()的方法获取到相关的通知信息。

在实现 Activity 类的时候,通过覆盖( override )这些方法即可在你需要处理的时候来调用。

1、onCreate :当活动第一次启动的时候,触发该方法,可以在此时完成活动的初始化工作。 onCreate 方法有一个参数,该参数可以为空( null ),也可以是之前调用 onSaveInstanceState()方法保存到存储设备中的数据。

2、onStart :该方法在 onCreate() 方法之后被调用,或者在 Activity 从 Stop 状态转换为 Active 状态时被调用。该方法的触发表示所属活动将被展现给用户。

3、onResume :当一个活动和用户发生交互的时候,触发该方法。在 Activity 从 Pause 状态转换到 Active 状态时被调用。

4、onPause :当一个正在前台运行的活动因为其他的活动需要前台运行而转入后台运行的时候,触发该方法。这时候需要将活动的状态持久化,比如正在编辑的数据库记录等。

5、onStop:当一个活动不再需要展示给用户的时候,触发该方法。如果内存紧张,系统会直接结束这个活动,而不会触发 onStop 方法。 所以保存状态信息是应该在onPause时做,而不是onStop时做。活动如果没有在前台运行,都将被停止或者Linux管理进程为了给新的活动预留足够的存储空间而随时结束这些活动。因此对于开发者来说,在设计应用程序的时候,必须时刻牢记这一原则。在一些情况下,onPause方法或许是活动触发的最后的方法,因此开发者需要在这个时候保存需要保存的信息。

6、onRestart :当处于停止状态的活动需要再次展现给用户的时候,触发该方法。

7、onDestroy :当活动销毁的时候,触发该方法。和onStop方法一样,如果内存紧张,系统会直接结束这个活动而不会触发该方法。

8、onSaveInstanceState :系统调用该方法,允许活动保存之前的状态,比如说在一串字符串中的光标所处的位置等。 通常情况下,开发者不需要重写覆盖该方法,在默认的实现中,已经提供了自动保存活动所涉及到的用户界面组件的所有状态信息。

三、Activity栈

Android 是通过一种 Activity 栈的方式来管理 Activity 的,一个 Activity 的实例的状态决定它在栈中的位置。处于前台的 Activity 总是在栈的顶端,当前台的 Activity 因为异常或其它原因被销毁时,处于栈第二层的 Activity 将被激活,上浮到栈顶。当新的 Activity 启动入栈时,原 Activity 会被压入到栈的第二层。一个 Activity 在栈中的位置变化反映了它在不同状态间的转换。Activity 的状态与它在栈中的位置关系如下图所示:

ActivityStack.jpg

每个Activity的状态是由它在Activity栈(是一个后进先出LIFO,包含所有正在运行Activity的队列)中的位置决定的。

一个应用程序的优先级是受最高优先级的Activity影响的。当决定某个应用程序是否要终结去释放资源,Android内存管理使用栈来决定基于Activity的应用程序的优先级。

ActivityStack2.jpg

四、Activity的加载模式以及区别

在Android的多Activity开发中,Activity之间的跳转可能需要有多种方式,有时是普通的生成一个新实例,有时希望跳转到原来某个Activity实例,而不是生成大量的重复的Activity。加载模式便是决定以哪种方式启动一个跳转到原来某个Activity实例。

在Android里,有4种Activity的启动模式,分别为:

1、标准模式standard:一调用startActivity()方法就会产生一个新的实例。

2、singleTop:如果已经有一个实例位于Activity栈的顶部时,就不产生新的实例,而只是调用Activity中的newInstance()方法。如果不位于栈顶,会产生一个新的实例。

3、singleTask: 会在一个新的task中产生这个实例,以后每次调用都会使用这个,不会去产生新的实例了。

4、singleInstance: 这个跟singleTask基本上是一样,只有一个区别:在这个模式下的Activity实例所处的task中,只能有这个activity实例,不能有其他的实例。

这些启动模式可以在功能清单文件AndroidManifest.xml中的launchMode属性进行设置。

相关的代码中也有一些标志可以使用,比如我们想只启用一个实例,则可以使用 Intent.FLAG_ACTIVITY_REORDER_TO_FRONT 标志,这个标志表示:如果这个activity已经启动了,就不产生新的activity,而只是把这个activity实例加到栈顶来就可以了。

1
2
3
Intent intent =new Intent(ReorderFour.this,ReorderTwo.class);  
intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
startActivity(intent);

四种加载模式的区别:

1、所属task的区别

一般情况下,standard和singleTop的Activity的目标task,和收到的Intent的发送者在同一个task内,就相当于谁调用它,它就跟谁在同一个Task中。

除非Intent包括参数FLAG_ACTIVITY_NEW_TASK。如果提供了FLAG_ACTIVITY_NEW_TASK参数,会启动到别的task里。
singleTask和singleInstance总是把要启动的Activity作为一个task的根元素,他们不会被启动到一个其他task里。

2、是否允许多个实例

standard和singleTop可以被实例化多次,并且是可以存在于不同的task中。这种实例化时一个task可以包括一个activity的多个实例。

singleTask和singleInstance则限制只生成一个实例,并且是task的根元素。

singleTop 要求在创建intent的时候,如果栈顶已经有要创建的Activity的实例,则将intent发送给该实例,而不创建新的实例。

3、是否允许其它Activity存在于本task内

singleInstance独占一个task,其它Activity不能存在那个task里。

如果它启动了一个新的Activity,不管新的Activity的launch mode如何,新的Activity都将会到别的task里运行(如同加了FLAG_ACTIVITY_NEW_TASK参数)。

而另外三种模式,则可以和其它activity共存。

4、是否每次都生成新实例

standard对于每一个启动Intent都会生成一个activity的新实例。

singleTop的Activity如果在task的栈顶的话,则不生成新的该activity的实例,直接使用栈顶的实例,否则,生成该activity的实例。

比如:现在task栈元素为A-B-C-D(D在栈顶),这时候给D发一个启动intent,如果D是standard的,则生成D的一个新实例,栈变为A-B-C-D-D。如果D是singleTop的话,则不会生产D的新实例,栈状态仍为A-B-C-D。

如果这时候给B发Intent的话,不管B的launchmode是standard还是 singleTop,都会生成B的新实例,栈状态变为A-B-C-D-B。

singleInstance是其所在栈的唯一Activity,它会每次都被重用。

singleTask如果在栈顶,则接受intent,否则,该intent会被丢弃,但是该task仍会回到前台。当已经存在的Activity实例处理新的intent时候,会调用onNewIntent()方法,如果收到intent生成一个Activity实例,那么用户可以通过back键回到上一个状态;如果是已经存在的一个Activity来处理这个intent的话,用户不能通过按back键返回到这之前的状态。

五、Activity 之间通信

在 Android 中,不同的Activity实例可能运行在一个进程中,也可能运行在不同的进程中。因此我们需要一种特别的机制帮助我们在 Activity 之间传递消息。

1、使用 Intent 通信

Android 中通过 Intent 对象来表示一条消息,一个 Intent 对象不仅包含有这个消息的目的地,还可以包含消息的内容,这好比一封 Email,其中不仅应该包含收件地址,还可以包含具体的内容。对于一个 Intent 对象,消息“目的地”是必须的,而内容则是可选项。

在通过 Activity. startActivity(intent)启动另外一个 Activity 的时候,我们在 Intent 类的构造器中指定了“收件人地址”。

如果我们想要给“收件人”Activity 说点什么的话,那么可以通过下面这封“e-mail”来将我们消息传递出去:

1
2
3
4
5
6
7
Intent intent =new Intent(CurrentActivity.this,OtherActivity.class);
// 创建一个带“收件人地址”的 email
Bundle bundle =new Bundle();// 创建 email 内容
bundle.putBoolean("boolean_key", true);// 编写内容
bundle.putString("string_key", "string_value");
intent.putExtra("key", bundle);// 封装 email
startActivity(intent);// 启动新的 Activity

那么“收件人”该如何收信呢?在 OtherActivity类的 onCreate()或者其它任何地方使用下面的代码就可以打开这封“e-mail”阅读其中的信息:

1
2
3
4
Intent intent =getIntent();// 收取 email 
Bundle bundle =intent.getBundleExtra("key");// 打开 email
bundle.getBoolean("boolean_key");// 读取内容
bundle.getString("string_key");

上面我们通过 bundle对象来传递信息,bundle维护了个 HashMap<String, Object>对象,将我们的数据存贮在这个 HashMap 中来进行传递。但是像上面这样的代码稍显复杂,因为 Intent 内部为我们准备好了一个 bundle,所以我们也可以使用这种更为简便的方法:

1
2
3
4
Intent intent =new Intent(EX06.this,OtherActivity.class); 
intent.putExtra("boolean_key", true);
intent.putExtra("string_key", "string_value");
startActivity(intent);
1
2
3
Intent intent=getIntent(); 
intent.getBooleanExtra("boolean_key",false);
intent.getStringExtra("string_key");

2、使用 SharedPreferences

SharedPreferences 使用 xml 格式为 Android 应用提供一种永久的数据存贮方式。对于一个 Android 应用,它存贮在文件系统的 /data/ data/your_app_package_name/shared_prefs/目录下,可以被处在同一个应用中的所有 Activity 访问。Android 提供了相关的 API 来处理这些数据而不需要程序员直接操作这些文件或者考虑数据同步问题。

1
2
3
4
5
6
7
8
9
10
11
// 写入 SharedPreferences 
SharedPreferences preferences = getSharedPreferences("name", MODE_PRIVATE);
Editor editor = preferences.edit();
editor.putBoolean("boolean_key", true);
editor.putString("string_key", "string_value");
editor.commit();

// 读取 SharedPreferences
SharedPreferences preferences = getSharedPreferences("name", MODE_PRIVATE);
preferences.getBoolean("boolean_key", false);
preferences.getString("string_key", "default_value");

3、其它方式

Android 提供了包括 SharedPreferences 在内的很多种数据存贮方式,比如 SQLite,文件等,程序员可以通过这些 API 实现 Activity 之间的数据交换。如果必要,我们还可以使用 IPC 方式。

六、Activity 的 Intent Filter

Intent Filter 描述了一个组件愿意接收什么样的 Intent 对象,Android 将其抽象为 android.content.IntentFilter 类。在 Android 的 AndroidManifest.xml 配置文件中可以通过 节点为一个 Activity 指定其 Intent Filter,以便告诉系统该 Activity 可以响应什么类型的 Intent。

当程序员使用 startActivity(intent) 来启动另外一个 Activity 时,如果直接指定 intent 对象的 Component 属性,那么 Activity Manager 将试图启动其 Component 属性指定的 Activity。否则 Android 将通过 Intent 的其它属性从安装在系统中的所有 Activity 中查找与之最匹配的一个启动,如果没有找到合适的 Activity,应用程序会得到一个系统抛出的异常。Activity中Intent Filter 的匹配过程如下:

ActivityIntentFilter.jpg

1、Action 匹配
Action 是一个用户定义的字符串,用于描述一个 Android 应用程序组件,一个 Intent Filter 可以包含多个 Action。在 AndroidManifest.xml 的 Activity 定义时可以在其 节点指定一个 Action 列表用于标示 Activity 所能接受的“动作”,例如:

1
2
3
4
5
 <intent-filter > 
<action android:name="android.intent.action.MAIN" />
<action android:name="com.cyw.myaction" />
……
</intent-filter>

如果我们在启动一个 Activity 时使用这样的 Intent 对象:

1
2
Intent intent =new Intent(); 
intent.setAction("com.cyw.myaction");

那么所有的 Action 列表中包含了“com.cyw.myaction”的 Activity 都将会匹配成功。
Android 预定义了一系列的 Action 分别表示特定的系统动作。这些 Action 通过常量的方式定义在 android.content. Intent中,以“ACTION_”开头。我们可以在 Android 提供的文档中找到它们的详细说明。

2、URI 数据匹配

一个 Intent 可以通过 URI 携带外部数据给目标组件。在 节点中,通过 节点匹配外部数据。
mimeType 属性指定携带外部数据的数据类型,scheme 指定协议,host、port、path 指定数据的位置、端口、和路径。如下:

1
2
<data android:mimeType="mimeType" android:scheme="scheme" 
android:host="host" android:port="port" android:path="path"/>

如果在 Intent Filter 中指定了这些属性,那么只有所有的属性都匹配成功时 URI 数据匹配才会成功。

3、Category 类别匹配
节点中可以为组件定义一个 Category 类别列表,当 Intent 中包含这个列表的所有项目时 Category 类别匹配才会成功。

Android热修复方案

前言

当一个App发布之后,突然发现了一个严重bug需要进行紧急修复,这时候公司各方就会忙得焦头烂额:重新打包App、测试、向各个应用市场和渠道换包、提示用户升级、用户下载、覆盖安装。有时候仅仅是为了修改了一行代码,也要付出巨大的成本进行换包和重新发布。

这时候就提出一个问题:有没有办法以补丁的方式动态修复紧急Bug,不再需要重新发布App,不再需要用户重新下载,覆盖安装?

虽然Android系统并没有提供这个技术,但是很幸运的告诉大家,答案是:可以,热补丁动态修复技术可以解决以上这些问题。

对于热修复方案,当前市场有四种解决方案,分别是 Xposed、AndFix、ClassLoader、Proxy/Delegate。前两个是阿里开源的框架,第三个是QQ空间团队想出的对策。在我看来,前两个方案的思路是极其相似的,都是想要通过指针替换掉出Bug的方法、类,不同的是Xposed将需要替换的方法连接到hookedMethodCallback,以他来实现替换后方法的调配,而AndFix就比较简洁粗暴了,直接就是获取需要替换的方法指针,将指针指向修改之后的新的java代码;相比之下,ClassLoader方案便较为巧妙的多了,他巧妙利用了BaseDexClassLoader的机制,类似于dex分包技术。Proxy/Delegate的方案则是使用ProxyApplication动态加载主程序dex。

目录

  1. Xposed
  2. AndFix
  3. ClassLoader
  4. 基于Proxy/Delegate
  5. 四种方案的比较
  6. 基于以上四种方案的开源框架以及比较

一、Xposed

大体上的原理是:

1、首先,我们来理解一个概念——Zygote进程。

在Android系统中,每一个应用程序都是由Zygote进程进行孵化出来的。Zygote进程是由init进程创建而成。Zygote在启动时候,创建了一个Davlik虚拟机实例,每当Zygote进程创建孵化出一个应用程序进程时,都会将这个Daclik虚拟机实例进行拷贝一份,拷贝到新创建的应用程序进程之中,从而使得每一个应用程序进程都有一个独立的Dalvik虚拟机实例。

这样看来,Zygote进程就犹如一个盛产的孕妇,每生产一个孩子,也就是应用程序进程,都会给孩子先打疫苗(拷贝一份Davlik虚拟机实例到应用程序之中),让孩子们能够正常的健康成长。

Zygote进程在启动的过程中,除了会创建一个Dalvik虚拟机实例之外,还会将Java运行时库加载到进程中来,以及注册一些Android核心类的JNI方法来前面创建的Dalvik虚拟机实例中去。注意,一个应用程序进程被Zygote进程孵化出来的时候,不仅会获得Zygote进程中的Dalvik虚拟机实例拷贝,还会与Zygote一起共享Java运行时库。这也就是可以将XposedBridge这个jar包(Xposed的关键jar包)加载到每一个Android应用程序中的原因。XposedBridge有一个私有的Native方法hookMethodNative,这个方法也在app_process中使用。这个函数提供一个方法对象利用Java的Reflection机制来对内置方法覆写。

2、接着,我们再来看看Xposed的关键思想Hook。

Hook的中文翻译是,挂钩、钓钩,比较形象。那么它要用来钓哪一条鱼的呢?根据不同的鱼,我们以便准备不同的鱼饵。

所以,在Android系统启动时候Zygote进程加载的XposedBridge jar包,就有了用武之地。在XposedBridge之中有一个私有的方法——hookMethodNative,它将一个方法作为输入参数并且改变Dalvik虚拟机中对于该方法的定义。它将该方法的类型改变为native并且将这个方法的实现链接到它的本地的通用类的方法。

在hookMethodNative的实现中,会调用XposedBridge中的handleHookedMethod这个方法来传递参数。而这个方法,我们可以称之为鱼竿。它将一个方法对象,也就是我们需要替换的方法或者类,作为输入参数,当然,你可以使用java的反射几只来获取这个方法,不过,这个方法对象,这个输入参数就是我们说的“鱼饵”了,根据鱼饵,我们使用鱼竿,便可以钓起我们想要钓起的大鱼。而大鱼便是所要hook的方法对象Method的指针。得到所需要替换的Method指针之后,因为我们的目的是要替换有bug的方法。所以,我们将得到的指针链接到hookedMethodCallback上面去,并设置该Method指向替换成为的方法,以便hookedMethodCallback可以获取真正期望执行的java方法。

现在所有被hook的方法,都指向了hookedMethodCallback的c方法中,然后在此方法中实现调用替换成为的java方法。

handleHookedMethod这个方法类似于一个统一调度的Dispatch任务分派例程,其对应的底层的C++函数是xposedCallHandler。而handleHookedMethod实现里面会根据一个全局结构hookedMethodCallbacks来选择相应的hook函数,并调用他们的before, after函数。

那么,hook的具体过程是:

1、首先通过DexClassloader(这个会在ClassLoader部分具体说明)来加载所要hook的方法,分析类后,进c++层,拿到要hook的Method类,然后在handleHookedMethod中,通过dvmslotTomethod方法获取所要替换的Method*指针。

1
Method* method = dvmSlotToMethod(declaredClass, slot);

declaredClass就是所hook方法所在的类,对应的object。slot是Method类中,描述此java对象在vm中的索引;那么通过这个方法,我们就获取了c层的Method指针

2、将该方法标记为一个native方法

1
SET_METHOD_FLAG(method, ACC_NATIVE);

3、重定向该方法到hookedMethodCallback,这样当被hook的java方法执行时,就会调到c层的hookedMethodCallback方法。通过meth->nativeFunc重定向MethodCallBridge到hookedMethodCallback这个方法上,控制这个c++指针是无视java的private的。

1
method->nativeFunc = &hookedMethodCallback;

另外,在method结构体中有

1
method->insns = (const u2*) hookInfo;

用insns指向替换成为的方法,以便hookedMethodCallback可以获取真正期望执行的java方法。
现在所有被hook的方法,都指向了hookedMethodCallbackc方法中,然后在此方法中实现调用替换成为的java方法。

3、优点

1)无需重启就可以达到修复bug的目的

2)多个模块可同时进行安装

3)无需修改任何的APK

4、缺点

1)需要Android 的root权限

2)Dexposed不支持Art模式(5.0+),且写补丁有点困难,需要反射写混淆后的代码,粒度太细,要替换的方法多的话,工作量会比较大。  

二、AndFix

1、AndFix的原理就是方法的替换,把有bug的方法替换成补丁文件中的方法。

1.png

注:在Native层使用指针替换的方式替换bug方法,以达到修复bug的目的。

使用AndFix修复热修复的整体流程:

2.png

AndFix,我觉得是阿里开源的自动化程度比较高的热修复框架了。因为你发现,框架、工具已经可以做到帮你查找修改前修改后类的不同,并能够将之打包成为对应的补丁。开发人员所要负责的不过是对于Bug的修改而已。

3.png

修复补丁的具体过程是:

1)我们及时修复好bug之后,我们可以apkpatch工具将两个apk做一次对比,然后找出不同的部分。可以看到生成的apatch了文件。若果这个时候,我们把后缀改成zip再解压开,里面有一个dex文件。反编译之后查看一下源码,里面就是被修复的代码所在的类文件,这些更改过的类都加上了一个_CF的后缀,并且变动的方法都被加上了一个叫@MethodReplace的annotation,通过clazz和method指定了需要替换的方法。

2)客户端得到补丁文件后就会根据annotation来寻找需要替换的方法。从AndFixManager.fix方法开始,客户端找到对应的需要替换的方法,然后在fix方法的具体实现中调用fixClass方法进行方法替换过程。

3)由JNI层完成方法的替换。fixClass方法遍历补丁class里的方法,在jni层对所需要替换的方法进行一一替换。关键代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private void replaceMethod(ClassLoader classLoader, String clz, String meth, Method method) {
try {
String key = clz + "@" + classLoader.toString();
Class<?> clazz = mFixedClass.get(key);
if (clazz == null) {// class not load
// 要被替换的class
Class<?> clzz = classLoader.loadClass(clz);
// 这里也很黑科技,通过C层,改写accessFlags,把需要替换的类的所有方法(Field)改成了public,具体可以看Method结构体
clazz = AndFix.initTargetClass(clzz);
}
if (clazz != null) {// initialize class OK
mFixedClass.put(key, clazz);
// 需要被替换的函数
Method src = clazz.getDeclaredMethod(meth, method.getParameterTypes());
// 这里是调用了jni,art和dalvik分别执行不同的替换逻辑,在cpp进行实现
AndFix.addReplaceMethod(src, method);
}
} catch (Exception e) {
Log.e(TAG, "replaceMethod", e);
}
}

2、优点

1)可以多次打补丁。如果本地保存了多个补丁,那么AndFix会按照补丁生成的时间顺序加载补丁。具体是根据.apatch文件中的PATCH.MF的字段Created-Time。

2)安全性

readme提示开发者需要验证下载过来的apatch文件的签名是否就是在使用apkpatch工具时使用的签名,如果不验证那么任何人都可以制作自己的apatch文件来对你的APP进行修改。
但是我看到AndFix已经做了验证,如果补丁文件的证书和当前apk的证书不是同一个的话,就不能加载补丁。
官网还有一条,提示需要验证optimize file的指纹,应该是为了防止有人替换掉本地保存的补丁文件,所以要验证MD5码,然而SecurityChecker类里面也已经做了这个工作。。但是这个MD5码是保存在sharedpreference里面,如果手机已经root那么还是可以被访问的。

3)不需要重启APP即可应用补丁。

3、缺点

1)不支持YunOS
2)无法添加新类和新的字段
3)需要使用加固前的apk制作补丁,但是补丁文件很容易被反编译,也就是修改过的类源码容易泄露
4)使用加固平台可能会使热补丁功能失效
5)无法添加类和字段

三、ClassLoader

其实我更希望将之称之为MutilDex方案,因为他是基于谷歌的MutilDex项目思想而来,dex拆分是其核心。

1、ClassLoader的原理

首先,在说到这个方案的原理的时候,我们需要先了解一下Android的ClassLoader体系

4.png

由上图可以看出,在叶子节点上,我们能使用到的是DexClassLoader和PathClassLoader,这也是Android中加载类一般使用到的。

首先看下这两个类的区别:

对于PathClassLoader,从文档上的注释来看:

Provides a simple {@link ClassLoader} implementation that operates on a list of files and directories in the local file system, but does not attempt to load classes from the network. Android uses this class for its system class loader and for its application class loader(s).

Android是使用这个类作为其系统类和应用类的加载器,只能去加载已经安装到Android系统中的apk文件。

对于DexClassLoader,依然看下注释:

A class loader that loads classes from {@code .jar} and {@code .apk} files containing a {@code classes.dex} entry. This can be used to execute code not installed as part of an application.

可以用来从.jar和.apk类型的文件内部加载classes、dex文件。可以用来执行非安装的程序代码。Android应用就是用它来加载;

那这两个ClassLoader有与我们的热修复有什么联系?

好吧,我们先来看看代码吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//BaseDexClassLoader:  
@Override
protected Class< ?> findClass(String name) throws ClassNotFoundException {
List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
Class c = pathList.findClass(name, suppressedExceptions);
if (c == null) {
ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);
for (Throwable t : suppressedExceptions) {
cnfe.addSuppressed(t);
}
throw cnfe;
}
return c;
}

由上述函数可知,当我们需要加载一个class时,实际是从pathList中去需要的,查阅源码,发现pathList是DexPathList类的一个实例。好,接着去分析DexPathList类中的findClass函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 public Class findClass(String name, List<Throwable> suppressed) {      
for (Element element : dexElements) {
DexFile dex = element.dexFile;
if (dex != null) {
Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}

那么我们开始有点头绪了。我们在使用类的时候,会让PathClassLoader或者DexClassLoader对该类进行类加载。而加载的过程就是,遍历一个装在dex文件(每个dex文件实际上是一个DexFile对象)的数组(Element数组,Element是一个内部类),然后依次去加载所需要的class文件,直到找到为止。

BaseDexClassLoader中有个pathList对象,pathList中包含一个DexFile的集合dexElements,而对于类加载,就是遍历这个集合,通过DexFile去寻找。一个ClassLoader可以包含多个dex文件,每个dex文件是一个Element,多个dex文件排列成一个有序的数组dexElements,当找类的时候,会按顺序遍历dex文件,然后从当前遍历的dex文件中找类,如果找类则返回,如果找不到从下一个dex文件继续查找。

而这里面就正好让我们有机可乘。对,也就是理论上,如果在不同的dex中有相同的类存在,那么会优先选择排在前面的dex文件的类,如下图:

5.png

在此基础上,我们觉得可以用来作为热补丁的方案。把有问题的类打包到一个dex(patch.dex)中去,然后把这个dex插入到Elements的最前面,这样,在类加载的过程中便会优先加载补丁包中dex的类,如下图:

6.png

嗯,原理大致就是这样。

但是,在实现过程中,我们还会遇到一个CLASS_ISPREVERIFIED问题。

什么是CLASS_ISPREVERIFIED问题?

首先我们来看看一下代码:

7.png

这段代码是dex转化成odex(dexopt)的代码中的一段,我们知道当一个apk在安装的时候,apk中的classes.dex会被虚拟机(dexopt)优化成odex文件,然后才会拿去执行。

虚拟机在启动的时候,会有许多的启动参数,其中一项就是verify选项,当verify选项被打开的时候,上面doVerify变量为true,那么就会执行dvmVerifyClass进行类的校验,如果dvmVerifyClass校验类成功,那么这个类会被打上CLASS_ISPREVERIFIED的标志。这样看来,打上CLASS_ISPREVERIFIED其实是Android的一种性能优化方式。

那么具体的校验过程是什么样子的呢?
此代码在DexVerify.cpp中,如下:

8.png

概括一下就是如果以上方法中直接引用到的类(第一层级关系,不会进行递归搜索)和clazz都在同一个dex中的话,那么这个类就会被打上CLASS_ISPREVERIFIED。

而被打上CLASS_ISPREVERIFIED之后,在ClassLoader加载与需要修改类相关的相关类时候,却发现他们两个却不是来自于同一个dex的,这个时候就会抛出异常了。

所以我们接下要做的就是阻止相关类被打上CLASS_ISPREVERIFIED标记。

一般采用的方案是往所有类的构造函数里面插入了一段代码,代码如下:

1
2
3
if (ClassVerifier.PREVENT_VERIFY) {
System.out.println(AntilazyLoad.class);//需要修改的类
}

这样,需要与修改的类以及其相关类,在进行校验的时候,因为修改后的类所在的dex与原来的dex不同,也就是与相关类的dex不同,这样,他们就不会被打上CLASS_ISPREVERIFIED了。

为了实现以上操作,QQ空间采用了javassist动态代码注入。用javassist将这个类在编译打包的过程中插入到目标类中。

大致的流程是:在dx工具执行之前,将LoadBugClass.class文件呢,进行修改,再其构造中添加System.out.println(dodola.hackdex.AntilazyLoad.class),然后继续打包的流程。注意:AntilazyLoad.class这个类是独立在hack.dex中。

总结下,其实我们需要做的就是两件事:

1、动态改变BaseDexClassLoader对象间接引用的dexElements;

2、在app打包的时候,阻止相关类去打上CLASS_ISPREVERIFIED标志。所有与该类相关的类都需要进行动态注入,可进行修改,也是需要进行fix的对象。

四、基于Proxy/Delegate

当项目有加壳子,插件化或热修复等需求的时候,可以使用Proxy/Delegate Application框架的方式。

在正常的模式中,一个程序一般只有一个Application入口,而Proxy/Delegate模式中需要有两个Application,原程序的Application改为Delegate Application,再新加一个Proxy Application,由Proxy Application 提供一系列的个性化定制,再将所有的context和context相关的引用全部转化为Delegate Application的实例,让外界包括Delegate Application自身都以为该App的Application入口就是Delegate Application.

采用的是Proxy/Delegate Application框架。

主要实现的流程:

1、替换主程序dex文件为代理启动程序的dex文件

2、代理启动程序启动后,动态加载主程序dex

3、ProxyApplication替换消除本身Context引用为MyApplication

4、启动主程序的Application.

经过上述的步骤处理,由于程序启动dex只是一个代理,而主程序的dex是动态加载的,所以就可以达到不升级主程序不更改版本号只升级dex文件来修复线上紧急bug的目的.

五、四种方案的比较

  1. Xposed方法生成补丁难度较大,需要反射写混淆后的代码,粒度太细,如果替换方法很多的话,工作量巨大。
  2. AndFix支持2.3 - 6.0系统,但JNI不像JAVA那样标准,所以偶尔会有未知的机型异常。从实现来说,类似Xposed,通过JNI来替代方法,更加简洁的完成补丁修复,应用PATCH不需要重启。但由于实现上直接跳过了类初始化,设置为初始化完毕,所以静态函数、静态成员、构造函数都会出现问题,复杂的类Class.forName很可能直接崩溃。
  3. ClassLoader方案支持2.3 - 6.0系统,对启动速度会有略微影响,而且只能下次启动生效。
  4. Proxy/Delegate方案,对于dex的要求比较高,如果DEx文件较为庞大,启动速度会变慢。

相比而言,ClassLoader方案较为可靠,但是如果Dex文件较为庞大,启动速度会变慢;而AndFix则适用于应用不重启即可修复,且方法够简单;Xposed方案较为复杂,暂不考虑。

六、基于以上四种方案的开源框架以及比较

1、Dexpost:

1)原理:在底层虚拟机运行时hoop方法;

2)缺点:适配方面存在一些问题,目前不支持android6.0,5,1;art运行时;

3)优点:无需重启就可以达到修复bug的目的;

2、AndFix:

1)原理:在Native层使用指针替换的方法替换bug方法,达到修复bug的目的;

2)缺点:

底层替换,稳定性方面可能需要实际检测;

不支持YunOS;

无法添加新类和新的字段;

需要使用加固前的apk制作补丁,但是补丁文件很容易被反编译,也就是修改过的类源码容易泄露;

使用加固平台可能会使热补丁功能失效;

无法添加类和字段;

3)优点:无需中期就可以达到修复bug的目的;

3、HotFix:

1)原理:通过替换类加载器中bugclass,达到修复bug的目的;

2)缺点:需要重新启动才可以修复bug;

3)优点:java运行层修复,稳定性较好;

4、Nuwa:

1)原理:通过替换类加载器中bugclass,达到修复bug的目的;

2)缺点:需要重新启动才可以修复bug;

3)优点:java运行层修复,稳定性较好;自动化热修复

5、DroidFix:

1)原理:通过替换类加载器中bugclass,达到修复bug的目的;

2)缺点:需要重新启动才可以修复bug;

3)优点:java运行层修复,稳定性较好;

6、dynamic-load-apk

1)缺点:不支持Service和BroadcastReceiver;迁移成本高,需要修改插件,插件app需要继承自proxyActivity

2)优点: 插件无需安装host即可吊起;支持R访问插件资源;插件支持Activity和FragmentActivity;基本无反射调用;插件安装后任可独立运行

7、Droid Plugin

1)缺点:无法使用自定义资源的通知;法注册一些特殊Intent Filter的组件(四大组件);对Native支持不好

2)优点:插件无需任何修改,可独立安装运行,也可以做插件运行;四大组件无需在Host程序注册;超强隔离性,不同插件运行在不同的进程中;资源完全隔离;实现进程管理,插件的空进程会被及时回收,占用内存低;插件的静态广播会被当作动态处理,如果插件没有运行,静态广播永远不会触发;API侵入性低

8、DynamicAPK

1)优点:
迁移成本低(无需做任何activity/fragment/resource的proxy实现)不使用代理来管理插件的activity/fragment的生命周期。修改后aapt会处理插件种的资源,R.java中的资源引用和普通Android工程没有区别,开发者可以保持原有的开发规范;

更加有利于并发开发;

提升编译速度;

提升启动速度。

2)缺点:

dex解压、dexopt、加载耗时较长,使用按需加载启动时间过长

数据_陈越威_中南大学_18175139250
yalechen
(+86) 181-7513-9250
yalechen@163.com
Wechat: cyw20130609
Seeking for a data analysis engineer position
EDUCATION
Central South University
Sep.2013-Jul.2017.07
Bachelor in Automation Professional
SKILLS
Computer Language: Java, R, Python
Data Analysis Software: Hadoop, Matlab, R
Version management tools: SVN, GitHub
Algorithm: Machine Learning Algorithm
Operating Systems: Linux, Windows
PROJECT EXPERIENCES
OriStone Programming Language
2015-2016
Principal: Originally develope.
OriStone is a scripting language based on Java, which is object-oriented, and has its own VM.
Base on BNF paradigm, and regular expression parsing.
The language compiler, interpreter, grammatical structure and VM, have already been constructed.
Entrepreneurial team of FindThings App
2014-2016
CTO and Android development engineer
To meet the demand for skills and matching inside the College, the team, an intermediary platforms, including Android App and website, was established. 
The project won the second prize of the Innovation, Entrepreneurship and Creativity Contest.
I am responsible for the speech, looking for investment. 
Use technology development frameworks Volley.
Rescue Simulation Group of RoboCup Competition 
2015-2016
Core member
Based on photoelectric sensor and MCU, automatically patrol movement and the intelligent car on a particular task
Write operation algorithm of agents. Use KNN algorithm for partition, A * algorithm for searching path and so on.
Won the second prize of RoboCup in the world, and won the first prize in the national competition of RoboCup.
INTERN EXPERIENCE
Qutke Ltd. in Beijing
Dec.2015-Feb.2016
R Programming Language intern
Optimized code about the Internet banking, and wrote the blog about how to use the API.
Pipa Planet Ltd. in Changsha
Aug.2015-Sep.2019
All Stack Engineer
Responsible for Web Development.Use the technical framework of bootsrap.
BLOG: cyw3.github.io/yalechen
HOBBIES
Sing songs, traveling, skating, Eason,the book of Three Body, AI.

申万行业分类日间表格

前言

申银万国证券股份有限公司(简称:申银万国),是国内最早的一家股份制证券公司,也是目前国内规模最大、经营业务最齐全、营业网点分布最广泛的综合类证券公司之一。为能够实时掌握申银万国各行业的股市数据,包括当天收盘价、成交量、涨跌幅等,以应对变化多端的市场,采取正确的举措,收获最大的利益,所以一个能够实时生成申万行业分类日间表格是十分需要的。接下来,我们便使用况客Api实现这个的表格。

目录

1、申万行业分类日间表格的介绍

2、使用况客API实现的具体思路

1.申万行业分类日间表格的介绍

在开始动手之前,我们先看看我们要制作的成品是什么样子的吧。如下图:

上图,是搜索的申万行业一级行业中各个个股当天的基本数据,包括了当天收盘价、成交量、涨跌幅、周涨跌幅、月涨跌幅、年涨跌幅等等。(注:此处搜索的申万行业一级行业是“商业贸易”,即SW1 <- ‘商业贸易’)当然,你也可以根据需要选定表格的内容。

从表格,我们可以了解到我们关注的个股的情况,看他目前是张还是跌,涨跌幅多大,通过比较周涨跌幅、月涨跌幅、年涨跌幅来判断该个股是否值得投资,以更好的做出正确的判断、举措。

2.使用况客API实现表格的具体思路

1、系统环境

1
2
3
win7
RStudio
qutke

需要提前下载况客R语言Api,即qutke,GitHub链接是:https://github.com/qutke/qutke
可以安装注册git,通过git命令来下载相应的R包。

1
git clone https://github.com/qutke/qutke.git 

也可以通过RStudio命令来下载:

1
2
library(devtools)
install_github('qutke/qutke')

2、咱们需要初始化,为正式编写代码做个准备吧,就像木匠一样,先把原材料以及工具准备好,才干活是不。其中,我们需要安装需要使用的R包,这里我们要用得到的有与date日期相关的lubridate包,以及我们刚下载的qutke。接着,通过数据库key,我们进行初始化,并与数据库连接上。

1
2
3
4
5
#安装相应的需要使用的R包
library('lubridate')
library(qutke)
key<-'ff5ed58edf645c6581e8148db1130dc310fbab5fdccc4b2a9ea0be30f4128ace'
init(key)

3、定义变量,取得需要的参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
date <- Sys.Date()
#获取交易日期,虽然在init(key)函数里面就已经取得了tradingDay,并且保存在e$TRADINGDAY变量中。
#不过这里为了介绍getDate()函数。于是就写明了吧。
lastYearDate <- as.Date(paste(year(date)-1,month(date),day(date),sep='-'))
tradingDay <- getDate(data='tradingDay',startdate=lastYearDate,enddate=date,key=key)
length <- length(tradingDay)
date <- tradingDay[length]
#进行筛选,判断其SW1,此时只对‘商业贸易’这一行业进行筛选
sw1 <- '商业贸易'
#通过getIndustry()函数,取得SW1,即‘商业贸易’这一行业相对应的个股
industry <- getIndustry(data='industryType',date=date,SW1=sw1,key=key)
#符合条件的个股的股票代码
qtid <- industry$qtid

#获得今年的第一个交易日日期
FirstDay <- as.Date(paste(year(date)-1,'1','1',sep='-'))
FirstDay <- (getDate(data='tradingDay',startdate=FirstDay,enddate=date,key=key))[1]

4、依照股票代码qtid来筛选获取数据库中这些个股的数据,并进行计算。此处,我们可以直接获得当天的收盘价、成交量,但是涨跌幅是需要进行计算的。
涨跌幅的计算公式是:(当前最新成交价(或收盘价)-开盘参考价)÷开盘参考价×100%
一般情况: 开盘参考价=前一交易日收盘价
除权息日: 开盘参考价=除权后的参考价
我们参考以上涨跌幅的计算公式,可以计算当天的涨跌幅,至于周涨跌幅,则是要把开盘参考价改为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
#选用不复权
#通过getDailyQuote()方法获取股票日间行情。
mktDaily <- getDailyQuote(data='mktDaily',qtid = qtid,startdate=date,enddate=date,key=key)
qtid <- mktDaily$qtid
#个股证券名称
SecuAbbr <- (getIndustry(data='industryType',qtid = qtid,date=date,SW1=sw1,key=key))$SecuAbbr
#当日收盘价
close<- mktDaily$close
#当日交易量
volume <- mktDaily$volume
#上一交易日的收盘价
prevClose <- mktDaily$prevClose
#当日涨跌幅
quoteChangeDaily <- (close-prevClose)/prevClose
#获取近期的数据,分别为近一周、近一月、近一年、年初
mktWeek <- getDailyQuote(data='mktDaily',qtid = qtid,startdate=tradingDay[length-5],enddate=tradingDay[length-5],key=key)
mktMonth <- getDailyQuote(data='mktDaily',qtid = qtid,startdate=tradingDay[length-20],enddate=tradingDay[length-20],key=key)
mktYear <- getDailyQuote(data='mktDaily',qtid = qtid,startdate=tradingDay[1],enddate=tradingDay[1],key=key)
mktFYear <- getDailyQuote(data='mktDaily',qtid = qtid,startdate=FirstDay,enddate=FirstDay,key=key)
#通过公式计算涨跌幅
#近一周的涨跌幅
quoteChangeWeek <- (close-mktWeek$close)/mktWeek$close
#近一月的涨跌幅
quoteChangeMonth <- (close-mktMonth$close)/mktMonth$close
#近一年的涨跌幅
quoteChangeYear <- (close-mktYear$close)/mktYear$close
#年初至今的涨跌幅
quoteChangeFYear <- (close-mktFYear$close)/mktFYear$close

5、生成表格data.frame

1
2
3
4
5
6
7
8
stock1 <- data.frame('1'=qtid,'2'=SecuAbbr,'3'=paste(year(date),month(date),day(date),sep='-'),'4'=close,
'5'=quoteChangeDaily,'6'=volume,'7'=quoteChangeWeek,
'8'=quoteChangeMonth,'9'=quoteChangeYear,
'10'=quoteChangeFYear,'11'=sw1)
#修改data.frame的列名
names(stock1)<-c('代码','名称','日期','收盘价','涨跌幅(%)','成交量(万元)','周涨跌幅(%)','月涨跌幅(%)','年涨跌幅(%)','年初至今涨跌幅(%)','SW1')
#发送到况客的数据服务器中,服务器将保存你生成的dataframe,并可以在况客投研平台看到可视化界面
postData(stock1,name='stock1',key=key)

6、登录况客投研平台,在 创建图表 选项中对生成的图表进行修改,并增加涨跌提示、搜索、排序等功能,然后可以将表格嵌入网站、博客之中,进行展示。

投资组合的收益率走势图

前言

各位看官好,我是小陈,今天又来献丑了。作为金融小白,小陈我最近一直在恶补投资方面的知识。而前些天,正好涌哥叫我用况客API实现一个投资组合的收益率走势图,感觉自己一身代码神功又有用武之地了。

所谓投资组合是说,投资持有各种金融产品的组合,需要分段分批分散投入,目的是分散风险。当然,投资人都是希望自己投的股票能够不断的涨,把钱花在刀刃上,以最少的钱套取最大的利益,但是这是不现实的,毕竟股市,尤其是中国股市(大家都懂的),是变化莫测的。所以,如何尽可能的减少风险,利益最大化,成为了关键所在。而接下来我将说到的“收益率走势图”,将会是一个不错的工具,让我们趋利避害。

目录

1.投资组合以及收益率走势图的介绍

2.收益率走势图效果图展示

3.收益率走势图的代码实现以及优化

1.投资组合以及收益率走势图的介绍

美国经济学家马考维茨(Markowitz)曾这样说过:“若干种证券组成的投资组合,其收益是这些证券收益的加权平均数,但是其风险不是这些证券风险的加权平均风险,投资组合能降低非系统性风险。”

马考维茨最著名的作品便是《投资组合理论》,他还因此获得了诺贝尔经济学奖。当然,本文的重点不是说要探讨马考维茨的“投资组合理论”云云(其实我也不懂)。但是这也足够说明了,合理的投资组合,能够让投资者获取更大的利益。投资者持有多种金融产品,不同的产品可能具有不同的市场地位和价值优势,需要综合评价企业的价值能力,进行投资组合分析。至于收益率走势图,显然是我们进行投资组合分析时的一大工具了。

收益率是指投资的回报率,一般以年度百分比表达,根据当时市场价格、面值、息票利率以及距离到期日时间计算。对公司而言,收益率指净利润占使用的平均资本的百分比。而通过收益率走势图,我们可以实时地知道,我们所持股票的自买入以来的收益情况,以及可以大体预测股票的大致走向,如果辅以好的算法的话,甚至可以如同化身“预言家”,对未来股票走势了如指掌。

2.收益率走势图效果图展示

哎呀,我也不说什么太虚的了。直接开干。以下便是我们代码实现的效果图了。怎么样?很棒吧!觉得羡慕吧!羡慕就继续往下看,走起。yieldChart

3.收益率走势图的代码实现以及优化

1.系统环境

跟上一篇文一样,我们需要准备好开发所需要的环境。就像学习一样,得要有个好的学习氛围嘛。

1
2
3
win7系统
RStudio编辑器
qutke包

需要提前下载况客R语言Api,即qutke,GitHub链接
可以安装注册git,通过git命令来下载相应的R包。

1
git clone https://github.com/qutke/qutke.git 

也可以通过RStudio命令来下载:

1
2
library(devtools)
install_github('qutke/qutke')

2.初始化,弹药准备

1
2
3
4
5
#安装相应的需要使用的R包
library('lubridate')
library(qutke)
key<-'ff5ed58edf645c6581e8148db1130dc310fbab5fdccc4b2a9ea0be30f4128ace'
init(key)

3.参数选定

在制作走势图之前,我们得知道,是什么样的组合,什么时候买入的。这里,我参考了雪球里的一个热门组合“丁丁丁涨不停ZH078564”,分别是科大讯飞“002230.SZ”,以及登云股份“002715.SZ”。日期就选在“2015-10-01”吧。

1
2
qtid <- c('002230.SZ','002715.SZ')
date <- '2015-10-01'

4.获取股票基本信息

依次获取这两支股票的从买入日期至今的每天的收盘价。

1
2
3
4
5
6
7
8
9
#股票基本信息
md <- getMD(data='keyMap',qtid=qtid,key=key)
#股票日间行情(前复权)
dailyQuote <- getDailyQuote(data='mktFwdDaily',qtid=qtid,startdate=date,enddate=Sys.Date(),key=key)
#当日收盘价(前复权)
stock1 <-dailyQuote[which(dailyQuote$qtid==qtid[1]),]
stock2 <-dailyQuote[which(dailyQuote$qtid==qtid[2]),]
#x轴,作为时间轴。取买入日期至今。
tradingDay <- getDate(data='tradingDay',startdate=stock1$date[1],enddate=Sys.Date(),key=key)

5.数据处理,以备后面生成dataframe表格。此时,fwdAdjClose是每只股票在每一个交易日的收盘价。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#归一化
i <- 1
length <- length(tradingDay)
fwdAdjClose1 <- c()
fwdAdjClose2 <- c()
while(i<=length){
fwdAdjClose1 <- c(fwdAdjClose1,stock1[i,]$fwdAdjClose)
if(is.null(fwdAdjClose1[i])||is.na(fwdAdjClose1[i]))
fwdAdjClose1[i] <- fwdAdjClose1[i-1]
fwdAdjClose2 <- c(fwdAdjClose2,stock2[i,]$fwdAdjClose)
if(is.null(fwdAdjClose2[i])||is.na(fwdAdjClose2[i]))
fwdAdjClose2[i] <- fwdAdjClose2[i-1]
i=i+1
}

6.收益率计算

收益率计算公式是:

某只股票收益率 = (当天的股价-买入价)/ 买入价 *100

1
2
3
#计算
fwdAdjClose1 <- (fwdAdjClose1-fwdAdjClose1[1])/fwdAdjClose1[1]*100
fwdAdjClose2 <- (fwdAdjClose2-fwdAdjClose2[1])/fwdAdjClose2[1]*100

7.生成dataframe表格,并将数据发送到况客投研中心,以便后期处理

1
2
3
4
#形成dataframe
yieldChart <- data.frame('date'=tradingDay,'2'=fwdAdjClose1,'3'=fwdAdjClose2)
names(yieldChart)<-c('日期',md[1,]$ChiAbbr,md[2,]$ChiAbbr)
postData(yieldChart,name='yieldChart',key=key)

8.代码优化

当然,我们不能就这满足了。我们需要在对代码略微修改,使之只需要我们输入交易日期、股票组合,就可以自动生成需要的收益率走势图。

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
getYieldChart<-function(date,qtid=c(),key){
if(length(qtid)<=0){
stop("Qtid is more than one.")
}
#股票基本信息
md <- getMD(data='keyMap',qtid=qtid,key=key)
#股票日间行情(前复权)
dailyQuote <- getDailyQuote(data='mktFwdDaily',qtid=qtid,startdate=date,enddate=Sys.Date(),key=key)
#当日收盘价(前复权)
stock <- list()
qtidlength <- length(qtid)
i <- 1
while(i<=qtidlength){
ChiAbbr <- md[i,]$ChiAbbr
qtidSt <- md[i,]$qtid
stock[[ChiAbbr]] <- dailyQuote[which(dailyQuote$qtid==qtidSt),]
i <- i+1
}
#x轴,作为时间轴。取买入日期至今。
tradingDay <- getDate(data='tradingDay',startdate=stock[[1]]$date[1],enddate=Sys.Date(),key=key)
fwdAdjClose <- list()
fwdAdjClose[['date']] <- tradingDay
#归一化
i <- 1
length <- length(tradingDay)
while(i<=qtidlength){
ChiAbbr <- md[i,]$ChiAbbr
j <- 1
while(j<=length){
fwdAdjClose[[ChiAbbr]] <- c(fwdAdjClose[[ChiAbbr]],stock[[ChiAbbr]][j,]$fwdAdjClose)
if(is.null(fwdAdjClose[[ChiAbbr]][j])||is.na(fwdAdjClose[[ChiAbbr]][j]))
fwdAdjClose[[ChiAbbr]][j] <- fwdAdjClose[[ChiAbbr]][j-1]
j <- j+1
}
#计算
fwdAdjClose[[ChiAbbr]] <- (fwdAdjClose[[ChiAbbr]]-fwdAdjClose[[ChiAbbr]][1])/fwdAdjClose[[ChiAbbr]][1]*100
i=i+1
}
#形成dataframe
yieldChart <-data.frame(fwdAdjClose)
postData(yieldChart,name='yieldChart',key=key)
}

这样,就算完成了。(满意脸)

yalechen's Resume

陳越威


联系方式


个人信息


项目经历

湘雅生物大数据项目

使用中南大学湘雅医学院内部数据集,分析处理各有机物质的性质特征。我在此项目负责了java蛋白质分子解析平台的工作,在数据分析这一块成长快。

“找事er”App创业项目

我在此项目负责了Android App开发工作,在Android与ThinkPHP后台的数据收发这一块做得出色。这个项目中,我最自豪的技术细节是,我能够连续7天每日每夜的进行开发工作,为的只是想要让我们的产品尽快的落地。

OriStone语言开发项目

这是我自己想要做的项目。然后确定开始做这个项目之后,我每天泡在图书馆里面,利用图书馆的资源,在图书馆里面完成了OriStone编程语言的开发。一个属于自己的语言。在这个项目中,我遇到的最大的困难是,如何构建VM。本来是想要把OriSone架构在LLVM框架之上,以此降低开发难度。但是,为了学习其中的原理,我就自己写了个VM。

RoboCup移动智能机器人大赛

这个系统是仿真地震过后,城市里面医疗、警察、火警,尽最大可能拯救市民,救火,以得到最大分数的比赛。主要是编写各智能体的运作算法。我负责的是其中医疗智能体的控制算法。对其使用了KNN算法进行分区,A* 算法进行路径搜寻等等。在2015年7月份带领团队在RoboCup世界杯取得世界第六的好成绩,又于10月份取得全国第三名的成绩。

CCV项目

基于OpenCV框架,完成的银行卡号识别项目。这是我单独完成的项目。其中的难处在于数字的提取策略。后来我使用的基于灰度变化的数字提取方法:数字与数字的间隔往往是背景颜色或者是背景像素点个数和的一个波峰,即前景像素点个数和的一个波谷,我们利用这样的特点来分割图片。


开源项目和作品

开源项目

技术文章

技能清单

以下均为我熟练使用的技能

  • 语言:java/R/Python
  • Web框架:ThinkPHP/Spring/Struct/Hebernate
  • 数据库相关:MySQL/Hbase
  • 版本管理、文档和自动化部署工具:Svn/Git
  • 常用OS:Linux/Windows
  • 算法:机器学习/算法导论
  • 云计算:hadoop/Spark

致谢

感谢您花时间阅读我的简历,期待能有机会和您共事。

You need to set client_id and slot_id to show this AD unit. Please set it in _config.yml.