而立之年,未开始的创业路

Catalogue
  1. 1. 题记
  2. 2. A君
  3. 3. 聊聊周围
    1. 3.1. B君
    2. 3.2. C君
    3. 3.3. D君
  4. 4. 这两年的一面之缘
    1. 4.1. E君
    2. 4.2. F君
    3. 4.3. G君
    4. 4.4. H君
    5. 4.5.
  5. 5. 回到自己
  6. 6. 到你了
  7. 7. 后记

题记

“现代人不是渴望成功,而是渴望马上成功。”

前几天同一个创客类培训机构合伙人就合作事宜巴拉吧啦,一句话刺激到了小小的心脏,加上90后创业者的遍地开花🌼,似乎些许浮躁,香菇难受,辗转反侧,诸多思考,有了此文。

A君

先从这位创业者(简称A君)说起吧。
A,一位87年小伙,13年研究生毕业后校招进鹅厂,14年深圳宝安2w- 买了房解决了第一个生存问题,15年在一个老板给的200w启动资金下,与几个同事还有以前的同学一起创业,路演获取前海梦工厂创意园办公基地免费入驻…
他想让我加入他们培训体系中某个领域所谓的“权威人士”, 录制线上视频并兼线下培训高级讲师,与我之前合作过的几家大型线上/线下培训机构不同,聊天中他一味的强调他们平台多么多么N,可以将我包装成这个领域绝对的权威,签署排他协议,可以通过他们平台获得很好的声誉,还可以在它们的专栏中出售自己书籍/作品,当报名学员比较多时组织线下培训,培训费用以某个比例分成巴拉巴拉…说白了,没有实际利益,大饼的方式加上我个人时间确实差不多饱和,所以当场直接婉拒了。
然后继续聊他们的创业,讨论了一些关于创业的观点,巴拉巴拉,然后谈到我自己时,我说我的创业路一定会前行,但现在想先满足或者说解决部分生活上的财务自由再开始…他给我一句话刺到了我,“创业的话,真的不年轻了…”,然后提到他们公司现在招聘的都是90后,93/94年小伙子,有冲劲,技术也不错,Github上开源项目star也众多…面试时问 “大家技术都不错,相比别人,我们为什么要录用你而不是别人?”,”年轻资本,熬夜资本…” 这样的答复A自己都笑了,确实很现实,创业公司不多一份付出也不现实(说的有点委婉了),熬夜我们肯定熬不过他们年轻人,这是他们的资本,就如现在邀请我作为他们讲师也一样,这个领域让这群90后去啃,给一定的时间也完全不会有问题,他希望可以找一个既有地位的人来负责比较好(真厉害又被绕进去了)…后面还聊了很多,交换了微信和名片,嘱咐不要急着给答复回去权衡思考下,同他们一起把这件事有意义的事干一干…巴拉巴拉,又一大堆,这些都不重要了,我的思维还停留在他前面的那句话,“创业的话,真的不年轻了…”,是的,打算创业,30,而立之年,创业还未真正开始,慌?盲?迟? 创业,真的想清楚了?

聊聊周围

B君

B君,大学同学,14年离职后同三个同事创立了一家停车场相关公司,自己负责技术,其它三个负责资金和市场,股份平均(创业禁忌)。他们四人每人每月6k生活补贴,其它所有资金都作为储备资金,风风火火的干了一年,团队也从四人做到了十来个,月账面流水也从几万到几十万。
15年,B选择退出,以股份比例分到了一大笔钱。自己注册了另外一家公司,还是做原来的产品,跑华强北购买元器件,找PCB板商画板贴片再招人包装,然后跑市场积累客户,一年多下来,毛收入做到了200w。
现在的他,每天就是进货出货,跑跑市场谈谈主要客户,自由时间大把大把的,每天打打球陪陪小孩,从来没有10点前起过床,也经常找我聊聊是否有其它扩展门路,结合互联网如何来做巴拉巴拉…
说说他这个产品吧,很简单的一个小硬件产品,停车远距离刷卡,读头+卡片,主要赚取卡片插差价。14年15年很多次跟我聊天,说心里话,我压根就看不起这样的产品,这样的所谓“创业”,我跟他聊天灌输的思想一定是高大上的,一定是平台级的,一定是结合互联网,一定是大大的XX。现在,我会发现,错了,我有很多很多很好ideal,我有很好的互联网思维,我拥有技术,但创业或许是个伪命题,只是一个概念,那时的我纯粹的认为创业真的只是一种情怀,是为了情怀,从来没考虑过商业化的思想,突然感觉好像错了,创业,谈情怀之前先谈利益,赚到钱了能盈利才是企业,才是创业,才是中国现社会追从的成功人生。 是么?

C君

C君,同校师兄,08年毕业来到深圳,从SCM到DSP到ARM,从普通工程师到现在某企业(中小企业,100多号人)总技术负责人,拥有企业的股份。他没有所谓的创业,但却能实现100w+的年收入,当然,单纯工作工资肯定没这么多,他在很早时通过别人介绍认识了他现在做的这个行业中某位台湾老板,当时完全不计较得失没日没夜的给他绘图做板开发,最终成为了长期合作伙伴维持并到现在,他们的产品在台北市拥有近乎1/3的市场…是的,他以工作和兼职的方式也在深圳立足下来,农村出生,今年(2016年喔)在深圳南山刚换了第二套房。 每次跟他爬塘朗山,聊天中会发现他是那种慢性子,做事有条不紊,绝不会去追求冒险。我不止一次问他为什么不创立一家公司,招一点人把事做大,他每次都笑一笑说道,成立公司又能怎么样呢?能赚的更多么? 当时不理解,觉得太势利,目光看不远,只在乎短期利益,现在想想,其实也不然,雄浑大志之前一定也要脚踏实地,现在大多数互联网创业者,用所谓的互联网思维去创业,成天谈的就是融资圈钱甚至上市,通过融资去圈用户,然后再考虑如何盈利,是的动辄几百万甚至上千万融资(独角兽类公司毕竟是少数,我们需要那样的实例和憧憬,但短期还是先从产品做起),自身也华丽转身,转身之后呢?我希望的创业也是就酱紫的?

D君

D君,初中同学,未读大学,职高毕业就来到了南广州,从事会展相关设计工作,前面和行业一个朋友一起成立了一家会展相关公司,承办国内外中小型会展设计与布展,目前公司十多个人,一部分是设计师,公司每年也有好几百w的毛收入。我们聚的不是很多,寥寥几次都是记忆深刻,总是带着我出入一些作为码农不太可能涉入的场合,such as夜总会、酒吧、棋牌室等,一群人聊的除了生意就是女人…这也是身边的一个创业者,有可以自己赖以生存的小公司,用他自己的话说反正比打工强。

是的,上面介绍的都是身边的熟人朋友,做的事并不是纯互联网,或实业或者硬件相关,或许他们观点不太正确,甚至有点out,有点跟不上或者脱离我们现在吹的天花乱坠的互联网+的时代,但是,他们至少在他的那领域暂时可以说是成功的,获取了财富,获得了自己想要的,还有自由之身。当然,我周边也有一些互联网创业朋友,天天谈创业,谈融资,天天吹,很多都处在或惆怅融资或惆怅产品阶段,对此不评价…

这两年的一面之缘

这两年有太多创业者找我聊天,有过太多一面之缘,微信上有近乎150+个创业者信息,或创始人或CEO或CTO或合伙人或投资者。看了很多项目,聊了很多项目,也更加发现项目本身真的不那么重要,创业也真的想象中的那么高大上,简单聊聊几个。

E君

E君,一家传统行业的老板,有过行业多次创业经验,15年辞职到现在的公司任执行总裁,创始人是拥有多个摄影基地,拥有雄厚资本,一起创立的公司天使轮就2000w的融资,租赁了比较豪华奢侈有讲究的office,每次过去都是会在他们奢侈办公室喝茶聊项目,然后晚上入驻酒店。我给他们做了几个月的技术负责人,虽然一直要求我去全职入职做他们的技术总监/CTO,综合考虑后最终还是婉拒了。谈谈这个公司吧,才开始技术(双端App+服务端+Web页+公众号 全平台)是外包的,我加入的时候外包已经开始了,我跟进时发现外包公司技术很low,有点忽悠的感觉,招聘应届生来写不熟悉的代码。后面为了进度(定在某个时间需要新闻发布会,准备A轮融资),采取了一方面外包开发继续,一方面组建自己的技术团队,然后再通过自己技术团队逐步将外包可用代码和需求迭代过来,整个过程还是挺艰辛的,最终产品稍许delay后基本按期凑合发布了(发布前一晚还在熬夜赶业务调整需求,没法留出时间做任何性能或质量上的测试等等)。其实按道理说这是一个很不错的经历,团队是我自己组建的,各方面都还不错,但中间过程中我逐渐发现,总监E和Boss都是传统行业人,都不懂技术而且都不太重视技术,他们眼里技术只是为了完成他们这个App或者某个功能,很多时候沟通起来真费劲甚至有些可笑,我的那只团队成员中间近乎3个月没怎么休息过(有一个月全月都在工作,团队成员都单独找我私聊,我用各种理由把E说服后,后面才好些),每天加班赶需求改需求,纵然我给他们挡了很多,给E立了很多项目规范,建立需求池,培训敏捷迭代开发…但,很多都只是暂时的,很难建立一套真正的互联网化敏捷化的项目过程,甚至都很难以项目的方式推进…整个过程下来逐渐发现其实这不是自己想要的创业,想要的事业,想要的一起奋斗的团队,加入后也不会有太大改观,领导的思维很重要,有些固有思维几乎是不可该改变的,确实,纵然,资金+资源这块是E公司最大的优势(这也是我见了几十家创业公司中资源和资本最充足的),但拼凑的团队以及太多传统行业的束缚以及对技术的不重视,会觉得这家企业不会走很长远,不会做的足够大,这是我当时的判断。现在这家公司线下还是做的火热火热的(整合和合作了众多线下一线品牌加盟商),几个月了线上却还只有几千的用户量,日活也少的可怜,比起我自己做的一些App都少很多。是的,创业,团队和资源同样重要,创始人的思维更重要,有什么的思维注定了可以你的团队,你的企业,你的事业可以走多远,所以,创业者的思维,你准备好了么?

F君

F君,一家3D打印平台的CEO/创始人,她在这个行业一直做了近20年,对整个相关联的实体了解颇深(3D打印商等),也正好赶上习大大的一带一路政策,所以就夫妻一起在14年底注册了这家公司来做这样一个平台,深受当地政府的厚重(跟我聊天时将她与市长的微信聊天记录给我看,展览时国家部级领导人参观交流的照片等),政府免费提供了一层楼给其办公,公司人员就十来个,融了两次资(私人老板),第一次200w,10%的股份;第二次200w,5%的股份。 我们见了两次面,一次是她来我上班的地方,咖啡馆里简单聊了两个小时,第二次是在15年底,她公司的城市,我们聊了一整天,上午在其办公室,中午餐馆,下午办公室园区,一直再聊。个人直觉对这样一个行业,领域是非常的看好和认同,CEO眼光也不错,内心希望加入(回家后还跟媳妇商量了好久),但最终没有去成,败在现实利益面前。我们后面邮件往来了很多次主要是沟通待遇利益问题,CTO职位,她最终能给的股份期权非常有限,而且受限很大(不是一次性给),而工资就很少了,基本就是万把块钱的基本生活费,考虑到当时自己的情况,纠结了好久最后选择了放弃。这是自己现在回想会觉得有丝许后悔的为数不多一件事,其实,那时的我也不懂什么创业不创业,仅仅是凭自己的直觉,认为这件事可以为之,认可这样一个领域和产品,却败给了现实的自己。 现在的我反思,总结出创业最最重要的一点,是你必须认可这样一件事,这样才能全身心有激情的去当作自己的事业奋斗,但,同时,创业之路是有风险的,不可能考虑太多现实物质基础的,这样,你真的准备好了么?

G君

G君,应该是一位富二代,从他第二次深夜里开着加长版大奔(恕我浅薄,第一次在深圳见到)来我住的地方判断的(当时车上还坐着一位美女,然后犹豫找不着停车位就让她在车上等着,我俩找了个小店深聊到凌晨1点多)。他从事的是银行放贷这一块的,手里有很多资源,已经有好几家公司,他想做一个放贷相关的金融平台,然后提到马云,然后巴拉巴拉…后面又跟我聊了很多金融相关的东东,包括谈到很多现实的问题,例如在深圳没房,他说保证我明年不出一分钱拥有一套房,具体如何通过资本运作获得跟我详细讲了,听的我云里雾里…是的,他是玩资本的,而且玩了很多年了,国内外资本兼备,对金融完全陌生的我对他有些感到害怕(实话说长这么大还没具体说害怕过某个人)和胆怯…他要求我立即辞职,第二天就去他高大上办公室开始干起,具体他要做的事我都事晕的没有思考的余地,针对马上辞职这个观点,我试着辩解说这不太可能,他马上说提供高额补贴,巴拉巴拉…感觉在他面前我完全没有任何话语权,这是我长这么大从来没有遇到的事…我当时只好回复说要回去同媳妇商量下再决定,当然最后还是没去。 后面和一些朋友聊到这事,他们都说我的抉择是正确的,至少我认为,对于创业,至少要在你熟悉的领域进行,完全陌生的领域,虽然你只需要负责技术,但这不应该叫创业,所以,创业,这个领域你熟悉么?
(ps,现在朋友圈看到G经常晒各种照片,非常成功,各种投资,上月还看到其参加第十一届中国中小企业企业家创新发展大会中拿奖…只能说是一枚有魄力有资本有资源的领导者,服…只是资本的世界现在的我真的不懂~)

H君

H君,没见过面但留有印象,外包做了一个智慧问答平台,纯互联网项目,他的思想一直说这个项目是他自己这辈子真正想要做的事,自己出资金找外包团队做,不考虑融资(至少暂时),只想好好的把这样一件事做好…巴拉巴拉,这样的聊天让我感觉有点异类,真的可以做到在自己资金并不是那么充裕的前提下出资来做一件情怀的事情或产品么,这值得尊重和记忆。 他也跟我沟通,说他CTO有两三个人选,感觉我面相上像,有一种领导者的感觉,所以选我(当时听了,挺舒服的,第一次听人这样夸我…),但我们都还一直没见过面哦,他也不急,说等外包产品完成并发布后我再介入…就这样,中间等到了两个多月,在这段时间里,我在他的那个产品相关的群里逐渐发现,其实,项目除外,他并不是我想象中的那样情怀,很多可能只是言语上的高大上,或者只是我还太嫩…,当然,至少我认为,对于创业,情怀的东西需要要,但只有情怀我觉得是干不好一件事情的,就如我之前提到的,做企业你必须要考虑盈利,不以营利为目的的企业都是耍流氓。

I君、J君,…

回到自己

创业是什么?

我认为的创业,只是一种信息的不对称下,风口面前,事物或东西的归属,或许是你,或许是他人,区别点在于,站在风口面前的你,对这种不对称信息的了解和熟知(Ideal),你的资本是否可以经得起风口风的力度(资源/技术/团队等)? (By SkySeraph 2016)

你真的需要创业么?

你需要创业么? 你真的需要创业么? 这里不想谈太多其它主观客观巴拉巴拉讨论,仅一点,你想通过创业获得什么? 财富? 地位? 情怀? 梦想? 或许是其中之一,但它们不矛盾,你冒着风险,一定抉择后艰辛旅程后这些都只是结果,仅是结果产物。 我更希望的创业是在一定的时机和阶段,做一件自己想要为之的事,把它做成,留一点东西在这个社会,让复杂的事加上你的用心变的简单,仅此而已。

知乎一句话比较直接的击中了现在很多打工人的心。 “认真读书,拿好成绩,上好大学,找好工作,去大公司。努力在公司奋斗30年,费劲力气往上爬,不乱花销,攒钱买房子,做投资。等到退休了,就是有钱人了。相信这个,并且为之付出200%的努力的,就是穷人思维”。 对比一下,前半段是我经历了经历着,似乎没得选择;后半段之前没有考虑过的,以后也不会这样考虑,不曾想过打工可以成为自己的事业,可以实现财富自由,当然,创业也未必,但有可能。为了这种可能,你觉得呢?

我的创业路

任正非,43岁,华为,87年;
马云,35岁,阿里,99年;
马化腾,27岁,腾讯,98年;

创业是一件水到渠成的事,是人人可以为之的事,所以,不着急,不忧虑,试着梳理自己的Ideal,择一其二慢慢实现,沉下心里做几件自己想做的事,相信该来的一切都会来临。
而立之年,你的创业元年。

到你了

你呢?

后记

本文首发于skyseraph.com“而立之年,未开始的创业路”
同步发表/转载 cnBlogs / CSDN / 伯乐在线

By SkySeraph 10/2016 SZ China


版权声明


SkySeraph by SkySeraph is licensed under a Creative Commons BY-NC-ND 4.0 International License.
Bob创作并维护的SkySeraph博客采用创作共用保留署名-非商业-禁止演绎4.0国际许可证.
本文首发于SkySeraph博客( http://skyseraph.com ),版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

微信扫码打赏SkySeraph

如果您愿意捐助其它金额请戳我~~,扫码支付宝/微信

本文永久链接:http://skyseraph.com/2016/10/31/SkySeraph/Life/而立之年,未开始的创业路/

Comments

Jenkins Gitlab持续集成打包平台搭建

Catalogue
  1. 1. 相关概念
    1. 1.1. Jenkins
    2. 1.2. Gitlab
    3. 1.3. CI
    4. 1.4. OTA
    5. 1.5. pgyer
  2. 2. 流程结构
  3. 3. 平台搭建
    1. 3.1. Jenkins安装和启动
    2. 3.2. Jenkins插件安装
    3. 3.3. Jenkins系统设置
    4. 3.4. Jenkins Jobs配置
      1. 3.4.1. Jobs基础配置
      2. 3.4.2. Jobs源码库配置(Gitlab为例)
      3. 3.4.3. Jobs触发条件配置
      4. 3.4.4. Jobs构建方式/编译 配置
      5. 3.4.5. Jobs构建后处理
  4. 4. 后记
    1. 4.1. 签名和证书问题(iOS)
    2. 4.2. 邮件发送失败
    3. 4.3. Jenkins Https Support
  5. 5. Refs
  6. 6. 后记

相关概念

Jenkins

  • Jenkins,一个用Java编写的开源的持续集成工具,提供了软件开发的持续集成服务,可监控并触发持续重复的工作,具有开源,支持多平台和插件扩展,安装简单,界面化管理等特点。更多介绍参考维基介绍.

Gitlab

  • GitLab是一个利用Ruby on Rails开发的开源应用程序,实现一个自托管的Git项目仓库,可通过Web界面进行访问公开的或者私人项目,更多介绍参考维基介绍.

CI

  • 持续集成, 简称CI(continuous integration).
  • CI作为敏捷开发重要的一步,其目的在于让产品快速迭代的同时,尽可能保持高质量.
  • CI一种可以增加项目可见性,降低项目失败风险的开发实践。其每一次代码更新,都要通过自动化测试来检测代码和功能的正确性,只有通过自动测试的代码才能进行后续的交付和部署.
  • CI 是团队成员间(产研测)更好地协调工作,更好的适应敏捷迭代开发,自动完成减少人工干预,保证每个时间点上团队成员提交的代码都能成功集成的,可以很好的用于对Android/iOS项目的打包.

OTA

  • OTA(Over-the-Air Technology)空中下载技术,具体参考此文介绍.

pgyer

  • 蒲公英(pgyer)为移动开发者提供App免费测试分发应用的服务平台,支持iOS与Android,简单两步分发应用。类似的还有fir.im等.

流程结构

简单绘制了下Jenkins的一个流程,如下图:

IBM Developer上也有一个看似更复杂一点的图,如下图

持续交互流程图:

平台搭建

Jenkins安装和启动

官网https://jenkins.io/index.html
下载http://mirrors.jenkins-ci.org/war/latest/jenkins.war

安装:

  • 依赖于Java环境,首先安装和配置Java环境
  • 官网下载Jenkins,双击安装,如果是Mac电脑,会自动生成全局变量jenkins
  • 修改参数: jenkins + 相关参数,如
    jenkins –httpPort=8888 #更换端口号,当默认端口8080被占用,或指定特定端口时。

启动

  • 手动启动: java -jar jenkins.war
  • 后台启动(默认端口): nohup java -jar jenkins.war &
  • 后台启动(指定端口): nohup java -jar jenkins.war -httpPort=88 &
  • 后台启动(HTTPS): nohup java -jar jenkins.war -httpsPort=88 &
  • 浏览:http://localhost:8080/ , localhost可配置

Jenkins插件安装

插件安装

操作: Manage Jenkins -> Manage Plugins -> Available -> Search -> Click to install,如下图所示

实用插件

  • iOS专用:Xcode integration
  • Android专用:Gradle plugin
  • Gitlab插件:GitLab Plugin 和 Gitlab Hook Plugin
  • Git插件: Git plugin
  • GitBuckit插件: GitBuckit plugin
  • 签名证书管理插件: Credentials Plugin 和Keychains and Provisioning Profiles Management
  • FTP插件: Publish over FTP
  • 脚本插件: Post-Build Script Plug-in
  • 修改Build名称/描述(二维码): build-name-setter / description setter plugin
  • 获取仓库提交的commit log: Git Changelog Plugin
  • 自定义全局变量: Environment Injector Plugin
  • 自定义邮件插件: Email Extension Plugin
  • 获取当前登录用户信息: build-user-vars-plugin
  • 显示代码测试覆盖率报表: Cobertura Plugin
  • 来展示生成的单元测试报表,支持一切单测框架,如junit、nosetests等: Junit Plugin
  • 其它: GIT plugin / SSH Credentials Plugin

Jenkins系统设置

操作: Manage Jenkins -> Configure System

  • Jenkins内部shell UTF-8 编码设置,如下图所示

  • Jenkins Location和Email设置,如下图所示

  • E-mail Notification,设置如下如所示

Jenkis系统设置3

SMTP详细配置请参考 How to send Email at every build with Jenkins

Jenkins Jobs配置

Jobs基础配置

配置编译参数

例如,如果需要打包者自行选择打包类型,如需要编译Release/Debug/Test等不同版本的包,那需要配置Jobs的编译参数,配置方法如下图所示:

Jenkins编译设置-参数设置2

你还可以配置一些其它参数,例如:

Jenkins编译设置-参数设置3

配置完后,build界面中就会出现,如下如所示:

Jenkins编译设置-参数设置1

How to configure a single Jenkins job to make the release process from trunk or branches?

配置匿名用户权限

后面打包的应用发布时,如果懒得自己搭建服务器,就用Jenkins的,但发布出去的链接需要登录才能访问,这时候你可以设置匿名用户的访问权限,这样匿名用户可以下载访问你提供的应用链接了,非常取巧的方法,如下图:

Jenkins编译设置-参数设置4.png

Jobs源码库配置(Gitlab为例)

配置SSH

操作: Manage Jenkins -> Credentials -> Global credentials (unrestricted) -> Add Credentials

  1. 本机生成SSH:ssh-keygen -t rsa -C “Your email” , 生成过程中需设置密码,最终生成id_rsa和id_rsa.pub(公钥)
  2. 本机添加秘钥到SSH:ssh-add 文件名(需输入管理密码)
  3. Gitlab上添加公钥:复制id_rsa.pub里面的公钥添加到Gitlab
  4. Jenkins上配置密钥到SSH:复制id_rsa.pub里面的公钥添加到Jenkins(private key选项)

新建Job

在Jenkins中,所有的任务都是以”Job”为单位的。在进行操作前,你需要新建一个Job,Job新建比较简单,只需要在Jenkins管理的首页左侧,点击“New Job”,一般选择free-style software project,再输入Job的名字即可。

配置Gitlab

在新建的任务(Jobs)中,Gitlab源码配置如下图:需要输入git仓库和build分支,公钥使用上面配置SSH生成的公钥。

Jobs触发条件配置

Jenkins支持多种触发器配置,包括:

  • 定期进行构建(Build periodically),定时器使用示例如下:

    H(25-30) 18 1-5: 工作日下午6点25到30分之间进行build
    H 23 1-5:工作日每晚23:00至23:59之间的某一时刻进行build
    H(0-29)/15 :前半小时内每隔15分钟进行build(开始时间不确定)
    H/20 :每隔20分钟进行build(开始时间不确定)

  • 根据提交进行构建(Build when a change is pushed to GitHub)

  • 定期检测代码更新,如有更新则进行构建(Poll SCM)

Jobs构建方式/编译 配置

Jenkins支持多种编译配置方式,包括:

  • Xcode: iOS编译配置(安装Xcode integration插件)
  • Invoke Gradle script: Android编译配置(安装Gradle plugin插件)
  • Exceute Shell: 脚本方式

对于iOS应用的构建,如果选择Xcode方式构建,需要配置好开发者证书,具体参考后面签名和整数问题。
推荐使用Exceute Shell方式,简单有效。

Jobs构建后处理

Artifacts和邮件通知配置,参考下图

Jenkins项目设置-Archive-min

可借助Email Extension Plugin 插件进行详细配置,具体可参考此文

Jenkins项目设置-邮件-min

发布

当然,如果不想自己的应用发布到三方网站,只希望在自己的内网上托管,这样需要在自己内网上搭建服务器,服务器搭建方式有很多种,Mac上可以用自带的Apache服务,也可以用其它服务。

iOS的发布可能希望用到OTA,可参考此文 还有这篇一步一步实现无线安装iOS应用(内网OTA)

这里分享一个我写的shell脚本模板(已开源),可以用于iOS的plist文件自动创建以及OTA简单发布页面的自动创建,参考此链接获取源码, 欢迎Star.

自动生成一个简单HTML界面,如下图,点击Install即可安装:

Jenkis发布1

注意,这里iOS7.1以后限定必须要要用https,所以需要对jenkins设置下https,参考下面”后记” 中的Jenkins Https设置

Last Show

构建成功后最终的结果如下如所示:

后记

签名和证书问题(iOS)

邮件发送失败

  • 实际搭建过程中有遇到此问题,折腾了小会,还以为是公司邮箱地址为题,后面发现仅仅是一个小配置问题。
  • Extended E-mail Notification中也需要和E-mail Notification一样,点击Advanced,然后选择Use SMTP Authentication,配置同E-mail Notification的参数。

Jenkins Https Support

查询Jenkins Https相关命令:

1
java -jar jenkins.war --help | grep -i https

说明:下面以Mac为例.

KeyStore方式

  • 生成:
1
keytool -genkey -keyalg RSA -alias selfsigned -keystore keystore.jks -storepass 密码 -dname "cn=WEB"
  • 使用:
1
java -jar jenkins.war --httpPort=-1 --httpsPort=8080 --httpsKeyStore=/目录/keystore.jks --httpsKeyStorePassword=密码
  • 注意: 第一次使用时需要将”WEB”证书导入,导入步骤为:Chrome导出证书 -> 安装证书 -> 设置证书”全部允许”, 图解步骤可参考下面实用参考中的第5篇文章.

Certificate方式

  • 生成:
1
2
3
4
5
sudo openssl genrsa -out server.key 2048  
sudo openssl req -new -key server.key -out server.csr
sudo openssl genrsa -out ca.key 1024
sudo openssl req -new -x509 -days 365 -key ca.key -out ca.crt
sudo openssl ca -in server.csr -out server.crt -cert ca.crt -keyfile ca.key
  • 使用:

启动Jenkins:

1
java -jar jenkins.war --httpsPort=8088 --httpsCertificate=/path/server.crt --httpsPrivateKey=/path/server.key

  • 注意/说明:
    1 同上
    2 iOS手机需要导入cer证书(ca.crt)
    3 Common Name 填写IP地址或域名地址
    4 第5步骤如果提提示“I am unable to access the ./demoCA/newcerts directory” 错误,解决方法为:
    在当前操作目录,新建demoCA\newcerts2层文件夹
    然后再demoCA文件夹下新建一个空的index.txt文件
    再新建一个serial文件,没有后缀。里面填入01

实用参考

Refs

后记

本文首发于skyseraph.com“Jenkins Gitlab持续集成打包平台搭建”
同步发表/转载 cnBlogs / CSDN /



By SkySeraph-2016

版权声明


SkySeraph by SkySeraph is licensed under a Creative Commons BY-NC-ND 4.0 International License.
Bob创作并维护的SkySeraph博客采用创作共用保留署名-非商业-禁止演绎4.0国际许可证.
本文首发于SkySeraph博客( http://skyseraph.com ),版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

微信扫码打赏SkySeraph

如果您愿意捐助其它金额请戳我~~,扫码支付宝/微信

本文永久链接:http://skyseraph.com/2016/07/18/Tools/Jenkins Gitlab持续集成打包平台搭建/

Comments

Select all the cell in UITabView or UICollectionView problem

Catalogue
  1. 1. The Issue
  2. 2. Solutions in NetWork
  3. 3. The real Solution
  4. 4. Ref

The Issue

Recently in my new project I need to select all the cell data in my UITabViewCell and UICollectionViewCell, and need to do some operations with all the cells(like delete etc.), What I do as follows:

UITabView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private func selectAll(select: Bool) {
let numSections = mListTableView?.numberOfSections
if let numSections = numSections {
for numSection in 0 ..< numSections{
let numItems = mListTableView?.numberOfRowsInSection(numSection)
if let numItems = numItems {
for numItem in 0 ..< numItems {
selectCell(NSIndexPath(forRow: numItem, inSection: numSection), select: select)
}
}
}
}
}

private func selectCell(indexPath : NSIndexPath, select: Bool) {
if mListTableView?.cellForRowAtIndexPath(indexPath) != nil {
let cell = mListTableView?.cellForRowAtIndexPath(indexPath) as! DownloadListViewCell
//cell.setSelected(select, animated: true)
cell.setSelectForDelete(select) // select status UI in UITabViewCell
mDownloadList[indexPath.row].selectToDelete = select // Pojo data
}
}

UICollectionView

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
private func selectAll(select: Bool) {
let numSections = mMyOfflineCollectView?.numberOfSections()
if let numSections = numSections {
for numSection in 0 ..< numSections{
let numItems = mMyOfflineCollectView?.numberOfItemsInSection(numSection)
if let numItems = numItems {
for numItem in 0 ..< numItems {
selectCell(NSIndexPath(forRow: numItem, inSection: numSection), flag: select)
}
}
}
}
}

private func selectCell(indexPath : NSIndexPath, flag: Bool) {
if mMyOfflineCollectView.cellForItemAtIndexPath(indexPath) != nil {
let cell = mMyOfflineCollectView.cellForItemAtIndexPath(indexPath) as! MyOfflineCollectionViewCell
cell.setSelect(flag)
if flag {
mMyOfflineCollectView.selectItemAtIndexPath(indexPath, animated: true, scrollPosition: UICollectionViewScrollPosition.None)
}else {
mMyOfflineCollectView.deselectItemAtIndexPath(indexPath, animated: true)
}
mMyofflinesData[indexPath.row].needDelete = flag
}
}

But, The problem is , I can only select the visible cell when I scoll down or up, or do operations

Solutions in NetWork

UICollectionView cellForItemAtIndexPath is nil

cellForItemAtIndexPath returns nil after force scrolling to make it visible

Select all the cells in UITableView

Easier way to select all rows in UITableView

tableView.cellForRowAtIndexPath returns nil with too many cells (swift)

tableView.cellForRowAtIndexPath(indexPath) return nil

The real Solution

The real problem happened at the cellForRowAtIndexPath / cellForItemAtIndexPath, Which defined in Apple as follows:

1
2
public func cellForRowAtIndexPath(indexPath: NSIndexPath) -> UITableViewCell? 
// returns nil if cell is **not visible** or index path is out of range
1
public func cellForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewCell?

When the cell is not visible, the cellForRowAtIndexPath will return nil,
So, it’s not the right way to do the cell select operation out the
UITableViewDataSource in cellForRowAtIndexPath (UITabView), you should do it separate. The right way as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(DOWNLOAD_LIST_CELL_INDENTIFIER, forIndexPath: indexPath) as! DownloadListViewCell
cell.selectionStyle = UITableViewCellSelectionStyle.None
// ...
cell.setSelectForDelete(self.mDownloadList[indexPath.row].selectToDelete)// select status UI in UITabViewCell
// ...
return cell
}

private func selectCell(indexPath : NSIndexPath, select: Bool) {
mDownloadList[indexPath.row].selectToDelete = select // Pojo data
mListTableView?.reloadData() // reloadData
}

Ref

UITableView

UICollectionView



By SkySeraph-2016

版权声明


SkySeraph by SkySeraph is licensed under a Creative Commons BY-NC-ND 4.0 International License.
Bob创作并维护的SkySeraph博客采用创作共用保留署名-非商业-禁止演绎4.0国际许可证.
本文首发于SkySeraph博客( http://skyseraph.com ),版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

微信扫码打赏SkySeraph

如果您愿意捐助其它金额请戳我~~,扫码支付宝/微信

本文永久链接:http://skyseraph.com/2016/06/30/iOS/Select all the cell in UITabView or UICollectionView problem/

Comments

一种提高Android应用进程存活率新方法

Catalogue
  1. 1. 基础知识
    1. 1.1. Android 进程优先级
    2. 1.2. Android Low Memory Killer
    3. 1.3. 查看某个App的进程
    4. 1.4. Linux AM命令
    5. 1.5. NotificationListenerService
    6. 1.6. Android账号和同步机制
    7. 1.7. Android多进程
  2. 2. 现有方法
    1. 2.1. 网络连接保活方法
    2. 2.2. 双service(通知栏) 提高进程优先级
    3. 2.3. Service及时拉起
    4. 2.4. 守护进程/进程互拉
    5. 2.5. Linux Am命令开启后台进程
    6. 2.6. NotificationListenerService通知
    7. 2.7. 前台浮窗
  3. 3. 新方法(AccountSync)
    1. 3.1. 思路
    2. 3.2. 效果
    3. 3.3. 风险
    4. 3.4. 实现 (核心代码)
  4. 4. Refs
  5. 5. 后记

基础知识

Android 进程优先级

1 进程优先级等级一般分法

  • Activte process
  • Visible Process
  • Service process
  • Background process
  • Empty process

2 Service技巧

  • onStartCommand返回START_STICKY
  • onDestroy中startself
  • Service后台变前置,setForground(true)
  • android:persistent = “true”

3 进程优先级号

ProcessList.java

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
// Adjustment used in certain places where we don't know it yet.
// (Generally this is something that is going to be cached, but we
// don't know the exact value in the cached range to assign yet.)
static final int UNKNOWN_ADJ = 16;

// This is a process only hosting activities that are not visible,
// so it can be killed without any disruption.
static final int CACHED_APP_MAX_ADJ = 15;
static final int CACHED_APP_MIN_ADJ = 9;

// The B list of SERVICE_ADJ -- these are the old and decrepit
// services that aren't as shiny and interesting as the ones in the A list.
static final int SERVICE_B_ADJ = 8;

// This is the process of the previous application that the user was in.
// This process is kept above other things, because it is very common to
// switch back to the previous app. This is important both for recent
// task switch (toggling between the two top recent apps) as well as normal
// UI flow such as clicking on a URI in the e-mail app to view in the browser,
// and then pressing back to return to e-mail.
static final int PREVIOUS_APP_ADJ = 7;

// This is a process holding the home application -- we want to try
// avoiding killing it, even if it would normally be in the background,
// because the user interacts with it so much.
static final int HOME_APP_ADJ = 6;

// This is a process holding an application service -- killing it will not
// have much of an impact as far as the user is concerned.
static final int SERVICE_ADJ = 5;

// This is a process with a heavy-weight application. It is in the
// background, but we want to try to avoid killing it. Value set in
// system/rootdir/init.rc on startup.
static final int HEAVY_WEIGHT_APP_ADJ = 4;

// This is a process currently hosting a backup operation. Killing it
// is not entirely fatal but is generally a bad idea.
static final int BACKUP_APP_ADJ = 3;

// This is a process only hosting components that are perceptible to the
// user, and we really want to avoid killing them, but they are not
// immediately visible. An example is background music playback.
static final int PERCEPTIBLE_APP_ADJ = 2;

// This is a process only hosting activities that are visible to the
// user, so we'd prefer they don't disappear.
static final int VISIBLE_APP_ADJ = 1;

// This is the process running the current foreground app. We'd really
// rather not kill it!
static final int FOREGROUND_APP_ADJ = 0;

// This is a process that the system or a persistent process has bound to,
// and indicated it is important.
static final int PERSISTENT_SERVICE_ADJ = -11;

// This is a system persistent process, such as telephony. Definitely
// don't want to kill it, but doing so is not completely fatal.
static final int PERSISTENT_PROC_ADJ = -12;

// The system process runs at the default adjustment.
static final int SYSTEM_ADJ = -16;

// Special code for native processes that are not being managed by the system (so
// don't have an oom adj assigned by the system).
static final int NATIVE_ADJ = -17;

Android Low Memory Killer

Android系统内存不足时,系统会杀掉一部分进程以释放空间,谁生谁死的这个生死大权就是由LMK所决定的,这就是Android系统中的Low Memory Killer,其基于Linux的OOM机制,其阈值定义如下面所示的lowmemorykiller文件中,当然也可以通过系统的init.rc实现自定义。
lowmemorykiller.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static uint32_t lowmem_debug_level = 1;
static int lowmem_adj[6] = {
0,
1,
6,
12,
};
static int lowmem_adj_size = 4;
static int lowmem_minfree[6] = {
3 * 512, /* 6MB */
2 * 1024, /* 8MB */
4 * 1024, /* 16MB */
16 * 1024, /* 64MB */
};
static int lowmem_minfree_size = 4;

在Low Memory Killer中通过进程的oom_adj与占用内存的大小决定要杀死的进程,oom_adj值越小越不容易被杀死。其中,lowmem_minfree是杀进程的时机,谁被杀,则取决于lowmem_adj,具体值得含义参考上面 Android进程优先级 所述.

在init.rc中定义了init进程(系统进程)的oom_adj为-16,其不可能会被杀死(init的PID是1),而前台进程是0(这里的前台进程是指用户正在使用的Activity所在的进程),用户按Home键回到桌面时的优先级是6,普通的Service的进程是8.
init.rc

1
2
# Set init and its forked children's oom_adj.
write /proc/1/oom_adj -16

关于Low Memory Killer的具体实现原理可参考Ref-2.

查看某个App的进程

步骤(手机与PC连接)

  1. adb shell
  2. ps | grep 进程名
  3. cat /proc/pid/oom_adj //其中pid是上述grep得到的进程号

Linux AM命令

am命令:在Android系统中通过adb shell 启动某个Activity、Service、拨打电话、启动浏览器等操作Android的命令.其源码在Am.java中,在shell环境下执行am命令实际是启动一个线程执行Am.java中的主函数(main方法),am命令后跟的参数都会当做运行时参数传递到主函数中,主要实现在Am.java的run方法中。

拨打电话
命令:am start -a android.intent.action.CALL -d tel:电话号码
示例:am start -a android.intent.action.CALL -d tel:10086

打开一个网页
命令:am start -a android.intent.action.VIEW -d 网址
示例:am start -a android.intent.action.VIEW -d http://www.skyseraph.com

启动一个服务
命令:am startservice <服务名称>
示例:am startservice -n com.android.music/ com.android.music.MediaPlaybackService

NotificationListenerService

“A service that receives calls from the system when new notifications are posted or removed, or their ranking changed.” From Google

用来监听到通知的发送以及移除和排名位置变化,如果我们注册了这个服务,当系统任何一条通知到来或者被移除掉,我们都能通过这个service来监听到,甚至可以做一些管理工作。

Android账号和同步机制

属于Android中较偏冷的知识,具体参考 Ref 3 /4 /5

Android多进程

  • 实现:android:process
  • 好处:一个独立的进程可以充分利用自己的RAM预算,使其主进程拥有更多的空间处理资源。此外,操作系统对待运行在不同组件中的进程是不一样的。这意味着,当系统运行在低可用内存的条件时,并不是所有的进程都会被杀死
  • 大坑:每一个进程将有自己的Dalvik VM实例,意味着你不能通过这些实例共享数据,至少不是传统意义上的。例如,静态字段在每个进程都有自己的值,而不是你倾向于相信的只有一个值。
  • 更多详细请参考Ref 9

现有方法

网络连接保活方法

A. GCM
B. 公共的第三方push通道(信鸽等)
C. 自身跟服务器通过轮询,或者长连接
具体实现请参考 微信架构师杨干荣的”微信Android客户端后台保活经验分享” (Ref-1).

双service(通知栏) 提高进程优先级

思路:(API level > 18 )

  • 应用启动时启动一个假的Service(FakeService), startForeground(),传一个空的Notification
  • 启动真正的Service(AlwaysLiveService),startForeground(),注意必须相同Notification ID
  • FakeService stopForeground()

效果:通过adb查看,运行在后台的服务其进程号变成了1(优先级仅次于前台进程)

风险:Android系统前台service的一个漏洞,可能在6.0以上系统中修复

实现:核心代码如下

  • AlwaysLiveService 常驻内存服务
1
2
3
4
5
6
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
startForeground(R.id.notify, new Notification());
startService(new Intent(this, FakeService.class));
return super.onStartCommand(intent, flags, startId);
}
  • FakeService 临时服务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class FakeService extends Service {	
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
startForeground(R.id.notify, new Notification());
stopSelf();
return super.onStartCommand(intent, flags, startId);
}

@Override
public void onDestroy() {
stopForeground(true);
super.onDestroy();
}
}

Service及时拉起

AlarmReceiver, ConnectReceiver,BootReceiver等

  • Service设置(见上面基础部分)
  • 通过监听系统广播,如开机,锁屏,亮屏等重新启动服务
  • 通过alarm定时器,启动服务

守护进程/进程互拉

在分析360手机助手app时,发现其拥有N多个进程,一个进程kill后会被其它未kill的进程拉起,这也是一种思路吧,虽然有点流氓~
守护进程一般有这样两种方式:

  • 多个java进程守护互拉
  • 底层C守护进程拉起App上层/java进程

Linux Am命令开启后台进程

一种底层实现让进程不被杀死的方法,在Android4.4以上可能有兼容性问题,具体参考Ref-7

NotificationListenerService通知

一种需要用户允许特定权限的系统拉起方式,4.3以上系统

前台浮窗

有朋友提出一种应用退出后启动一个不可交互的浮窗,个人觉得这种方法是无效的,读者有兴趣可以一试

新方法(AccountSync)

思路

利用Android系统提供的账号和同步机制实现

效果

  • 通过adb查看,运行在后台的服务其进程号变成了1(优先级仅次于前台进程),能提高进程优先级,对比如下图

正常情况

采用AccountSyncAdapter方法后

  • 进程被系统kill后,可以由syn拉起

风险

  • SyncAdapter时间进度不高,往往会因为手机处于休眠状态,而时间往后调整,同步间隔最低为1分钟
  • 用户可以单独停止或者删除,有些手机账号默认是不同步的,需要手动开启

实现 (核心代码)

1 建立数据同步系统(ContentProvider)

通过一个ContentProvider用来作数据同步,由于并没有实际数据同步,所以此处就直接建立一个空的ContentProvider即可

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
public class XXAccountProvider extends ContentProvider {
public static final String AUTHORITY = "包名.provider";
public static final String CONTENT_URI_BASE = "content://" + AUTHORITY;
public static final String TABLE_NAME = "data";
public static final Uri CONTENT_URI = Uri.parse(CONTENT_URI_BASE + "/" + TABLE_NAME);

@Override
public boolean onCreate() {
return true;
}

@Nullable
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder)
{

return null;
}

@Nullable
@Override
public String getType(Uri uri) {
return new String();
}

@Nullable
@Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}

@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}

@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0;
}
}

然后再Manifest中声明

1
2
3
4
5
<provider
android:name="**.XXAccountProvider"
android:authorities="@string/account_auth_provider"
android:exported="false"
android:syncable="true"/>

2 建立Sync系统 (SyncAdapter)

通过实现SyncAdapter这个系统服务后, 利用系统的定时器对程序数据ContentProvider进行更新,具体步骤为:

  • 创建Sync服务
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
public class XXSyncService extends Service {
private static final Object sSyncAdapterLock = new Object();
private static XXSyncAdapter sSyncAdapter = null;
@Override
public void onCreate() {
synchronized (sSyncAdapterLock) {
if (sSyncAdapter == null) {
sSyncAdapter = new XXSyncAdapter(getApplicationContext(), true);
}
}
}

@Override
public IBinder onBind(Intent intent) {
return sSyncAdapter.getSyncAdapterBinder();
}

static class XXSyncAdapter extends AbstractThreadedSyncAdapter {
public XXSyncAdapter(Context context, boolean autoInitialize) {
super(context, autoInitialize);
}
@Override
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
getContext().getContentResolver().notifyChange(XXAccountProvider.CONTENT_URI, null, false);
}
}
}
  • 声明Sync服务
1
2
3
4
5
6
7
8
9
10
11
12
<service
android:name="**.XXSyncService"
android:exported="true"
android:process=":core">

<intent-filter>
<action
android:name="android.content.SyncAdapter"/>

</intent-filter>
<meta-data
android:name="android.content.SyncAdapter"
android:resource="@xml/sync_adapter"/>

</service>

其中sync_adapter为:

1
2
3
4
5
6
7
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="@string/account_auth_type"
android:allowParallelSyncs="false"
android:contentAuthority="@string/account_auth_provide"
android:isAlwaysSyncable="true"
android:supportsUploading="false"
android:userVisible="true"/>

参数说明:

android:contentAuthority 指定要同步的ContentProvider在其AndroidManifest.xml文件中有个android:authorities属性。
android:accountType 表示进行同步的账号的类型。
android:userVisible 设置是否在“设置”中显示
android:supportsUploading 设置是否必须notifyChange通知才能同步
android:allowParallelSyncs 是否支持多账号同时同步
android:isAlwaysSyncable 设置所有账号的isSyncable为1
android:syncAdapterSettingsAction 指定一个可以设置同步的activity的Action。

  • 账户调用Sync服务
    首先配置好Account(第三步),然后再通过ContentProvider实现
    手动更新
1
2
3
4
5
6
7
8
9
public void triggerRefresh() {
Bundle b = new Bundle();
b.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
b.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
ContentResolver.requestSync(
account,
CONTENT_AUTHORITY,
b);
}

添加账号

1
2
3
Account account = AccountService.GetAccount(); 
AccountManager accountManager = (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE);
accountManager.addAccountExplicitly(...)

同步周期设置

1
2
3
ContentResolver.setIsSyncable(account, CONTENT_AUTHORITY, 1);
ContentResolver.setSyncAutomatically(account, CONTENT_AUTHORITY, true);
ContentResolver.addPeriodicSync(account, CONTENT_AUTHORITY, new Bundle(), SYNC_FREQUENCY);

3 建立账号系统 (Account Authenticator)

通过建立Account账号,并关联SyncAdapter服务实现同步

  • 创建Account服务
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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
public class XXAuthService extends Service {
private XXAuthenticator mAuthenticator;

@Override
public void onCreate() {
mAuthenticator = new XXAuthenticator(this);
}

private XXAuthenticator getAuthenticator() {
if (mAuthenticator == null)
mAuthenticator = new XXAuthenticator(this);
return mAuthenticator;
}

@Override
public IBinder onBind(Intent intent) {
return getAuthenticator().getIBinder();
}

class XXAuthenticator extends AbstractAccountAuthenticator {
private final Context context;
private AccountManager accountManager;
public XXAuthenticator(Context context) {
super(context);
this.context = context;
accountManager = AccountManager.get(context);
}

@Override
public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options)
throws NetworkErrorException {

// 添加账号 示例代码
final Bundle bundle = new Bundle();
final Intent intent = new Intent(context, AuthActivity.class);
intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
bundle.putParcelable(AccountManager.KEY_INTENT, intent);
return bundle;
}

@Override
public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options)
throws NetworkErrorException {

// 认证 示例代码
String authToken = accountManager.peekAuthToken(account, getString(R.string.account_token_type));
//if not, might be expired, register again
if (TextUtils.isEmpty(authToken)) {
final String password = accountManager.getPassword(account);
if (password != null) {
//get new token
authToken = account.name + password;
}
}
//without password, need to sign again
final Bundle bundle = new Bundle();
if (!TextUtils.isEmpty(authToken)) {
bundle.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
bundle.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
bundle.putString(AccountManager.KEY_AUTHTOKEN, authToken);
return bundle;
}

//no account data at all, need to do a sign
final Intent intent = new Intent(context, AuthActivity.class);
intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
intent.putExtra(AuthActivity.ARG_ACCOUNT_NAME, account.name);
bundle.putParcelable(AccountManager.KEY_INTENT, intent);
return bundle;
}

@Override
public String getAuthTokenLabel(String authTokenType) {
// throw new UnsupportedOperationException();
return null;
}

@Override
public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
return null;
}

@Override
public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options)
throws NetworkErrorException {

return null;
}

@Override
public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options)
throws NetworkErrorException {

return null;
}

@Override
public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features)
throws NetworkErrorException {

return null;
}
}
}
  • 声明Account服务
1
2
3
4
5
6
7
8
9
10
11
12
<service
android:name="**.XXAuthService"
android:exported="true"
android:process=":core">

<intent-filter>
<action
android:name="android.accounts.AccountAuthenticator"/>

</intent-filter>
<meta-data
android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/authenticator"/>

</service>

其中authenticator为:

1
2
3
4
5
6
7
<?xml version="1.0" encoding="utf-8"?>
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="@string/account_auth_type"
android:icon="@drawable/icon"
android:smallIcon="@drawable/icon"
android:label="@string/app_name"
/>

Refs

  1. 微信Android客户端后台保活经验分享

  2. Android Low Memory Killer原理

  3. stackOverflow 上介绍的双Service方法

  4. Write your own Android Sync Adapter

  5. Write your own Android Authenticator

  6. Android developer

  7. Android篇从底层实现让进程不被杀死(失效Closed)

  8. Android 4.3+ NotificationListenerService 的使用

  9. Going multiprocess on Android

后记

2016.5.24

1. 本文发布时间写错了,5.19手贱成了6.19,就酱紫吧,懒得改了,五月份看过的童鞋就当狠狠滴穿越了一把吧,O(∩_∩)O哈哈哈~~

2. 本文在V2EX稀土掘金博客园CSDN等等诸多网站上有转载或发布,收到了很多评论和讨论,其中有一部分以“天下兴亡匹夫有责”的心态批判笔者等同类开发者把Android生态给搞坏了,提到iOS的诸多好处等等,阐述几点个人观点:

据笔者研究,目前双Service拉起的方式在国内排前几的应用(微信/支付宝等等)中都有用到,进程互拉方式在360手机助手、应用宝等应用中有用到,这些才是真正黑科技,笔者提到的方法仅仅是取巧性的用到了Android系统提供的方法,谈不上XXX~~

iOS的封闭造就其天然的优势,不存在这些问题; 而Android的开源,有诸多问题但不可否认的是其促进了技术的发展,科技的发展甚至人类的进步。 物极必反,很多事情都是双刃剑~

后来经一些网友提醒,发现所谓提异议的这群家伙都是产品汪,半吊子技术,所以XXOO~~

法海无涯,技术无边,风涯无罪,南无阿弥陀佛~~

本文首发于skyseraph.com“一种提高Android应用进程存活率新方法”
同步发表/转载 cnBlogs / CSDN / 伯乐在线



By SkySeraph-2016

版权声明


SkySeraph by SkySeraph is licensed under a Creative Commons BY-NC-ND 4.0 International License.
Bob创作并维护的SkySeraph博客采用创作共用保留署名-非商业-禁止演绎4.0国际许可证.
本文首发于SkySeraph博客( http://skyseraph.com ),版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

微信扫码打赏SkySeraph

如果您愿意捐助其它金额请戳我~~,扫码支付宝/微信

本文永久链接:http://skyseraph.com/2016/06/19/Android/一种提高Android应用进程存活率新方法/

Comments