2
评论

【新手快速入门】集成环信常见问题+解决方案汇总 常见问题

dujiepeng 发表了文章 • 2756 次浏览 • 2017-05-22 15:51 • 来自相关话题

   这里整理了集成环信的常见问题和一些功能的实现思路,希望能帮助到大家。感谢热心的开发者贡献,大家在观看过程中有不明白的地方欢迎直接跟帖咨询。
 
ios篇
APNs证书创建和上传到环信后台头像昵称的简述和处理方案音视频离线推送Demo实现环信服务器聊天记录保存多久?离线收不到好友请求IOS中环信聊天窗口如何实现文件发送和预览的功能ios集成常见问题环信推送的一些常见问题
 
Android篇
环信3.0SDK集成小米推送教程EaseUI库中V4、v7包冲突解决方案Android EaseUI里的百度地图替换为高德地图android扩展消息(名片集成)关于会话列表的置顶聊天java.lang.UnsatisfiedLinkError: 的问题android 端 app 后台被杀死收不到消息的解决方案
昵称头像篇
android中如何显示开发者服务器上的昵称和头像 Android中显示头像(接上一篇文章看)环信(Android)设置头像和昵称的方法(最简单暴力的基于环信demo的集成)IOS中如何显示开发者服务器上的昵称和头像【环信公开课第12期视频回放】-所有关于环信IM昵称头像的问题听这课就够了
 
直播篇
一言不合你就搞个直播APP
 
客服集成
IM-SDK和客服SDK并存开发指南—Android篇IM-SDK和客服SDK并存开发指南—iOS篇
 
开源项目
Android简版demoios简版demo凡信2.0:超仿微信的开源项目 凡信3.0:携直播和红包而来高仿微信:Github 3,515 Star方圆十里:环信编程大赛冠军项目泛聊:定一个小目标写一个QQSlack聊天机器人:一天时间做一个聊天机器人TV视频通话:在电视上视频通话视频通话:Android手机视频通话酷信:ios高仿微信公众号助手:与订阅用户聊天沟通
 
持续更新ing...小伙伴们还有什么想知道欢迎跟帖提出。
  查看全部
   这里整理了集成环信的常见问题和一些功能的实现思路,希望能帮助到大家。感谢热心的开发者贡献,大家在观看过程中有不明白的地方欢迎直接跟帖咨询。
 
ios篇

 
Android篇

昵称头像篇

 
直播篇
  1. 一言不合你就搞个直播APP

 
客服集成
  1. IM-SDK和客服SDK并存开发指南—Android篇
  2. IM-SDK和客服SDK并存开发指南—iOS篇

 
开源项目

 
持续更新ing...小伙伴们还有什么想知道欢迎跟帖提出。
 
4
评论

【视频教程+源码】基于环信IM做一个仿微信APP-更新ing 郭永峰 高仿微信 仿微信 环信 XMPP

郭永峰 发表了文章 • 5520 次浏览 • 2017-05-16 15:29 • 来自相关话题

我只是一个普通人,做人要谦虚。
我不是大神,我也不是很厉害的。
天外有天,人外有人。
老师引入门,修行靠个人。
希望能帮助大家,谢谢。
    大家好,我是郭永峰(峰哥) | 一个普通大学计算机系毕业的大学生,曾就职于澳门遊澳集团有限公司,负责大型银联支付业务系统、跨国际短信业务系统(基于电信的SGIP)以及集团内部通讯系统 (负责android和openfire后台二次开发)的主要开发任务,担任项目负责人。13年就职于广州拓谷科技有限公司负责“酷蛙”车联网产品研发及汽车销售产品研发。14来年到17年2月份,就职于国内知名教育机构,负责教学研发及授课的工作。
 
本人现状况:
  
   在家录制教学视频(无收入),搞工作室,组建团队成立公司,如果大家觉得分享内容很喜欢,可以给我打点赏支持本人的工作室,二维码在文末,就不影响阅读了。

 
郭永峰IT教育工作室于2017年4月12日成立!
 
成立原因:

希望把近10年来从事IT互联网的知识分享给大家,包括Linux,WindowServer,Java,PHP,Android,iOS,H5等等等。
 

进入正题,本套课程基于环信IM教大家如何做一个类似微信的APP,只用于技术交流,请勿用于任何商业用途。
4月12号成立工作室,现在18号,过了一个星期一个星期录了5天的环信教程视频,我将放在网盘免费分享环信的教程视频主要是针对有开发经验者教程主要是使用环信来模仿微信来做一个即时通讯的案例课程主要是先讲socket基础 -> 环信 ->自定义协议希望这些教程视频能帮助大家,对即时通讯、socket和自定义协议有个较深入的了解同时能希望大家在面试时,在即时通讯这块不在陌生
  持续更新

第一阶段:即时通讯的了解和微信APP开发前的准备!

【视频教程+源码】基于环信IM做一个仿微信APP-01.即时通讯简介(了解)

【视频教程+源码】基于环信IM做一个仿微信APP-02.XMPP简介(了解)

【视频教程+源码】基于环信IM做一个仿微信APP-03.XMPP实现即时通信的准备工作(了解)

【视频教程+源码】基于环信IM做一个仿微信APP-04.环信简介(了解) 

【视频教程+源码】基于环信IM做一个仿微信APP-05.集成环信的前提准备(掌握)
 
【视频教程+源码】基于环信IM做一个仿微信APP-06.环信SDK的版本的区别(掌握)

【视频教程+源码】基于环信IM做一个仿微信APP-07.微信-项目创建及代码目录结构规范(MVC)

【视频教程+源码】基于环信IM做一个仿微信APP-08.微信-集成环信SDK

【视频教程+源码】基于环信IM做一个仿微信APP-09.微信-登录界面排版

【视频教程+源码】基于环信IM做一个仿微信APP-10.微信-主界面搭建

【视频教程+源码】基于环信IM做一个仿微信APP-11.微信-注册功能

【视频教程+源码】基于环信IM做一个仿微信APP- 12.微信-登录功能

【视频教程+源码】基于环信IM做一个仿微信APP- 13.微信-自动登录

【视频教程+源码】基于环信IM做一个仿微信APP- 14.微信-主动退出
 
【视频教程+源码】基于环信IM做一个仿微信APP-15.微信-在其它设备登录
 
整个项目源码,git地址https://github.com/mayaole/fWeiXin

 微信打赏






支付宝打赏






谢谢大家的支持,个人微信号清扫描下面张图






 
郭永峰IT交流QQ群请加:596441895 查看全部
我只是一个普通人,做人要谦虚。
我不是大神,我也不是很厉害的。
天外有天,人外有人。
老师引入门,修行靠个人。
希望能帮助大家,谢谢。

    大家好,我是郭永峰(峰哥) | 一个普通大学计算机系毕业的大学生,曾就职于澳门遊澳集团有限公司,负责大型银联支付业务系统、跨国际短信业务系统(基于电信的SGIP)以及集团内部通讯系统 (负责android和openfire后台二次开发)的主要开发任务,担任项目负责人。13年就职于广州拓谷科技有限公司负责“酷蛙”车联网产品研发及汽车销售产品研发。14来年到17年2月份,就职于国内知名教育机构,负责教学研发及授课的工作。
 
本人现状况:
  
   在家录制教学视频(无收入),搞工作室,组建团队成立公司,如果大家觉得分享内容很喜欢,可以给我打点赏支持本人的工作室,二维码在文末,就不影响阅读了。

 
郭永峰IT教育工作室于2017年4月12日成立!
 
成立原因:

希望把近10年来从事IT互联网的知识分享给大家,包括Linux,WindowServer,Java,PHP,Android,iOS,H5等等等。
 

进入正题,本套课程基于环信IM教大家如何做一个类似微信的APP,只用于技术交流,请勿用于任何商业用途。
  1. 4月12号成立工作室,现在18号,过了一个星期
  2. 一个星期录了5天的环信教程视频,我将放在网盘免费分享
  3. 环信的教程视频主要是针对有开发经验者
  4. 教程主要是使用环信来模仿微信来做一个即时通讯的案例
  5. 课程主要是先讲socket基础 -> 环信 ->自定义协议
  6. 希望这些教程视频能帮助大家,对即时通讯、socket和自定义协议有个较深入的了解
  7. 同时能希望大家在面试时,在即时通讯这块不在陌生

  持续更新

第一阶段:即时通讯的了解和微信APP开发前的准备!

【视频教程+源码】基于环信IM做一个仿微信APP-01.即时通讯简介(了解)

【视频教程+源码】基于环信IM做一个仿微信APP-02.XMPP简介(了解)

【视频教程+源码】基于环信IM做一个仿微信APP-03.XMPP实现即时通信的准备工作(了解)

【视频教程+源码】基于环信IM做一个仿微信APP-04.环信简介(了解) 

【视频教程+源码】基于环信IM做一个仿微信APP-05.集成环信的前提准备(掌握)
 
【视频教程+源码】基于环信IM做一个仿微信APP-06.环信SDK的版本的区别(掌握)

【视频教程+源码】基于环信IM做一个仿微信APP-07.微信-项目创建及代码目录结构规范(MVC)

【视频教程+源码】基于环信IM做一个仿微信APP-08.微信-集成环信SDK

【视频教程+源码】基于环信IM做一个仿微信APP-09.微信-登录界面排版

【视频教程+源码】基于环信IM做一个仿微信APP-10.微信-主界面搭建

【视频教程+源码】基于环信IM做一个仿微信APP-11.微信-注册功能

【视频教程+源码】基于环信IM做一个仿微信APP- 12.微信-登录功能

【视频教程+源码】基于环信IM做一个仿微信APP- 13.微信-自动登录

【视频教程+源码】基于环信IM做一个仿微信APP- 14.微信-主动退出
 
【视频教程+源码】基于环信IM做一个仿微信APP-15.微信-在其它设备登录
 
整个项目源码,git地址https://github.com/mayaole/fWeiXin

 微信打赏

微信.png


支付宝打赏

支付宝.png


谢谢大家的支持,个人微信号清扫描下面张图

个人.png


 
郭永峰IT交流QQ群请加:596441895
2
评论

【环信公开课第12期视频回放】-所有关于环信IM昵称头像的问题听这课就够了 环信公开课 昵称头像 环信大表哥

beyond 发表了文章 • 1573 次浏览 • 2017-05-08 11:55 • 来自相关话题

 ​ 青年的思想愈被榜样的力量所激动,就愈会发出强烈的光辉-法捷耶夫
  在刚刚过去的五四青年节,环信公开课第12期如期举行,这期公开课嘉宾“环信大表哥”一人手写三端,Android/IOS/服务端信手捏来,关于用户体系更是引经据典一番,整场公开课妙语连珠、妙趣横生。加上环信MM的现场答疑,这样一来便消除了拘谨,活跃了气氛,环信小伙伴们在欢快愉悦的气氛中度过了这个有意义的五四青年节! 

环信公开课12期讲了什么?
如何利用消息扩展属性显示昵称头像?如何通过APP服务器处理昵称头像的显示?昵称头像的本地缓存策略?音视频通话如何显示昵称头像?

关于环信大表哥:
   马骏斌丨美国海遇网络CTO,传说中的祖传CTO“环信大表哥”,10年研发经验,现任美国海遇网络CTO。曾就职于北京超图从事GIS研发,负责基于SuperMap平台研究GIS算法以及图形处理工具,优化底层三维渲染引擎,协助产品研发进行数据处理或空间分析工作。

环信简版demo作者,开源了基于环信的直播项目-小马直播间,在imgeek、简书等社区发表了数十篇环信教程,他的教程和开源项目累计帮助了超过1W多名开发者集成环信,自己创建了环信互帮互助群,每天"夜黑风高"活跃在群里回答问题+远程写代码,人送外号“环信大表哥”。
 
自2011年2月创办www.C3DN.net(中国3D技术开发者社区),并兼任C3DN站长。创办C3DN,不仅为了延续对3D技术的热爱,最主要是想帮助更多需要帮助的人学习3D开发技术;

先放一张大表哥PPT截图,这个style!你们感(la)受(yan)下(jing)。





环信公开课视频回放:视频观看地址
  
环信公开课第12期讲师PPT在文末下载,最后引用一句某知名互联网公司创始人名言致大表哥“百年之后,你我的肉身终将陨灭,而我们的精神和梦想依然可以在代码中相见”
ios简版demo地址
Android简版demo地址 查看全部
 
​ 青年的思想愈被榜样的力量所激动,就愈会发出强烈的光辉-法捷耶夫

  在刚刚过去的五四青年节,环信公开课第12期如期举行,这期公开课嘉宾“环信大表哥”一人手写三端,Android/IOS/服务端信手捏来,关于用户体系更是引经据典一番,整场公开课妙语连珠、妙趣横生。加上环信MM的现场答疑,这样一来便消除了拘谨,活跃了气氛,环信小伙伴们在欢快愉悦的气氛中度过了这个有意义的五四青年节! 

环信公开课12期讲了什么?
  1. 如何利用消息扩展属性显示昵称头像?
  2. 如何通过APP服务器处理昵称头像的显示?
  3. 昵称头像的本地缓存策略?
  4. 音视频通话如何显示昵称头像?


关于环信大表哥:

   马骏斌丨美国海遇网络CTO,传说中的祖传CTO“环信大表哥”,10年研发经验,现任美国海遇网络CTO。曾就职于北京超图从事GIS研发,负责基于SuperMap平台研究GIS算法以及图形处理工具,优化底层三维渲染引擎,协助产品研发进行数据处理或空间分析工作。

环信简版demo作者,开源了基于环信的直播项目-小马直播间,在imgeek、简书等社区发表了数十篇环信教程,他的教程和开源项目累计帮助了超过1W多名开发者集成环信,自己创建了环信互帮互助群,每天"夜黑风高"活跃在群里回答问题+远程写代码,人送外号“环信大表哥”。
 
自2011年2月创办www.C3DN.net(中国3D技术开发者社区),并兼任C3DN站长。创办C3DN,不仅为了延续对3D技术的热爱,最主要是想帮助更多需要帮助的人学习3D开发技术;



先放一张大表哥PPT截图,这个style!你们感(la)受(yan)下(jing)。
QQ截图20170508115114.jpg


环信公开课视频回放:视频观看地址
  
环信公开课第12期讲师PPT在文末下载,最后引用一句某知名互联网公司创始人名言致大表哥“百年之后,你我的肉身终将陨灭,而我们的精神和梦想依然可以在代码中相见
ios简版demo地址
Android简版demo地址
0
回复

getConversation返回null,知道是自己sdk的原因,可是找不到解决方法 SDK不响应 SDK

回复

shallwe 发起了问题 • 1 人关注 • 15 次浏览 • 2017-06-23 17:50 • 来自相关话题

0
回复

demo运行崩溃 有专职工程师值守

回复

Grass age 发起了问题 • 1 人关注 • 14 次浏览 • 2017-06-23 17:04 • 来自相关话题

0
回复

网页客服的客服管理能不能也集成到项目里 环信移动客服

回复

百合 发起了问题 • 1 人关注 • 17 次浏览 • 2017-06-23 14:51 • 来自相关话题

0
回复

问下环信的消息表示怎么设计的? 消息

回复

haley 发起了问题 • 1 人关注 • 14 次浏览 • 2017-06-23 14:09 • 来自相关话题

0
回复

环信红包支付宝授权成功后不会回到 app 中,手动进入 app 显示取消授权 环信_Android

回复

onlyForward 发起了问题 • 1 人关注 • 25 次浏览 • 2017-06-22 18:31 • 来自相关话题

0
回复

项目中集成了单聊界面和语音视频通话单元,集成支付宝后出现问题 环信_iOS

回复

笑笑骗你一世~ 发起了问题 • 1 人关注 • 23 次浏览 • 2017-06-22 18:31 • 来自相关话题

0
评论

视频超过三十秒后再接受 无数据 环信_iOS

╰つ笑ぷ倾城ら 发表了文章 • 20 次浏览 • 2017-06-22 16:57 • 来自相关话题

iOS 根安卓的  视频聊天 超过三十秒之后在接受就没有画面了 都是黑的 小窗口是有画面的是因为 超时了吗 ? 还是什么原因该怎么解决呢  谢谢
iOS 根安卓的  视频聊天 超过三十秒之后在接受就没有画面了 都是黑的 小窗口是有画面的是因为 超时了吗 ? 还是什么原因该怎么解决呢  谢谢
0
评论

【环信征文】Android程序员的十大转型之路 Android

东风玖哥 发表了文章 • 138 次浏览 • 2017-06-21 16:29 • 来自相关话题

IT行业是一个瞬息万变的行业,程序员是一个不进则退的职业。我作为一个Android程序员,多年来一直保持随时可以转型其他技术领域的状态,保持对新技术敏感的嗅觉。
 
我先说说Android程序员不可能转型的几个方向,以下四个不靠谱方向的靠谱性递减:
首先不会转型iOS,iOS和Android工程师的工作内容都是大同小异的。
其次不会转型Windows Phone,好多Andr oid程序员就是受不了产品经理唠叨:“像QQ客户端那样做成和iOS一样”才转型的,怎么会转型比Android还难做成和iOS一样的WP?
再次不会转型Windows和MacOS等桌面软件,桌面开发周期长、难度大、升级不易,这是一个已经接近穷途末路的夕阳产业。
最后不会开JavaME或者Symbian的历史倒车,除非他有本事让每个用户都买(就一个“买”字,同时包含“想买”和“买得到”的意思)停产多年的机型。
 
我观察如今的技术形势,并亲身探索了一个Android程序员转型的几个技术方向的可行性:
 
Android病毒和恶意应用
最近肆虐全世界的WannaCry让安全成了IT圈最热的话题,开发腻了善意应用的Android工程师最便捷的转型方向就是开发Android病毒和恶意应用。在4.x时代对Android对敏感权限还不是很敏感的时候,我就研究过给肉鸡伪造短信记录和让肉鸡给通讯录里所有(或特定)联系人发送短信的病毒。去年还研究过窃取友商App推送内容、强杀友商App进程、卸载友商App甚至让友商App被卸载后就再也不能在这台肉鸡上安装的恶意应用(或应用里的恶意功能)。

 
转型建议:此外锁定肉鸡里的重要文件勒索用户(Android上的WannaCry?)和窃取肉鸡用户的支付密码的实现在技术上也像强奸8岁女童一样简单,只不过事后逍遥法外很难。这个转型方向只适合拿自己的手机当肉鸡玩玩,千万不要用这些技术赚钱。

SDK
开发SDK本质上仍然在为Android应用开发软件,只是不直接开发Android应用。
 
每个Android程序员工作几年后都积累了属于自己的或大或小的类库,比如封装好的LogUtils和ToastUtils等;也都或多或少研究过常用开源框架的底层原理,比如了解Picasso和EventBus等;还应该对不开源的第三方服务有自己简单的二次封装,比如我就封装了一键实现支付宝和微信支付的moudle(免费的Ping++?)。
 
转型建议:尽管看见自己的链接出现在无数Android应用的Gradle文件的compile后面,开发了无数软件的一部分的成就感不会比开发完整的软件差。但是几乎没有老板会为了支持你开发开源软件发你工资。
 
JavaEE
Android程序员转型Java在基础知识方面是没什么难度的,毕竟语言相通,特性相似。同时每个Android程序员在大学时J2EE课程学得都不会很差,不过有些知识是该忘掉的,比如Hibernate已经落后于时代了,SpringMVC的全面使用才是Java后台的大势所趋。
 
转型建议:建议不想每天改UI的刚入行不久的Android工程师转型,我有好几个学弟就是参加工作后从Android转型Java的,他们过得都不错。很多工作年限较长的Android工程师本来就是JavaEE转型来的,就别转回去了。
 
手游
首先考虑不放弃Java语言和Android开发习惯的情况:最合适的就是能把游戏view直接插入普通layout里的AndEngine,前几年大红大紫的Flappy Bird就是用它开发的。AndEngine的开发方式和Android别无二致,且有丰富的开源demo。不过AndEngine没有官方文档,理论学习上有一定难度。我用AndEngine开发了我的毕业设计,参加工作后也用AndEngine获得了几个奖,我珍藏着一本AndEngine的非官方文档《Android游戏开发实践指南》(全新未拆封),期待着有一天能回到2014年把它送给那个买不起它的毕业生。

 
提到了AndEngine就不得不提国产AndEngine——OGEngine,它是基于AndEngine衍生的游戏引擎,有详细的纯中文文档和说汉语的技术支持杨城(笔名:小城),极适合开发Android TV游戏。OGEngine目前已停止更新,这个国产游戏引擎的悲剧在于推出时间太早,希望Android TV普及的时候卷土重来的OGEngine能让中国在游戏引擎方面领跑全世界。
 
LibGDX是一个跨平台的游戏开发框架,同样使用Java作为开发语言,前文所说的AndEngine就是基于LiBGDX实现的。LibGDX最大的优点就是极强的兼容性,不仅兼容Android和iOS,还兼容Windows、Linux、Max OS X等桌面系统。极强的兼容性还为开发提供了便利——不必打开Android模拟器,直接用电脑debug你的应用。在LibGDX和Android之间相互转型都很容易,知名的Android专家宋志辉、吴佳俊等都是从LibGDX转型Android的。
 
如果不要Java语言,那就有Cosos2d-x可供选择。《Cocos2d-x游戏开发实战精解》的作者欧桐桐(笔名:OTT)认为Android程序员一般对面向对象的知识掌握的比较全面,上手Cosos2d-x比较容易,并且Cosos2d-x是中国人维护的,文档全、资源多、教程多。OTT在得知我是和他一样的藏书人士后还特地送我一本他的大作鼓励我。

转型建议:做好心理准备,国内手游行业比普通的移动互联网行业加班更疯狂,建议刚入行没多久的Android工程师为了加班费转型,不建议30岁以上的Android工程师转型。
 
HTML5
HTML5也是Android工程师改行的好方向,HTML5在移动互联网领域应用非常广泛,比如混合开发、手机站、小游戏、微信公众号、微信小程序等。简单的手机站和对性能要求不高小游戏直接用从懒人模板(http://www.lanrenmb.com/)上找到的资源稍微修改一下即可,这里我只说说的混合开发应用和的小游戏怎么开发。
 
最著名的HTML5移动开发框架当属Facebook发布于2015年的React Native,这是一套跨平台、动态更新的 Javascript 框架,口号是“Learn once, write anywhere”。与之类似有同属舶来的PhoneGap等。
 
国产的HTML5开发框架在国内也百家争鸣,常见的有HBuilder和AppCan,二者共同特点是都为了便于新手入门制作了专用的编译器。2016年,在Qcon大会上宣布开源的Weex也异军突起,来自阿里的它因为开发的软件与原生App别无二致受到很多人的青睐。
 
开发对性能要求比较高的HTML5游戏,靠模板是不行的。2014年2月创立于北京的Egret是一套完整的HTML5游戏开发解决方案,其核心产品白鹭引擎(Egret Engine)凭借上手简便、性能强大已占据国内超七成的手机页游引擎市场份额。
 
Egret布道师徐聪(笔名:臭臭打不死人)还送我了Egret官方教程《Egret——HTML5游戏开发指南》和Egret吉祥物。
 
转型建议:一般来说,除非手机页游或商场,大多数用HTML5开发的Android应用就是胡闹。这条路线几乎是专为电商和小游戏行业准备的,如果公司有这方面的需求,Android程序员可以凭借平时自学的这方面技术完成任务。
 
VR
2015年底游戏外设王者雷蛇推出了VR游戏头显,2016年各大游戏厂商和小工作室争先恐后开发VR游戏争夺市场,开启了“中国VR元年”。虽然目前VR主要用在娱乐领域,被很多人视为玩具,但是VR所具有的价值却远远超出“玩具”的范畴。
 
前文讨论游戏引擎的时候没说Unity-3d不是疏漏,而是要把Unity-3d放在这儿谈。Unity-3d 是Unity公司开发的一个3D游戏开发工具,近年来的新版本不断加强对VR硬件系统的支持。Android程序员转型VR不仅可以实现自己从小就想让游戏跳出四角方框的梦想,还有Unity-3d所用的C#语言本来就是嚷着“我不是Java语言”的Java语言的学习优势。
 
转型建议:VR现在正是一片蓝海,只要自学能力够强,转型VR就像2015年在合肥买房一样明智。当然前提是你能找到愿意出钱的老板或投资人。
 
大数据
移动互联网时代是一个科技发达,信息流通的时代,大数据就是这个高科技时代的产物。马云曾在演讲中提到:未来的时代将不是IT时代,而是DT的时代。DT就是Data Technology(数据科技)的缩写,大数据的合理利用与否成了很多行业成败的关键。
 
移动互联网经过这些年的发展,拿O2O和当噱头已经唬不住投资人了。Hadoop也就自然而然受到了青睐,很多每4个月“生产”一批“两年经验”的“程序员”的培训机构也问我:“Android和iOS现在不吃香了,你能帮我介绍几个Hadoop讲师吗?”
 
转型建议:与转型Java后台一样,Android程序员转型Hadoop也具备语言相通,特性相似的优势。目前各大培训机构已经如蝇逐臭争相批量生产Hadoop程序员,如果你是因为陷入了他们培训的Android程序员造成的红海才转型的话,建议你不要转型,提升自己的竞争力才是王道。
 
人工智能和深度学习
前一阵子AlphaGo战胜了人类世界的围棋世界冠军柯洁,轰动了全世界。柯洁认为AlphaGo是能够打败一切的围棋上帝,这个说法我不敢苟同,毕竟它没有和“天”对弈过,但存在能“胜天半子”的人类——祁同伟。即使AlphaGo不能打败一切,也没有人有理由认为人工智能和深度学习不能成为IT届的重要发展方向。
 
TensorFlow是谷歌基于DistBelief进行研发的第二代人工智能学习系统,具备极佳的灵活性和可延展性,在和人工智能相关的领域都有广泛的应用。TensorFlow是开源的,会大大降低深度学习在各个行业中的应用难度,有远大的发展前景。
 
转型建议:尽管我坚信将来会T(ensor)F(low)的boys受女性欢迎程度不亚于TFboys,但TensorFlow暂时很不成熟,这个“将来”距今多久还是未知数。
 
Android系统
Linux作为目前大多数服务器的操作系统,学习Linux的大多数人的目的是做一个运维。然而把脑洞再开大一点的话,Android程序员精通了Linux之后可以开发一套属于自己的Android系统。《Linux大棚命令百篇》的作者吴鹏冲(笔名:Roc,和我一样也是水浒迷)和《循序渐进Linux》的作者高俊峰都送了一本自己的作品鼓励我开发属于自己的Android ORM。

这张照片摄于2016年3月30日我拿着《循序渐进Linux(第二版)》回到母校的自习室里攻读想成为像高老师一样能定制自己的Android系统的Linux专家的路上(双关)
 
转型建议:如果Android程序员准备跳槽到生产手机等搭载Android系统的硬件的厂商的话学习Linux再合适不过了,否则就只能自己刷机玩了。
 
产品经理
每个人都可能变成自己最讨厌的人,我也不例外。我从《人人都是产品经理》中学到了产品经理的情怀,还从《从点子到产品》中学到了产品经理的技术。还有幸赶上了今年3月《从点子到产品》的作者刘飞收徒。关于我转型产品经理失败的情况是一个发生在我和刘飞之间的“挖隋炀帝坟墓的开发商名叫杨勇”的故事:
2016年初,我带新人,没有收刘飞(同名学弟)为徒
2017年初,刘飞带新人,不肯收我为徒
 
转型建议:产品经理也是技术岗位,只不过写的是给人看的需求文档。如果一个Android程序员写的代码只能让电脑看懂而不能让负责维护的程序员看懂,那么就不要转型产品经理。
 
Android程序员转型机会虽然多,但不要因为看招聘网站上某个职业平均工资高就转型,随波逐流的弄潮儿必然会在浪潮之巅摔得好惨。培训机构常说“Android不吃香了,移动互联网的寒冬来了”来吸引人报名学习速成的Hadoop和TensorFlow,其实遭遇寒冬的不是某个行业,而是某些没有打好基础的人。

本文第三人称版本首发于51CTO的IT故事汇:http://mdsa.51cto.com/art/201706/543046.htm 查看全部
IT行业是一个瞬息万变的行业,程序员是一个不进则退的职业。我作为一个Android程序员,多年来一直保持随时可以转型其他技术领域的状态,保持对新技术敏感的嗅觉。
 
我先说说Android程序员不可能转型的几个方向,以下四个不靠谱方向的靠谱性递减:
首先不会转型iOS,iOS和Android工程师的工作内容都是大同小异的。
其次不会转型Windows Phone,好多Andr oid程序员就是受不了产品经理唠叨:“像QQ客户端那样做成和iOS一样”才转型的,怎么会转型比Android还难做成和iOS一样的WP?
再次不会转型Windows和MacOS等桌面软件,桌面开发周期长、难度大、升级不易,这是一个已经接近穷途末路的夕阳产业。
最后不会开JavaME或者Symbian的历史倒车,除非他有本事让每个用户都买(就一个“买”字,同时包含“想买”和“买得到”的意思)停产多年的机型。
 
我观察如今的技术形势,并亲身探索了一个Android程序员转型的几个技术方向的可行性:
 
Android病毒和恶意应用
最近肆虐全世界的WannaCry让安全成了IT圈最热的话题,开发腻了善意应用的Android工程师最便捷的转型方向就是开发Android病毒和恶意应用。在4.x时代对Android对敏感权限还不是很敏感的时候,我就研究过给肉鸡伪造短信记录和让肉鸡给通讯录里所有(或特定)联系人发送短信的病毒。去年还研究过窃取友商App推送内容、强杀友商App进程、卸载友商App甚至让友商App被卸载后就再也不能在这台肉鸡上安装的恶意应用(或应用里的恶意功能)。

 
转型建议:此外锁定肉鸡里的重要文件勒索用户(Android上的WannaCry?)和窃取肉鸡用户的支付密码的实现在技术上也像强奸8岁女童一样简单,只不过事后逍遥法外很难。这个转型方向只适合拿自己的手机当肉鸡玩玩,千万不要用这些技术赚钱

SDK
开发SDK本质上仍然在为Android应用开发软件,只是不直接开发Android应用。
 
每个Android程序员工作几年后都积累了属于自己的或大或小的类库,比如封装好的LogUtils和ToastUtils等;也都或多或少研究过常用开源框架的底层原理,比如了解Picasso和EventBus等;还应该对不开源的第三方服务有自己简单的二次封装,比如我就封装了一键实现支付宝和微信支付的moudle(免费的Ping++?)。
 
转型建议:尽管看见自己的链接出现在无数Android应用的Gradle文件的compile后面,开发了无数软件的一部分的成就感不会比开发完整的软件差。但是几乎没有老板会为了支持你开发开源软件发你工资。
 
JavaEE
Android程序员转型Java在基础知识方面是没什么难度的,毕竟语言相通,特性相似。同时每个Android程序员在大学时J2EE课程学得都不会很差,不过有些知识是该忘掉的,比如Hibernate已经落后于时代了,SpringMVC的全面使用才是Java后台的大势所趋。
 
转型建议:建议不想每天改UI的刚入行不久的Android工程师转型,我有好几个学弟就是参加工作后从Android转型Java的,他们过得都不错。很多工作年限较长的Android工程师本来就是JavaEE转型来的,就别转回去了。
 
手游
首先考虑不放弃Java语言和Android开发习惯的情况:最合适的就是能把游戏view直接插入普通layout里的AndEngine,前几年大红大紫的Flappy Bird就是用它开发的。AndEngine的开发方式和Android别无二致,且有丰富的开源demo。不过AndEngine没有官方文档,理论学习上有一定难度。我用AndEngine开发了我的毕业设计,参加工作后也用AndEngine获得了几个奖,我珍藏着一本AndEngine的非官方文档《Android游戏开发实践指南》(全新未拆封),期待着有一天能回到2014年把它送给那个买不起它的毕业生。

 
提到了AndEngine就不得不提国产AndEngine——OGEngine,它是基于AndEngine衍生的游戏引擎,有详细的纯中文文档和说汉语的技术支持杨城(笔名:小城),极适合开发Android TV游戏。OGEngine目前已停止更新,这个国产游戏引擎的悲剧在于推出时间太早,希望Android TV普及的时候卷土重来的OGEngine能让中国在游戏引擎方面领跑全世界。
 
LibGDX是一个跨平台的游戏开发框架,同样使用Java作为开发语言,前文所说的AndEngine就是基于LiBGDX实现的。LibGDX最大的优点就是极强的兼容性,不仅兼容Android和iOS,还兼容Windows、Linux、Max OS X等桌面系统。极强的兼容性还为开发提供了便利——不必打开Android模拟器,直接用电脑debug你的应用。在LibGDX和Android之间相互转型都很容易,知名的Android专家宋志辉、吴佳俊等都是从LibGDX转型Android的。
 
如果不要Java语言,那就有Cosos2d-x可供选择。《Cocos2d-x游戏开发实战精解》的作者欧桐桐(笔名:OTT)认为Android程序员一般对面向对象的知识掌握的比较全面,上手Cosos2d-x比较容易,并且Cosos2d-x是中国人维护的,文档全、资源多、教程多。OTT在得知我是和他一样的藏书人士后还特地送我一本他的大作鼓励我。

转型建议:做好心理准备,国内手游行业比普通的移动互联网行业加班更疯狂,建议刚入行没多久的Android工程师为了加班费转型,不建议30岁以上的Android工程师转型。
 
HTML5
HTML5也是Android工程师改行的好方向,HTML5在移动互联网领域应用非常广泛,比如混合开发、手机站、小游戏、微信公众号、微信小程序等。简单的手机站和对性能要求不高小游戏直接用从懒人模板(http://www.lanrenmb.com/)上找到的资源稍微修改一下即可,这里我只说说的混合开发应用和的小游戏怎么开发。
 
最著名的HTML5移动开发框架当属Facebook发布于2015年的React Native,这是一套跨平台、动态更新的 Javascript 框架,口号是“Learn once, write anywhere”。与之类似有同属舶来的PhoneGap等。
 
国产的HTML5开发框架在国内也百家争鸣,常见的有HBuilder和AppCan,二者共同特点是都为了便于新手入门制作了专用的编译器。2016年,在Qcon大会上宣布开源的Weex也异军突起,来自阿里的它因为开发的软件与原生App别无二致受到很多人的青睐。
 
开发对性能要求比较高的HTML5游戏,靠模板是不行的。2014年2月创立于北京的Egret是一套完整的HTML5游戏开发解决方案,其核心产品白鹭引擎(Egret Engine)凭借上手简便、性能强大已占据国内超七成的手机页游引擎市场份额。
 
Egret布道师徐聪(笔名:臭臭打不死人)还送我了Egret官方教程《Egret——HTML5游戏开发指南》和Egret吉祥物。
 
转型建议:一般来说,除非手机页游或商场,大多数用HTML5开发的Android应用就是胡闹。这条路线几乎是专为电商和小游戏行业准备的,如果公司有这方面的需求,Android程序员可以凭借平时自学的这方面技术完成任务。
 
VR
2015年底游戏外设王者雷蛇推出了VR游戏头显,2016年各大游戏厂商和小工作室争先恐后开发VR游戏争夺市场,开启了“中国VR元年”。虽然目前VR主要用在娱乐领域,被很多人视为玩具,但是VR所具有的价值却远远超出“玩具”的范畴。
 
前文讨论游戏引擎的时候没说Unity-3d不是疏漏,而是要把Unity-3d放在这儿谈。Unity-3d 是Unity公司开发的一个3D游戏开发工具,近年来的新版本不断加强对VR硬件系统的支持。Android程序员转型VR不仅可以实现自己从小就想让游戏跳出四角方框的梦想,还有Unity-3d所用的C#语言本来就是嚷着“我不是Java语言”的Java语言的学习优势。
 
转型建议:VR现在正是一片蓝海,只要自学能力够强,转型VR就像2015年在合肥买房一样明智。当然前提是你能找到愿意出钱的老板或投资人。
 
大数据
移动互联网时代是一个科技发达,信息流通的时代,大数据就是这个高科技时代的产物。马云曾在演讲中提到:未来的时代将不是IT时代,而是DT的时代。DT就是Data Technology(数据科技)的缩写,大数据的合理利用与否成了很多行业成败的关键。
 
移动互联网经过这些年的发展,拿O2O和当噱头已经唬不住投资人了。Hadoop也就自然而然受到了青睐,很多每4个月“生产”一批“两年经验”的“程序员”的培训机构也问我:“Android和iOS现在不吃香了,你能帮我介绍几个Hadoop讲师吗?”
 
转型建议:与转型Java后台一样,Android程序员转型Hadoop也具备语言相通,特性相似的优势。目前各大培训机构已经如蝇逐臭争相批量生产Hadoop程序员,如果你是因为陷入了他们培训的Android程序员造成的红海才转型的话,建议你不要转型,提升自己的竞争力才是王道。
 
人工智能和深度学习
前一阵子AlphaGo战胜了人类世界的围棋世界冠军柯洁,轰动了全世界。柯洁认为AlphaGo是能够打败一切的围棋上帝,这个说法我不敢苟同,毕竟它没有和“天”对弈过,但存在能“胜天半子”的人类——祁同伟。即使AlphaGo不能打败一切,也没有人有理由认为人工智能和深度学习不能成为IT届的重要发展方向。
 
TensorFlow是谷歌基于DistBelief进行研发的第二代人工智能学习系统,具备极佳的灵活性和可延展性,在和人工智能相关的领域都有广泛的应用。TensorFlow是开源的,会大大降低深度学习在各个行业中的应用难度,有远大的发展前景。
 
转型建议:尽管我坚信将来会T(ensor)F(low)的boys受女性欢迎程度不亚于TFboys,但TensorFlow暂时很不成熟,这个“将来”距今多久还是未知数。
 
Android系统
Linux作为目前大多数服务器的操作系统,学习Linux的大多数人的目的是做一个运维。然而把脑洞再开大一点的话,Android程序员精通了Linux之后可以开发一套属于自己的Android系统。《Linux大棚命令百篇》的作者吴鹏冲(笔名:Roc,和我一样也是水浒迷)和《循序渐进Linux》的作者高俊峰都送了一本自己的作品鼓励我开发属于自己的Android ORM。

这张照片摄于2016年3月30日我拿着《循序渐进Linux(第二版)》回到母校的自习室里攻读想成为像高老师一样能定制自己的Android系统的Linux专家的路上(双关)
 
转型建议:如果Android程序员准备跳槽到生产手机等搭载Android系统的硬件的厂商的话学习Linux再合适不过了,否则就只能自己刷机玩了。
 
产品经理
每个人都可能变成自己最讨厌的人,我也不例外。我从《人人都是产品经理》中学到了产品经理的情怀,还从《从点子到产品》中学到了产品经理的技术。还有幸赶上了今年3月《从点子到产品》的作者刘飞收徒。关于我转型产品经理失败的情况是一个发生在我和刘飞之间的“挖隋炀帝坟墓的开发商名叫杨勇”的故事:
2016年初,我带新人,没有收刘飞(同名学弟)为徒
2017年初,刘飞带新人,不肯收我为徒
 
转型建议:产品经理也是技术岗位,只不过写的是给人看的需求文档。如果一个Android程序员写的代码只能让电脑看懂而不能让负责维护的程序员看懂,那么就不要转型产品经理。
 
Android程序员转型机会虽然多,但不要因为看招聘网站上某个职业平均工资高就转型,随波逐流的弄潮儿必然会在浪潮之巅摔得好惨。培训机构常说“Android不吃香了,移动互联网的寒冬来了”来吸引人报名学习速成的Hadoop和TensorFlow,其实遭遇寒冬的不是某个行业,而是某些没有打好基础的人。

本文第三人称版本首发于51CTO的IT故事汇:http://mdsa.51cto.com/art/201706/543046.htm
0
回复
0
评论

Android 头像和昵称的修改 头像 昵称 显示

HaoBoy_ 发表了文章 • 43 次浏览 • 2017-06-20 15:44 • 来自相关话题

1.在EaseChatFragment中的EaseChatFragmentHelper类中的onSetMessageAttributes的方法中设置自己的头像和昵称, @Override
public void onSetMessageAttributes(EMMessage message) {
String username = xxx;//自定义名称
String avatar = yyy;//自定义头像

message.setAttribute("avatar", avatar);
message.setAttribute("username", username);
}

2.在easeUI中的EaseUserUtils类里面有两个方法setUserNick和setUserAvatar,分别是设置昵称和头像。
将setUserAvatar改为:/**
* 显示头像
* set user avatar
* @param message
*/
public static void setUserAvatar(Context context, EMMessage message, ImageView imageView){
if (message == null){
return;
}

//发送消息 显示本地头像
if (message.direct() == EMMessage.Direct.SEND){
Glide.with(context).load(localUrl).into(imageView);
return;
}else {
EaseUser easeUser = UserProfileCache.GetSpCacheUser(context, message.getFrom());
//本地已缓存了这个用户信息 直接绑定
if (easeUser != null){
String avatar = easeUser.getAvatar();
if(avatar != null){
try {
Glide.with(context).load(avatar).into(imageView);
} catch (Exception e) {
//use default avatar
Glide.with(context).load(R.drawable.ease_default_avatar).into(imageView); }
}else{
Glide.with(context).load(R.drawable.ease_default_avatar).into(imageView);
}
}
//本地没有缓存了这个用户信息 保存本地
else {
try {
String avatar = message.getStringAttribute("avatar");

if(avatar != null){
try {
Glide.with(context).load(avatar).into(imageView);
} catch (Exception e) {
//use default avatar
Glide.with(context).load(R.drawable.ease_default_avatar).into(imageView); }
}else{
Glide.with(context).load(R.drawable.ease_default_avatar).into(imageView);
}
EaseUserUtils.getUserInfo(context, message);
} catch (HyphenateException e) {
e.printStackTrace();
}
}
}
}

public static EaseUser getUserInfo(Context context, EMMessage message){

EaseUser user = null;
try {
String avatar = message.getStringAttribute("avatar");
String nick = message.getStringAttribute("username");

user = new EaseUser(message.getFrom());
user.setAvatar(avatar);
user.setNickname(nick);
EaseCommonUtils.setUserInitialLetter(user);
UserProfileCache.setSpCacheUser(context, message.getFrom(), user);
} catch (HyphenateException e) {
e.printStackTrace();
}
return user;
}
UserProfileCache为用户缓存类。public class UserProfileCache {

private static Gson gson = new Gson();
public static String SP_CACHE_USER = "sp_cache_user";

private static SharedPreferences spf;

public static void setSpCacheUser(Context context, String user_id, EaseUser cacheUser){
if (user_id == null){
return;
}
if (spf == null){
spf = context.getSharedPreferences(SP_CACHE_USER, Context.MODE_PRIVATE);
}
SharedPreferences.Editor editor = spf.edit();
editor.putString(user_id, gson.toJson(cacheUser));
editor.commit();
}

public static EaseUser GetSpCacheUser(Context context, String userId){
if (userId == null){
return null;
}
if (spf == null){
spf = context.getSharedPreferences(SP_CACHE_USER, Context.MODE_PRIVATE);
}
String cacheUserString = spf.getString(userId,null);
if (cacheUserString == null){
return null;
}else {
return gson.fromJson(cacheUserString,EaseUser.class);
}
}

}
注:这种方式不用再创建数据库,只是对本地消息记录的显示做了下调整。其实是可以做的更好,比如把消息记录存到自己服务器,什么问题都不是问题,但是目前这里没有过多要求,所以还是不去麻烦后台爸爸,自己在客户端改了。
  查看全部
1.在EaseChatFragment中的EaseChatFragmentHelper类中的onSetMessageAttributes的方法中设置自己的头像和昵称,
    @Override
public void onSetMessageAttributes(EMMessage message) {
String username = xxx;//自定义名称
String avatar = yyy;//自定义头像

message.setAttribute("avatar", avatar);
message.setAttribute("username", username);
}


2.在easeUI中的EaseUserUtils类里面有两个方法setUserNick和setUserAvatar,分别是设置昵称和头像。
将setUserAvatar改为:
/**
* 显示头像
* set user avatar
* @param message
*/
public static void setUserAvatar(Context context, EMMessage message, ImageView imageView){
if (message == null){
return;
}

//发送消息 显示本地头像
if (message.direct() == EMMessage.Direct.SEND){
Glide.with(context).load(localUrl).into(imageView);
return;
}else {
EaseUser easeUser = UserProfileCache.GetSpCacheUser(context, message.getFrom());
//本地已缓存了这个用户信息 直接绑定
if (easeUser != null){
String avatar = easeUser.getAvatar();
if(avatar != null){
try {
Glide.with(context).load(avatar).into(imageView);
} catch (Exception e) {
//use default avatar
Glide.with(context).load(R.drawable.ease_default_avatar).into(imageView); }
}else{
Glide.with(context).load(R.drawable.ease_default_avatar).into(imageView);
}
}
//本地没有缓存了这个用户信息 保存本地
else {
try {
String avatar = message.getStringAttribute("avatar");

if(avatar != null){
try {
Glide.with(context).load(avatar).into(imageView);
} catch (Exception e) {
//use default avatar
Glide.with(context).load(R.drawable.ease_default_avatar).into(imageView); }
}else{
Glide.with(context).load(R.drawable.ease_default_avatar).into(imageView);
}
EaseUserUtils.getUserInfo(context, message);
} catch (HyphenateException e) {
e.printStackTrace();
}
}
}
}

public static EaseUser getUserInfo(Context context, EMMessage message){

EaseUser user = null;
try {
String avatar = message.getStringAttribute("avatar");
String nick = message.getStringAttribute("username");

user = new EaseUser(message.getFrom());
user.setAvatar(avatar);
user.setNickname(nick);
EaseCommonUtils.setUserInitialLetter(user);
UserProfileCache.setSpCacheUser(context, message.getFrom(), user);
} catch (HyphenateException e) {
e.printStackTrace();
}
return user;
}

UserProfileCache为用户缓存类。
public class UserProfileCache {

private static Gson gson = new Gson();
public static String SP_CACHE_USER = "sp_cache_user";

private static SharedPreferences spf;

public static void setSpCacheUser(Context context, String user_id, EaseUser cacheUser){
if (user_id == null){
return;
}
if (spf == null){
spf = context.getSharedPreferences(SP_CACHE_USER, Context.MODE_PRIVATE);
}
SharedPreferences.Editor editor = spf.edit();
editor.putString(user_id, gson.toJson(cacheUser));
editor.commit();
}

public static EaseUser GetSpCacheUser(Context context, String userId){
if (userId == null){
return null;
}
if (spf == null){
spf = context.getSharedPreferences(SP_CACHE_USER, Context.MODE_PRIVATE);
}
String cacheUserString = spf.getString(userId,null);
if (cacheUserString == null){
return null;
}else {
return gson.fromJson(cacheUserString,EaseUser.class);
}
}

}

注:这种方式不用再创建数据库,只是对本地消息记录的显示做了下调整。其实是可以做的更好,比如把消息记录存到自己服务器,什么问题都不是问题,但是目前这里没有过多要求,所以还是不去麻烦后台爸爸,自己在客户端改了。
 
0
回复

音频播放问题 环信_管理后台 环信_WebIM PHP音频下载 PHP

回复

晓看天色暮看云 发起了问题 • 1 人关注 • 51 次浏览 • 2017-06-20 14:27 • 来自相关话题

0
回复

安卓集成环信群聊的时候,报出GroupAuthorizationException异常 群组权限异常

回复

jingshuiliushen 发起了问题 • 1 人关注 • 42 次浏览 • 2017-06-19 12:17 • 来自相关话题

0
回复

iOS SDK 3.3.2 中如何hook 所有将要被发送的消息?⬆️⬆️⬆️⬆️ 环信_iOS WilSend

回复

KevinGong 发起了问题 • 1 人关注 • 55 次浏览 • 2017-06-19 12:01 • 来自相关话题

0
回复

环信的sdk可以集成到h5app下面吗? 环信_Android

回复

cheryl 发起了问题 • 1 人关注 • 44 次浏览 • 2017-06-19 10:51 • 来自相关话题

0
回复

使用MediaRecorder录制声音的时候,环信的语音通话无声音 环信_Android

回复

じ☆ 渁 发起了问题 • 1 人关注 • 49 次浏览 • 2017-06-16 17:23 • 来自相关话题

0
回复

Android使用easeui集成,在回话页面怎么显示用户的头像和昵称? 环信_Android

回复

fengge 发起了问题 • 1 人关注 • 55 次浏览 • 2017-06-16 14:24 • 来自相关话题

0
回复

android 3.0以后怎么获取自定义消息中Ext消息 环信 ext 接收消息 3.0 Android

回复

╰☆╮末↘ 发起了问题 • 1 人关注 • 54 次浏览 • 2017-06-16 10:30 • 来自相关话题

0
评论

环信Web IM 新版本发布,提供更为丰富的群组、聊天室功能,现在更新就送小风扇! 产品更新 Web IM

产品更新 发表了文章 • 56 次浏览 • 2017-06-15 16:06 • 来自相关话题

  六月的骄阳,暑气留恋,但这风风火火却远不能掩盖季节的丰富内涵。环信发布了WEB新版本,十余项更新,带来了更加丰富的群组、聊天室功能。环信还为小伙伴们准备了一批小风扇,参与使用新版本并在文章下方跟帖使用反馈,就能获得环信usb小风扇,数量有限,先到先得!




环信USB小风扇
Web IM v1.4.11 2017-06-14

 新功能:
[sdk] debug.js融合到sdk当中,优化日志内容输出[sdk] 通过Rest屏蔽群组[sdk] 通过Rest发出入群申请[sdk] 通过Rest获取群组列表[sdk] 通过Rest根据groupid获取群组详情[sdk] 通过Rest列出某用户所加入的所有群组[sdk] 通过Rest列出群组的所有成员[sdk] 通过Rest禁止群用户发言[sdk] 通过Rest取消对用户禁言的禁止[sdk] 通过Rest获取群组下所有管理员[sdk] 通过Rest获取群组下所有被禁言成员[sdk] 通过Rest设置群管理员[sdk] 通过Rest取消群管理员[sdk] 通过Rest同意用户加入群[sdk] 通过Rest拒绝用户加入群[sdk] 通过Rest添加用户至群组黑名单(单个)[sdk] 通过Rest添加用户至群组黑名单(批量)[sdk] 通过Rest将用户从群黑名单移除(单个)[sdk] 通过Rest将用户从群黑名单移除(批量)[demo] 聊天窗口中记录可清空[demo] 聊天窗口中发送方聊天记录显示状态(未送达、已送达、已读)[demo] 查看聊天室成员[demo] 通过链接直接打开与好友的对话框[demo] 新增申请加入公开群面板[demo] 在申请加入公开群面板可下拉分页获取公开群[demo] 在申请加入公开群面板可点击群名称可查看群详情[demo] 在申请加入公开群面板可搜索群查看群详情[demo] 在申请加入公开群面板群详情页面可申请加入群组[demo] 群主可同意、拒绝加群申请[demo] 在群主的群成员列表中新增添加/移除管理员、禁言/解禁群成员按钮

Bug修复:
[sdk] 添加好友会产生多余的订阅消息[sdk] 频繁的发送消息会导致消息id重复的问题[sdk] 适配SDK发送文件和图片的大小[demo] 优化sdk/demo.html,修复某些依赖文件找不到的问题[demo] 修复离线消息数量统计不准确问题
 
webim在线体验:https://webim.easemob.com

版本历史:更新日志
 
SDK下载:下载地址 查看全部
  六月的骄阳,暑气留恋,但这风风火火却远不能掩盖季节的丰富内涵。环信发布了WEB新版本,十余项更新,带来了更加丰富的群组、聊天室功能。环信还为小伙伴们准备了一批小风扇,参与使用新版本并在文章下方跟帖使用反馈,就能获得环信usb小风扇,数量有限,先到先得!

d1fddd2e88747ea05968d89f6b696963.jpg

环信USB小风扇


Web IM v1.4.11 2017-06-14

 新功能:
  • [sdk] debug.js融合到sdk当中,优化日志内容输出
  • [sdk] 通过Rest屏蔽群组
  • [sdk] 通过Rest发出入群申请
  • [sdk] 通过Rest获取群组列表
  • [sdk] 通过Rest根据groupid获取群组详情
  • [sdk] 通过Rest列出某用户所加入的所有群组
  • [sdk] 通过Rest列出群组的所有成员
  • [sdk] 通过Rest禁止群用户发言
  • [sdk] 通过Rest取消对用户禁言的禁止
  • [sdk] 通过Rest获取群组下所有管理员
  • [sdk] 通过Rest获取群组下所有被禁言成员
  • [sdk] 通过Rest设置群管理员
  • [sdk] 通过Rest取消群管理员
  • [sdk] 通过Rest同意用户加入群
  • [sdk] 通过Rest拒绝用户加入群
  • [sdk] 通过Rest添加用户至群组黑名单(单个)
  • [sdk] 通过Rest添加用户至群组黑名单(批量)
  • [sdk] 通过Rest将用户从群黑名单移除(单个)
  • [sdk] 通过Rest将用户从群黑名单移除(批量)
  • [demo] 聊天窗口中记录可清空
  • [demo] 聊天窗口中发送方聊天记录显示状态(未送达、已送达、已读)
  • [demo] 查看聊天室成员
  • [demo] 通过链接直接打开与好友的对话框
  • [demo] 新增申请加入公开群面板
  • [demo] 在申请加入公开群面板可下拉分页获取公开群
  • [demo] 在申请加入公开群面板可点击群名称可查看群详情
  • [demo] 在申请加入公开群面板可搜索群查看群详情
  • [demo] 在申请加入公开群面板群详情页面可申请加入群组
  • [demo] 群主可同意、拒绝加群申请
  • [demo] 在群主的群成员列表中新增添加/移除管理员、禁言/解禁群成员按钮


Bug修复:
  • [sdk] 添加好友会产生多余的订阅消息
  • [sdk] 频繁的发送消息会导致消息id重复的问题
  • [sdk] 适配SDK发送文件和图片的大小
  • [demo] 优化sdk/demo.html,修复某些依赖文件找不到的问题
  • [demo] 修复离线消息数量统计不准确问题

 
webim在线体验:https://webim.easemob.com

版本历史:更新日志
 
SDK下载:下载地址
0
评论

Android 守护进程的实现方式 进程 Android 推送

beyond 发表了文章 • 59 次浏览 • 2017-06-15 14:38 • 来自相关话题

    Android平台的进程保活,一直是Android开发者心中一道坎,想必大家都曾为这个问题和老板、客户激励讨论过,虽然大多时候你的解释都被当成了孔乙己在鲁镇的小酒馆说的“之乎者也”,不被接受成为谈资!
Android进程保活的教程随便在网上一搜,各种神乎其乎的操作都有,但我大Android日新月异的机型还有国内各家rom大大,可以负责的讲,至今那些保活的教程没有一个靠谱的!
  同为创业公司,我很能理解各位老板们保活的需求,但同时作为Android开发者也有责任去维护Android的生态环境。现在很多Android开发工程师,主力机居然是iPhone而不是Android设备,感到相当悲哀。
 
友情提示:
   本篇教程能让我们的程序在后台运行的时间长一些,或者在被干掉的时候,能够重新站起来,需要注意不是每次都有效,也不是在所有的设备的上都有效的,所以,想让自己程序在Android里永生不死的,请忽略本文!请忽略!

守护进程的实现,本文两个核心观点:
提高进程优先级,降低被回收或杀死概率在进程被干掉后,进行拉起

要实现实现上边所说,通过下边几点来实现,首先我们需要了解下进程的优先级划分:

Process Importance记录在ActivityManager.java类中:**
* Path:SDK/sources/android-25/android/app/ActivityManager#RunningAppProcessInfo.java
*
* 这个进程正在运行前台UI,也就是说,它是当前在屏幕顶部的东西,用户正在进行交互的而进程
*/
public static final int IMPORTANCE_FOREGROUND = 100;

/**
* 此进程正在运行前台服务,即使用户不是在应用中时也执行音乐播放,这一般表示该进程正在做用户积极关心的事情
*/
public static final int IMPORTANCE_FOREGROUND_SERVICE = 125;
/**
* 这个过程不是用户的直接意识到,但在某种程度上是他们可以察觉的。
*/
public static final int IMPORTANCE_PERCEPTIBLE = 130;

/**
* 此进程正在运行前台UI,但设备处于睡眠状态,因此用户不可见,意思是用户意识不到的进程,因为他们看不到或与它交互,
* 但它是相当重要,因为用户解锁设备时期望的返回到这个进程
*/
public static final int IMPORTANCE_TOP_SLEEPING = 150;

/**
* 进程在后台,但我们不能恢复它的状态,所以我们想尽量避免杀死它,不然这个而进程就丢了
*/
public static final int IMPORTANCE_CANT_SAVE_STATE = 170;

/**
* 此进程正在运行某些对用户主动可见的内容,但不是直接显示在UI,
* 这可能运行在当前前台之后的窗口(因此暂停并且其状态被保存,不与用户交互,但在某种程度上对他们可见);
* 也可能在系统的控制下运行其他服务,
*/
public static final int IMPORTANCE_VISIBLE = 200;

/**
* 服务进程,此进程包含在后台保持运行的服务,这些后台服务用户察觉不到,是无感知的,所以它们可以由系统相对自由地杀死
*/
public static final int IMPORTANCE_SERVICE = 300;

/**
* 后台进程
*/
public static final int IMPORTANCE_BACKGROUND = 400;

/**
* 空进程,此进程没有任何正在运行的代码
*/
public static final int IMPORTANCE_EMPTY = 500;

// 此过程不存在。
public static final int IMPORTANCE_GONE = 1000;进程回收机制

了解进程优先级之后,我们还需要知道一个进程回收机制的东西;这里参考AngelDevil在博客园上的一篇文章:
 
详情参考:【Android Low Memory Killer】
 
Android的Low Memory Killer基于Linux的OOM机制,在Linux中,内存是以页面为单位分配的,当申请页面分配时如果内存不足会通过以下流程选择bad进程来杀掉从而释放内存:
alloc_pages -> out_of_memory() -> select_bad_process() -> badness()在Low Memory Killer中通过进程的oom_adj与占用内存的大小决定要杀死的进程,oom_adj越小越不容易被杀死;
Low Memory Killer Driver在用户空间指定了一组内存临界值及与之一一对应的一组oom_adj值,当系统剩余内存位于内存临界值中的一个范围内时,如果一个进程的oom_adj值大于或等于这个临界值对应的oom_adj值就会被杀掉。

下边是表示Process State(即老版本里的OOM_ADJ)数值对照表,数值越大,重要性越低,在新版SDK中已经在android层去除了小于0的进程状态// Path:SDK/sources/android-25/android/app/ActivityManager#RunningAppProcessInfo.java
// 进程不存在。
public static final int PROCESS_STATE_NONEXISTENT = -1;
// 进程是一个持久的系统进程,一般指当前 UI 进程
public static final int PROCESS_STATE_PERSISTENT = 0;
// 进程是一个持久的系统进程,正在做和 UI 相关的操作,但不直接显示
public static final int PROCESS_STATE_PERSISTENT_UI = 1;
// 进程正在托管当前的顶级活动。请注意,这涵盖了用户可见的所有活动。
public static final int PROCESS_STATE_TOP = 2;
// 进程由于系统绑定而托管前台服务。
public static final int PROCESS_STATE_BOUND_FOREGROUND_SERVICE = 3;
// 进程正在托管前台服务。
public static final int PROCESS_STATE_FOREGROUND_SERVICE = 4;
// 与{@link #PROCESS_STATE_TOP}相同,但设备处于睡眠状态。
public static final int PROCESS_STATE_TOP_SLEEPING = 5;
// 进程对用户很重要,是他们知道的东西
public static final int PROCESS_STATE_IMPORTANT_FOREGROUND = 6;
// 进程对用户很重要,但不是他们知道的
public static final int PROCESS_STATE_IMPORTANT_BACKGROUND = 7;
// 进程在后台运行备份/恢复操作
public static final int PROCESS_STATE_BACKUP = 8;
// 进程在后台,但我们不能恢复它的状态,所以我们想尽量避免杀死它,不然这个而进程就丢了
public static final int PROCESS_STATE_HEAVY_WEIGHT = 9;
// 进程在后台运行一个服务,与oom_adj不同,此级别用于正常运行在后台状态和执行操作状态。
public static final int PROCESS_STATE_SERVICE = 10;
// 进程在后台运行一个接收器,注意,从oom_adj接收器的角度来看,在较高的前台级运行,但是对于我们的优先级,这不是必需的,并且将它们置于服务之下意味着当它们接收广播时,一些进程状态中的更少的改变。
public static final int PROCESS_STATE_RECEIVER = 11;
// 进程在后台,但主持家庭活动
public static final int PROCESS_STATE_HOME = 12;
// 进程在后台,但托管最后显示的活动
public static final int PROCESS_STATE_LAST_ACTIVITY = 13;
// 进程正在缓存以供以后使用,并包含活动
public static final int PROCESS_STATE_CACHED_ACTIVITY = 14;
// 进程正在缓存供以后使用,并且是包含活动的另一个缓存进程的客户端
public static final int PROCESS_STATE_CACHED_ACTIVITY_CLIENT = 15;
// 进程正在缓存以供以后使用,并且为空
public static final int PROCESS_STATE_CACHED_EMPTY = 16;Process State(即老版本的OOM_ADJ)与Process Importance对应关系,这个方法也是在ActivityManager.java类中,有了这个关系,就知道可以知道我们的应用处于哪个级别,对于我们后边优化有个很好地参考/**
* Path:SDK/sources/android-25/android/app/ActivityManager#RunningAppProcessInfo.java
*
* 通过这个方法,将Linux底层的 OOM_ADJ级别码和 android 层面的进程重要程度联系了起来
*/
public static int procStateToImportance(int procState) {
if (procState == PROCESS_STATE_NONEXISTENT) {
return IMPORTANCE_GONE;
} else if (procState >= PROCESS_STATE_HOME) {
return IMPORTANCE_BACKGROUND;
} else if (procState >= PROCESS_STATE_SERVICE) {
return IMPORTANCE_SERVICE;
} else if (procState > PROCESS_STATE_HEAVY_WEIGHT) {
return IMPORTANCE_CANT_SAVE_STATE;
} else if (procState >= PROCESS_STATE_IMPORTANT_BACKGROUND) {
return IMPORTANCE_PERCEPTIBLE;
} else if (procState >= PROCESS_STATE_IMPORTANT_FOREGROUND) {
return IMPORTANCE_VISIBLE;
} else if (procState >= PROCESS_STATE_TOP_SLEEPING) {
return IMPORTANCE_TOP_SLEEPING;
} else if (procState >= PROCESS_STATE_FOREGROUND_SERVICE) {
return IMPORTANCE_FOREGROUND_SERVICE;
} else {
return IMPORTANCE_FOREGROUND;
}
}一般情况下,设备端进程被干掉有一下几种情况




由以上分析,我们可以可以总结出,如果想提高我们应用后台运行时间,就需要提高当前应用进程优先级,来减少被杀死的概率

守护进程的实现

分析了那么多,现在对Android自身后台进程管理,以及进程的回收也有了一个大致的了解,后边我们要做的就是想尽一切办法去提高应用进程优先级,降低进程被杀的概率;或者是在被杀死后能够重新启动后台守护进程

1.模拟前台进程

第一种方式就是利用系统漏洞,使用startForeground()将当前进程伪装成前台进程,将进程优先级提高到最高(这里所说的最高是服务所能达到的最高,即1);

这种方式在7.x之前都是很好用的,QQ、微信、IReader、Keep 等好多应用都是用的这种方式实现;因为在7.x 以后的设备上,这种伪装前台进程的方式也会显示出来通知栏提醒,这个是取消不掉的,虽然Google现在还没有对这种方式加以限制,不过这个已经能够被用户感知到了,这种方式估计也用不了多久了

下边看下实现方式,这边这个VMDaemonService就是一个守护进程服务,其中在服务的onStartCommand()方法中调用startForeground()将服务进程设置为前台进程,当运行在 API18 以下的设备是可以直接设置,API18 以上需要实现一个内部的Service,这个内部类实现和外部类同样的操作,然后结束自己;当这个服务启动后就会创建一个定时器去发送广播,当我们的核心服务被干掉后,就由另外的广播接收器去接收我们守护进程发出的广播,然后唤醒我们的核心服务;
/**
* 以实现内部 Service 类的方式实现守护进程,这里是利用 android 漏洞提高当前进程优先级
*
* Created by lzan13 on 2017/3/7.
*/
public class VMDaemonService extends Service {

private final static String TAG = VMDaemonService.class.getSimpleName();

// 定时唤醒的时间间隔,这里为了自己测试方边设置了一分钟
private final static int ALARM_INTERVAL = 1 * 60 * 1000;
// 发送唤醒广播请求码
private final static int WAKE_REQUEST_CODE = 5121;
// 守护进程 Service ID
private final static int DAEMON_SERVICE_ID = -5121;

@Override public void onCreate() {
Log.i(TAG, "VMDaemonService->onCreate");
super.onCreate();
}

@Override public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "VMDaemonService->onStartCommand");
// 利用 Android 漏洞提高进程优先级,
startForeground(DAEMON_SERVICE_ID, new Notification());
// 当 SDk 版本大于18时,需要通过内部 Service 类启动同样 id 的 Service
if (Build.VERSION.SDK_INT >= 18) {
Intent innerIntent = new Intent(this, DaemonInnerService.class);
startService(innerIntent);
}

// 发送唤醒广播来促使挂掉的UI进程重新启动起来
AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
Intent alarmIntent = new Intent();
alarmIntent.setAction(VMWakeReceiver.DAEMON_WAKE_ACTION);

PendingIntent operation = PendingIntent.getBroadcast(this, WAKE_REQUEST_CODE, alarmIntent,
PendingIntent.FLAG_UPDATE_CURRENT);

alarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(),
ALARM_INTERVAL, operation);

/**
* 这里返回值是使用系统 Service 的机制自动重新启动,不过这种方式以下两种方式不适用:
* 1.Service 第一次被异常杀死后会在5秒内重启,第二次被杀死会在10秒内重启,第三次会在20秒内重启,一旦在短时间内 Service 被杀死达到5次,则系统不再拉起。
* 2.进程被取得 Root 权限的管理工具或系统工具通过 forestop 停止掉,无法重启。
*/
return START_STICKY;
}

@Override public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("onBind 未实现");
}

@Override public void onDestroy() {
Log.i(TAG, "VMDaemonService->onDestroy");
super.onDestroy();
}

/**
* 实现一个内部的 Service,实现让后台服务的优先级提高到前台服务,这里利用了 android 系统的漏洞,
* 不保证所有系统可用,测试在7.1.1 之前大部分系统都是可以的,不排除个别厂商优化限制
*/
public static class DaemonInnerService extends Service {

@Override public void onCreate() {
Log.i(TAG, "DaemonInnerService -> onCreate");
super.onCreate();
}

@Override public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "DaemonInnerService -> onStartCommand");
startForeground(DAEMON_SERVICE_ID, new Notification());
stopSelf();
return super.onStartCommand(intent, flags, startId);
}

@Override public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("onBind 未实现");
}

@Override public void onDestroy() {
Log.i(TAG, "DaemonInnerService -> onDestroy");
super.onDestroy();
}
}
}当我们启动这个守护进程的时候,就可以使用以下adb命令查看当前程序的进程情况(需要adb shell进去设备),
为了等下区分进程优先级,我启动了一个普通的后台进程,两外两个一个是我们启动的守护进程,一个是当前程序的核心进程,可以看到除了后台进程外,另外两个进程都带有isForeground=true的属性:# 这个命令的 services 可以换成 service,这样会只显示当前,进程,不显示详细内容
# dumpsys activity services <Your Package Name>
root@vbox86p:/ # dumpsys activity services com.vmloft.develop.daemon
ACTIVITY MANAGER SERVICES (dumpsys activity services)
User 0 active services:
* ServiceRecord{170fe1dd u0 com.vmloft.develop.daemon/.services.VMDaemonService}
intent={cmp=com.vmloft.develop.daemon/.services.VMDaemonService}
packageName=com.vmloft.develop.daemon
processName=com.vmloft.develop.daemon:daemon
baseDir=/data/app/com.vmloft.develop.daemon-1/base.apk
dataDir=/data/data/com.vmloft.develop.daemon
app=ProcessRecord{173fe77f 2370:com.vmloft.develop.daemon:daemon/u0a68}
isForeground=true foregroundId=-5121 foregroundNoti=Notification(pri=0 contentView=com.vmloft.develop.daemon/0x1090077 vibrate=null sound=null defaults=0x0 flags=0x62 color=0xff607d8b vis=PRIVATE)
createTime=-6s196ms startingBgTimeout=--
lastActivity=-6s157ms restartTime=-6s157ms createdFromFg=true
startRequested=true delayedStop=false stopIfKilled=false callStart=true lastStartId=1

* ServiceRecord{2fee4f84 u0 com.vmloft.develop.daemon/.services.VMCoreService}
intent={cmp=com.vmloft.develop.daemon/.services.VMCoreService}
packageName=com.vmloft.develop.daemon
processName=com.vmloft.develop.daemon
baseDir=/data/app/com.vmloft.develop.daemon-1/base.apk
dataDir=/data/data/com.vmloft.develop.daemon
app=ProcessRecord{18c6a1b4 2343:com.vmloft.develop.daemon/u0a68}
isForeground=true foregroundId=-5120 foregroundNoti=Notification(pri=0 contentView=com.vmloft.develop.daemon/0x1090077 vibrate=null sound=null defaults=0x0 flags=0x62 color=0xff607d8b vis=PRIVATE)
createTime=-28s136ms startingBgTimeout=--
lastActivity=-28s136ms restartTime=-28s136ms createdFromFg=true
startRequested=true delayedStop=false stopIfKilled=false callStart=true lastStartId=1

* ServiceRecord{2ef6909e u0 com.vmloft.develop.daemon/.services.VMBackgroundService}
intent={cmp=com.vmloft.develop.daemon/.services.VMBackgroundService}
packageName=com.vmloft.develop.daemon
processName=com.vmloft.develop.daemon:background
baseDir=/data/app/com.vmloft.develop.daemon-1/base.apk
dataDir=/data/data/com.vmloft.develop.daemon
app=ProcessRecord{29f8734c 2388:com.vmloft.develop.daemon:background/u0a68}
createTime=-3s279ms startingBgTimeout=--
lastActivity=-3s262ms restartTime=-3s262ms createdFromFg=true
startRequested=true delayedStop=false stopIfKilled=false callStart=true lastStartId=1然后我们可以用下边的命令查看ProcessID# 这个命令可以查看当前DProcessID(数据结果第二列),我们可以看到当前程序有两个进程
# ps | grep com.vmloft.develop.daemon
root@vbox86p:/ # ps | grep com.vmloft.develop.daemon
u0_a68 2343 274 1012408 42188 ffffffff f74f1b45 S com.vmloft.develop.daemon
u0_a68 2370 274 997012 26152 ffffffff f74f1b45 S com.vmloft.develop.daemon:daemon
u0_a68 2388 274 997012 25668 ffffffff f74f1b45 S com.vmloft.develop.daemon:background有了ProcessID之后,我们可以根据这个ProcessID获取到当前进程的优先级状态Process State,对应Linux层的oom_adj
可以看到当前核心进程的级别为0,因为这个表示当前程序运行在前台 UI 界面,守护进程级别为1,因为我们利用漏洞设置成了前台进程,虽然不可见,但是他的级别也是比较高的,仅次于前台 UI 进程,然后普通后台进程级别为4;当我们退到后台时,可以看到核心进程的级别变为1了,这就是因为我们利用startForeground()将进程设置成前台进程的原因,这样就降低了进程被系统回收的概率了;# 这个命令就是通过 ProcessID 输出其对应 oom_adj
# cat /proc/ProcessID/oom_adj
# 程序在前台时,查询进程级别
root@vbox86p:/ # cat /proc/2343/oom_adj
0
root@vbox86p:/ # cat /proc/2370/oom_adj
1
root@vbox86p:/ # cat /proc/2388/oom_adj
4
# 当程序退到后台时,再次查看进程级别
root@vbox86p:/ # cat /proc/2343/oom_adj
1
root@vbox86p:/ # cat /proc/2370/oom_adj
1
root@vbox86p:/ # cat /proc/2388/oom_adj
4可以看到这种方式确实能够提高进程优先级,但是在一些国产的设备上还是会被杀死的,比我我测试的时候小米点击清空最近运行的应用进程就别干掉了;当把应用加入到设备白名单里就不会被杀死了,微信就是这样,人家直接装上之后就已经在白名单里了,我们要做的就是在用户使用中引导他们将我们的程序设置进白名单,将守护进程和白名单结合起来,这样才能保证我们的应用持续或者

2.JobScheduler机制唤醒

Android系统在5.x以上版本提供了一个JobSchedule接口,系统会根据自己实现定时去调用改接口传递的进程去实现一些操作,而且这个接口在被强制停止后依然能够正常的启动;不过在一些国产设备上可能无效,比如小米;
下边是 JobServcie 的实现:/**
* 5.x 以上使用 JobService 实现守护进程,这个守护进程要做的工作很简单,就是启动应用的核心进程
* Created by lzan13 on 2017/3/8.
*/
@TargetApi(Build.VERSION_CODES.LOLLIPOP) public class VMDaemonJobService extends JobService {

private final static String TAG = VMDaemonJobService.class.getSimpleName();

@Override public boolean onStartJob(JobParameters params) {
Log.d(TAG, "onStartJob");
// 这里为了掩饰直接启动核心进程,没有做其他判断操作
startService(new Intent(getApplicationContext(), VMCoreService.class));
return false;
}

@Override public boolean onStopJob(JobParameters params) {
Log.d(TAG, "onStopJob");
return false;
}
}我们要做的就是在需要的时候调用JobSchedule的schedule来启动任务;剩下的就不需要关心了,JobSchedule会帮我们做好,下边就是我这边实现的启动任务的方法:/**
* 5.x以上系统启用 JobScheduler API 进行实现守护进程的唤醒操作
*/
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void startJobScheduler() {
int jobId = 1;
JobInfo.Builder jobInfo = new JobInfo.Builder(jobId, new ComponentName(this, VMDaemonJobService.class));
jobInfo.setPeriodic(10000);
jobInfo.setPersisted(true);
JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
jobScheduler.schedule(jobInfo.build());
}3.系统 Service START_STICKY 机制重启

在实现Service类时,将onStartCommand()返回值设置为START_STICKY,利用系统机制在Service挂掉后自动拉活;不过这种方式只适合比较原生一些的系统,像小米,华为等这些定制化比较高的第三方厂商,他们都已经把这些给限制掉了;@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "VMDaemonService->onStartCommand");
/**
* 这里返回值是使用系统 Service 的机制自动重新启动,不过这种方式以下两种方式不适用:
* 1.Service 第一次被异常杀死后会在5秒内重启,第二次被杀死会在10秒内重启,第三次会在20秒内重启,一旦在短时间内 Service 被杀死达到5次,则系统不再拉起。
* 2.进程被取得 Root 权限的管理工具或系统工具通过 forestop 停止掉,无法重启。
* 3.一些定制化比较高的第三方系统也不适用
*/
return START_STICKY;
}这种方式在以下两种情况无效:
Service第一次被异常杀死后会在5秒内重启,第二次被杀死会在10秒内重启,第三次会在20秒内重启,一旦在短时间内Service被杀死达到5次,这个服务就不能再次重启了;进程被取得Root权限的管理工具或系统工具通过fores-top方式停止掉,无法重启;一些定制化比较高的第三方系统也不适用

4.其他保活方式

利用 Native 本地进程,这个主要使用到 jni 调用底层实现,而且在 Android 5.x 以后对这个限制也比较高,不适用了,暂时不研究集成第三方SDK互相唤醒,这个只要正常集成了第三方的SDK,并使用了他们对应的服务,当一个设备安装的多个应用都集成了某一个第三方SDK时,启动任意一个 app 都会唤醒其他的 app,不过这个在一些新版的国内厂商系统也是做了限制,这种方式并没有什么效果一像素的 Activity 方式(流氓方式),经测试一些手机系统无法检测到解锁和锁屏,不确定是否系统修改了解锁或者锁屏的广播,还是禁用了这些广播,因此此方式无效;

结语

事事没有绝对,万物总有一些漏洞,就算上边的那些方式不可用了,后边肯定还会出现其他的方式;我们不能保证我们的应用不死,但我们可以提高存活率;

其实最好的方式还是把程序做好,让程序本身深入人心,别人喜欢你了,就算你被干掉了,他们也会主动的把你拉起来,然后把你加入他们的白名单,然后我们的目的就实现了不是 查看全部
    Android平台的进程保活,一直是Android开发者心中一道坎,想必大家都曾为这个问题和老板、客户激励讨论过,虽然大多时候你的解释都被当成了孔乙己在鲁镇的小酒馆说的“之乎者也”,不被接受成为谈资!
Android进程保活的教程随便在网上一搜,各种神乎其乎的操作都有,但我大Android日新月异的机型还有国内各家rom大大,可以负责的讲,至今那些保活的教程没有一个靠谱的!
  同为创业公司,我很能理解各位老板们保活的需求,但同时作为Android开发者也有责任去维护Android的生态环境。现在很多Android开发工程师,主力机居然是iPhone而不是Android设备,感到相当悲哀。
 
友情提示:
   本篇教程能让我们的程序在后台运行的时间长一些,或者在被干掉的时候,能够重新站起来,需要注意不是每次都有效,也不是在所有的设备的上都有效的,所以,想让自己程序在Android里永生不死的,请忽略本文!请忽略!

守护进程的实现,本文两个核心观点:
  1. 提高进程优先级,降低被回收或杀死概率
  2. 在进程被干掉后,进行拉起


要实现实现上边所说,通过下边几点来实现,首先我们需要了解下进程的优先级划分:

Process Importance记录在ActivityManager.java类中:
**
* Path:SDK/sources/android-25/android/app/ActivityManager#RunningAppProcessInfo.java
*
* 这个进程正在运行前台UI,也就是说,它是当前在屏幕顶部的东西,用户正在进行交互的而进程
*/
public static final int IMPORTANCE_FOREGROUND = 100;

/**
* 此进程正在运行前台服务,即使用户不是在应用中时也执行音乐播放,这一般表示该进程正在做用户积极关心的事情
*/
public static final int IMPORTANCE_FOREGROUND_SERVICE = 125;
/**
* 这个过程不是用户的直接意识到,但在某种程度上是他们可以察觉的。
*/
public static final int IMPORTANCE_PERCEPTIBLE = 130;

/**
* 此进程正在运行前台UI,但设备处于睡眠状态,因此用户不可见,意思是用户意识不到的进程,因为他们看不到或与它交互,
* 但它是相当重要,因为用户解锁设备时期望的返回到这个进程
*/
public static final int IMPORTANCE_TOP_SLEEPING = 150;

/**
* 进程在后台,但我们不能恢复它的状态,所以我们想尽量避免杀死它,不然这个而进程就丢了
*/
public static final int IMPORTANCE_CANT_SAVE_STATE = 170;

/**
* 此进程正在运行某些对用户主动可见的内容,但不是直接显示在UI,
* 这可能运行在当前前台之后的窗口(因此暂停并且其状态被保存,不与用户交互,但在某种程度上对他们可见);
* 也可能在系统的控制下运行其他服务,
*/
public static final int IMPORTANCE_VISIBLE = 200;

/**
* 服务进程,此进程包含在后台保持运行的服务,这些后台服务用户察觉不到,是无感知的,所以它们可以由系统相对自由地杀死
*/
public static final int IMPORTANCE_SERVICE = 300;

/**
* 后台进程
*/
public static final int IMPORTANCE_BACKGROUND = 400;

/**
* 空进程,此进程没有任何正在运行的代码
*/
public static final int IMPORTANCE_EMPTY = 500;

// 此过程不存在。
public static final int IMPORTANCE_GONE = 1000;
进程回收机制

了解进程优先级之后,我们还需要知道一个进程回收机制的东西;这里参考AngelDevil在博客园上的一篇文章:
 
详情参考:【Android Low Memory Killer】
 
Android的Low Memory Killer基于Linux的OOM机制,在Linux中,内存是以页面为单位分配的,当申请页面分配时如果内存不足会通过以下流程选择bad进程来杀掉从而释放内存:
alloc_pages -> out_of_memory() -> select_bad_process() -> badness()
在Low Memory Killer中通过进程的oom_adj与占用内存的大小决定要杀死的进程,oom_adj越小越不容易被杀死;
Low Memory Killer Driver在用户空间指定了一组内存临界值及与之一一对应的一组oom_adj值,当系统剩余内存位于内存临界值中的一个范围内时,如果一个进程的oom_adj值大于或等于这个临界值对应的oom_adj值就会被杀掉。

下边是表示Process State(即老版本里的OOM_ADJ)数值对照表,数值越大,重要性越低,在新版SDK中已经在android层去除了小于0的进程状态
// Path:SDK/sources/android-25/android/app/ActivityManager#RunningAppProcessInfo.java 
// 进程不存在。
public static final int PROCESS_STATE_NONEXISTENT = -1;
// 进程是一个持久的系统进程,一般指当前 UI 进程
public static final int PROCESS_STATE_PERSISTENT = 0;
// 进程是一个持久的系统进程,正在做和 UI 相关的操作,但不直接显示
public static final int PROCESS_STATE_PERSISTENT_UI = 1;
// 进程正在托管当前的顶级活动。请注意,这涵盖了用户可见的所有活动。
public static final int PROCESS_STATE_TOP = 2;
// 进程由于系统绑定而托管前台服务。
public static final int PROCESS_STATE_BOUND_FOREGROUND_SERVICE = 3;
// 进程正在托管前台服务。
public static final int PROCESS_STATE_FOREGROUND_SERVICE = 4;
// 与{@link #PROCESS_STATE_TOP}相同,但设备处于睡眠状态。
public static final int PROCESS_STATE_TOP_SLEEPING = 5;
// 进程对用户很重要,是他们知道的东西
public static final int PROCESS_STATE_IMPORTANT_FOREGROUND = 6;
// 进程对用户很重要,但不是他们知道的
public static final int PROCESS_STATE_IMPORTANT_BACKGROUND = 7;
// 进程在后台运行备份/恢复操作
public static final int PROCESS_STATE_BACKUP = 8;
// 进程在后台,但我们不能恢复它的状态,所以我们想尽量避免杀死它,不然这个而进程就丢了
public static final int PROCESS_STATE_HEAVY_WEIGHT = 9;
// 进程在后台运行一个服务,与oom_adj不同,此级别用于正常运行在后台状态和执行操作状态。
public static final int PROCESS_STATE_SERVICE = 10;
// 进程在后台运行一个接收器,注意,从oom_adj接收器的角度来看,在较高的前台级运行,但是对于我们的优先级,这不是必需的,并且将它们置于服务之下意味着当它们接收广播时,一些进程状态中的更少的改变。
public static final int PROCESS_STATE_RECEIVER = 11;
// 进程在后台,但主持家庭活动
public static final int PROCESS_STATE_HOME = 12;
// 进程在后台,但托管最后显示的活动
public static final int PROCESS_STATE_LAST_ACTIVITY = 13;
// 进程正在缓存以供以后使用,并包含活动
public static final int PROCESS_STATE_CACHED_ACTIVITY = 14;
// 进程正在缓存供以后使用,并且是包含活动的另一个缓存进程的客户端
public static final int PROCESS_STATE_CACHED_ACTIVITY_CLIENT = 15;
// 进程正在缓存以供以后使用,并且为空
public static final int PROCESS_STATE_CACHED_EMPTY = 16;
Process State(即老版本的OOM_ADJ)与Process Importance对应关系,这个方法也是在ActivityManager.java类中,有了这个关系,就知道可以知道我们的应用处于哪个级别,对于我们后边优化有个很好地参考
/** 
* Path:SDK/sources/android-25/android/app/ActivityManager#RunningAppProcessInfo.java
*
* 通过这个方法,将Linux底层的 OOM_ADJ级别码和 android 层面的进程重要程度联系了起来
*/
public static int procStateToImportance(int procState) {
if (procState == PROCESS_STATE_NONEXISTENT) {
return IMPORTANCE_GONE;
} else if (procState >= PROCESS_STATE_HOME) {
return IMPORTANCE_BACKGROUND;
} else if (procState >= PROCESS_STATE_SERVICE) {
return IMPORTANCE_SERVICE;
} else if (procState > PROCESS_STATE_HEAVY_WEIGHT) {
return IMPORTANCE_CANT_SAVE_STATE;
} else if (procState >= PROCESS_STATE_IMPORTANT_BACKGROUND) {
return IMPORTANCE_PERCEPTIBLE;
} else if (procState >= PROCESS_STATE_IMPORTANT_FOREGROUND) {
return IMPORTANCE_VISIBLE;
} else if (procState >= PROCESS_STATE_TOP_SLEEPING) {
return IMPORTANCE_TOP_SLEEPING;
} else if (procState >= PROCESS_STATE_FOREGROUND_SERVICE) {
return IMPORTANCE_FOREGROUND_SERVICE;
} else {
return IMPORTANCE_FOREGROUND;
}
}
一般情况下,设备端进程被干掉有一下几种情况
QQ截图20170615143439.jpg

由以上分析,我们可以可以总结出,如果想提高我们应用后台运行时间,就需要提高当前应用进程优先级,来减少被杀死的概率

守护进程的实现

分析了那么多,现在对Android自身后台进程管理,以及进程的回收也有了一个大致的了解,后边我们要做的就是想尽一切办法去提高应用进程优先级,降低进程被杀的概率;或者是在被杀死后能够重新启动后台守护进程

1.模拟前台进程

第一种方式就是利用系统漏洞,使用startForeground()将当前进程伪装成前台进程,将进程优先级提高到最高(这里所说的最高是服务所能达到的最高,即1);

这种方式在7.x之前都是很好用的,QQ、微信、IReader、Keep 等好多应用都是用的这种方式实现;因为在7.x 以后的设备上,这种伪装前台进程的方式也会显示出来通知栏提醒,这个是取消不掉的,虽然Google现在还没有对这种方式加以限制,不过这个已经能够被用户感知到了,这种方式估计也用不了多久了

下边看下实现方式,这边这个VMDaemonService就是一个守护进程服务,其中在服务的onStartCommand()方法中调用startForeground()将服务进程设置为前台进程,当运行在 API18 以下的设备是可以直接设置,API18 以上需要实现一个内部的Service,这个内部类实现和外部类同样的操作,然后结束自己;当这个服务启动后就会创建一个定时器去发送广播,当我们的核心服务被干掉后,就由另外的广播接收器去接收我们守护进程发出的广播,然后唤醒我们的核心服务;
/**
* 以实现内部 Service 类的方式实现守护进程,这里是利用 android 漏洞提高当前进程优先级
*
* Created by lzan13 on 2017/3/7.
*/
public class VMDaemonService extends Service {

private final static String TAG = VMDaemonService.class.getSimpleName();

// 定时唤醒的时间间隔,这里为了自己测试方边设置了一分钟
private final static int ALARM_INTERVAL = 1 * 60 * 1000;
// 发送唤醒广播请求码
private final static int WAKE_REQUEST_CODE = 5121;
// 守护进程 Service ID
private final static int DAEMON_SERVICE_ID = -5121;

@Override public void onCreate() {
Log.i(TAG, "VMDaemonService->onCreate");
super.onCreate();
}

@Override public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "VMDaemonService->onStartCommand");
// 利用 Android 漏洞提高进程优先级,
startForeground(DAEMON_SERVICE_ID, new Notification());
// 当 SDk 版本大于18时,需要通过内部 Service 类启动同样 id 的 Service
if (Build.VERSION.SDK_INT >= 18) {
Intent innerIntent = new Intent(this, DaemonInnerService.class);
startService(innerIntent);
}

// 发送唤醒广播来促使挂掉的UI进程重新启动起来
AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
Intent alarmIntent = new Intent();
alarmIntent.setAction(VMWakeReceiver.DAEMON_WAKE_ACTION);

PendingIntent operation = PendingIntent.getBroadcast(this, WAKE_REQUEST_CODE, alarmIntent,
PendingIntent.FLAG_UPDATE_CURRENT);

alarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(),
ALARM_INTERVAL, operation);

/**
* 这里返回值是使用系统 Service 的机制自动重新启动,不过这种方式以下两种方式不适用:
* 1.Service 第一次被异常杀死后会在5秒内重启,第二次被杀死会在10秒内重启,第三次会在20秒内重启,一旦在短时间内 Service 被杀死达到5次,则系统不再拉起。
* 2.进程被取得 Root 权限的管理工具或系统工具通过 forestop 停止掉,无法重启。
*/
return START_STICKY;
}

@Override public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("onBind 未实现");
}

@Override public void onDestroy() {
Log.i(TAG, "VMDaemonService->onDestroy");
super.onDestroy();
}

/**
* 实现一个内部的 Service,实现让后台服务的优先级提高到前台服务,这里利用了 android 系统的漏洞,
* 不保证所有系统可用,测试在7.1.1 之前大部分系统都是可以的,不排除个别厂商优化限制
*/
public static class DaemonInnerService extends Service {

@Override public void onCreate() {
Log.i(TAG, "DaemonInnerService -> onCreate");
super.onCreate();
}

@Override public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "DaemonInnerService -> onStartCommand");
startForeground(DAEMON_SERVICE_ID, new Notification());
stopSelf();
return super.onStartCommand(intent, flags, startId);
}

@Override public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("onBind 未实现");
}

@Override public void onDestroy() {
Log.i(TAG, "DaemonInnerService -> onDestroy");
super.onDestroy();
}
}
}
当我们启动这个守护进程的时候,就可以使用以下adb命令查看当前程序的进程情况(需要adb shell进去设备),
为了等下区分进程优先级,我启动了一个普通的后台进程,两外两个一个是我们启动的守护进程,一个是当前程序的核心进程,可以看到除了后台进程外,另外两个进程都带有isForeground=true的属性:
# 这个命令的 services 可以换成 service,这样会只显示当前,进程,不显示详细内容
# dumpsys activity services <Your Package Name>
root@vbox86p:/ # dumpsys activity services com.vmloft.develop.daemon
ACTIVITY MANAGER SERVICES (dumpsys activity services)
User 0 active services:
* ServiceRecord{170fe1dd u0 com.vmloft.develop.daemon/.services.VMDaemonService}
intent={cmp=com.vmloft.develop.daemon/.services.VMDaemonService}
packageName=com.vmloft.develop.daemon
processName=com.vmloft.develop.daemon:daemon
baseDir=/data/app/com.vmloft.develop.daemon-1/base.apk
dataDir=/data/data/com.vmloft.develop.daemon
app=ProcessRecord{173fe77f 2370:com.vmloft.develop.daemon:daemon/u0a68}
isForeground=true foregroundId=-5121 foregroundNoti=Notification(pri=0 contentView=com.vmloft.develop.daemon/0x1090077 vibrate=null sound=null defaults=0x0 flags=0x62 color=0xff607d8b vis=PRIVATE)
createTime=-6s196ms startingBgTimeout=--
lastActivity=-6s157ms restartTime=-6s157ms createdFromFg=true
startRequested=true delayedStop=false stopIfKilled=false callStart=true lastStartId=1

* ServiceRecord{2fee4f84 u0 com.vmloft.develop.daemon/.services.VMCoreService}
intent={cmp=com.vmloft.develop.daemon/.services.VMCoreService}
packageName=com.vmloft.develop.daemon
processName=com.vmloft.develop.daemon
baseDir=/data/app/com.vmloft.develop.daemon-1/base.apk
dataDir=/data/data/com.vmloft.develop.daemon
app=ProcessRecord{18c6a1b4 2343:com.vmloft.develop.daemon/u0a68}
isForeground=true foregroundId=-5120 foregroundNoti=Notification(pri=0 contentView=com.vmloft.develop.daemon/0x1090077 vibrate=null sound=null defaults=0x0 flags=0x62 color=0xff607d8b vis=PRIVATE)
createTime=-28s136ms startingBgTimeout=--
lastActivity=-28s136ms restartTime=-28s136ms createdFromFg=true
startRequested=true delayedStop=false stopIfKilled=false callStart=true lastStartId=1

* ServiceRecord{2ef6909e u0 com.vmloft.develop.daemon/.services.VMBackgroundService}
intent={cmp=com.vmloft.develop.daemon/.services.VMBackgroundService}
packageName=com.vmloft.develop.daemon
processName=com.vmloft.develop.daemon:background
baseDir=/data/app/com.vmloft.develop.daemon-1/base.apk
dataDir=/data/data/com.vmloft.develop.daemon
app=ProcessRecord{29f8734c 2388:com.vmloft.develop.daemon:background/u0a68}
createTime=-3s279ms startingBgTimeout=--
lastActivity=-3s262ms restartTime=-3s262ms createdFromFg=true
startRequested=true delayedStop=false stopIfKilled=false callStart=true lastStartId=1
然后我们可以用下边的命令查看ProcessID
# 这个命令可以查看当前DProcessID(数据结果第二列),我们可以看到当前程序有两个进程
# ps | grep com.vmloft.develop.daemon
root@vbox86p:/ # ps | grep com.vmloft.develop.daemon
u0_a68 2343 274 1012408 42188 ffffffff f74f1b45 S com.vmloft.develop.daemon
u0_a68 2370 274 997012 26152 ffffffff f74f1b45 S com.vmloft.develop.daemon:daemon
u0_a68 2388 274 997012 25668 ffffffff f74f1b45 S com.vmloft.develop.daemon:background
有了ProcessID之后,我们可以根据这个ProcessID获取到当前进程的优先级状态Process State,对应Linux层的oom_adj
可以看到当前核心进程的级别为0,因为这个表示当前程序运行在前台 UI 界面,守护进程级别为1,因为我们利用漏洞设置成了前台进程,虽然不可见,但是他的级别也是比较高的,仅次于前台 UI 进程,然后普通后台进程级别为4;当我们退到后台时,可以看到核心进程的级别变为1了,这就是因为我们利用startForeground()将进程设置成前台进程的原因,这样就降低了进程被系统回收的概率了;
# 这个命令就是通过 ProcessID 输出其对应 oom_adj
# cat /proc/ProcessID/oom_adj
# 程序在前台时,查询进程级别
root@vbox86p:/ # cat /proc/2343/oom_adj
0
root@vbox86p:/ # cat /proc/2370/oom_adj
1
root@vbox86p:/ # cat /proc/2388/oom_adj
4
# 当程序退到后台时,再次查看进程级别
root@vbox86p:/ # cat /proc/2343/oom_adj
1
root@vbox86p:/ # cat /proc/2370/oom_adj
1
root@vbox86p:/ # cat /proc/2388/oom_adj
4
可以看到这种方式确实能够提高进程优先级,但是在一些国产的设备上还是会被杀死的,比我我测试的时候小米点击清空最近运行的应用进程就别干掉了;当把应用加入到设备白名单里就不会被杀死了,微信就是这样,人家直接装上之后就已经在白名单里了,我们要做的就是在用户使用中引导他们将我们的程序设置进白名单,将守护进程和白名单结合起来,这样才能保证我们的应用持续或者

2.JobScheduler机制唤醒

Android系统在5.x以上版本提供了一个JobSchedule接口,系统会根据自己实现定时去调用改接口传递的进程去实现一些操作,而且这个接口在被强制停止后依然能够正常的启动;不过在一些国产设备上可能无效,比如小米;
下边是 JobServcie 的实现:
/**
* 5.x 以上使用 JobService 实现守护进程,这个守护进程要做的工作很简单,就是启动应用的核心进程
* Created by lzan13 on 2017/3/8.
*/
@TargetApi(Build.VERSION_CODES.LOLLIPOP) public class VMDaemonJobService extends JobService {

private final static String TAG = VMDaemonJobService.class.getSimpleName();

@Override public boolean onStartJob(JobParameters params) {
Log.d(TAG, "onStartJob");
// 这里为了掩饰直接启动核心进程,没有做其他判断操作
startService(new Intent(getApplicationContext(), VMCoreService.class));
return false;
}

@Override public boolean onStopJob(JobParameters params) {
Log.d(TAG, "onStopJob");
return false;
}
}
我们要做的就是在需要的时候调用JobSchedule的schedule来启动任务;剩下的就不需要关心了,JobSchedule会帮我们做好,下边就是我这边实现的启动任务的方法:
/**
* 5.x以上系统启用 JobScheduler API 进行实现守护进程的唤醒操作
*/
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void startJobScheduler() {
int jobId = 1;
JobInfo.Builder jobInfo = new JobInfo.Builder(jobId, new ComponentName(this, VMDaemonJobService.class));
jobInfo.setPeriodic(10000);
jobInfo.setPersisted(true);
JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
jobScheduler.schedule(jobInfo.build());
}
3.系统 Service START_STICKY 机制重启

在实现Service类时,将onStartCommand()返回值设置为START_STICKY,利用系统机制在Service挂掉后自动拉活;不过这种方式只适合比较原生一些的系统,像小米,华为等这些定制化比较高的第三方厂商,他们都已经把这些给限制掉了;
@Override 
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "VMDaemonService->onStartCommand");
/**
* 这里返回值是使用系统 Service 的机制自动重新启动,不过这种方式以下两种方式不适用:
* 1.Service 第一次被异常杀死后会在5秒内重启,第二次被杀死会在10秒内重启,第三次会在20秒内重启,一旦在短时间内 Service 被杀死达到5次,则系统不再拉起。
* 2.进程被取得 Root 权限的管理工具或系统工具通过 forestop 停止掉,无法重启。
* 3.一些定制化比较高的第三方系统也不适用
*/
return START_STICKY;
}
这种方式在以下两种情况无效:
  • Service第一次被异常杀死后会在5秒内重启,第二次被杀死会在10秒内重启,第三次会在20秒内重启,一旦在短时间内Service被杀死达到5次,这个服务就不能再次重启了;
  • 进程被取得Root权限的管理工具或系统工具通过fores-top方式停止掉,无法重启;
  • 一些定制化比较高的第三方系统也不适用


4.其他保活方式


  • 利用 Native 本地进程,这个主要使用到 jni 调用底层实现,而且在 Android 5.x 以后对这个限制也比较高,不适用了,暂时不研究
  • 集成第三方SDK互相唤醒,这个只要正常集成了第三方的SDK,并使用了他们对应的服务,当一个设备安装的多个应用都集成了某一个第三方SDK时,启动任意一个 app 都会唤醒其他的 app,不过这个在一些新版的国内厂商系统也是做了限制,这种方式并没有什么效果
  • 一像素的 Activity 方式(流氓方式),经测试一些手机系统无法检测到解锁和锁屏,不确定是否系统修改了解锁或者锁屏的广播,还是禁用了这些广播,因此此方式无效;


结语

事事没有绝对,万物总有一些漏洞,就算上边的那些方式不可用了,后边肯定还会出现其他的方式;我们不能保证我们的应用不死,但我们可以提高存活率;

其实最好的方式还是把程序做好,让程序本身深入人心,别人喜欢你了,就算你被干掉了,他们也会主动的把你拉起来,然后把你加入他们的白名单,然后我们的目的就实现了不是
0
回复

android 端 app 后台被杀死收不到消息的解决方案 Android 推送 守护进程

回复

lzan13 发起了问题 • 1 人关注 • 86 次浏览 • 2017-06-15 14:34 • 来自相关话题

0
评论

获取好友列表,报错SERVER_UNKNOWN_ERROR = 303 SERVER_UNKNOWN_ERROR

HaoBoy_ 发表了文章 • 39 次浏览 • 2017-06-15 12:16 • 来自相关话题

List<String> usernames = EMClient.getInstance().contactManager().getAllContactsFromServer();

获取好友列表在第一次登录成功后获取正常,但是当我在其他界面调用这个方法的时候去报错如下:
 
com.hyphenate.exceptions.HyphenateException: Unknown server error
 
解决方法:要放到子线程中。
 
注:我想知道why,why,why!!!!!!!! 查看全部
List<String> usernames = EMClient.getInstance().contactManager().getAllContactsFromServer();

获取好友列表在第一次登录成功后获取正常,但是当我在其他界面调用这个方法的时候去报错如下:
 
com.hyphenate.exceptions.HyphenateException: Unknown server error
 
解决方法:要放到子线程中。
 
注:我想知道why,why,why!!!!!!!!
0
回复
0
回复

小程序收到android发过来的语音,但是有时候在网络不好的时候播放不了 环信_Android 环信_WebIM

回复

xianghenggang 发起了问题 • 1 人关注 • 48 次浏览 • 2017-06-14 09:37 • 来自相关话题

0
评论

环信移动客服v5.20已发布,支持自助开通工单功能以及客服直接处理工单 产品更新 环信移动客服

产品更新 发表了文章 • 71 次浏览 • 2017-06-13 17:19 • 来自相关话题

客服模式

支持自助开通工单功能以及客服直接处理工单

新增工单页面,支持自助开通工单功能,以及客服直接处理工单,包括新建工单、回复工单、分配工单、修改帮助主题、修改工单状态,筛选工单,等等。

工单功能为增值服务,如需开通,请在工单页面提交申请,环信商务经理会主动联系您。

申请开通工单功能

工单功能可以帮助您实现高效的跨部门协作,只需提交申请,并绑定邮箱,即可启动您的工单功能服务。

步骤如下:
在工单页面,点击“申请工单功能”;填写姓名、电话、企业名称、邮箱,点击“下一步”;填写帮助主题(帮助主题为工单的类别),点击“下一步”;填写系统邮箱(用于接收和发送工单相关的邮件),点击“提交”。

提交申请后,请耐心等待,环信商务经理会尽快与您联系。 





新建工单

客服与客户聊天过程中,可以为客户创建工单。

步骤如下:
在会话页面,点击输入框上方的工单按钮;填写工单标题,选择优先级、帮助主题、分配技能组、分配坐席,填写工单内容,勾选“附带访客信息”,并保存。

勾选“附带访客信息”时,工单包含对应的客户信息,可以在工单详情的“客户资料”页签查看。 






处理工单

客服可以对工单进行回复,分配工单给技能组或坐席,修改帮助主题、优先级、工单状态,查看工单进度,查看客户资料。

在工单页面,点击任意工单,即可查看工单详情。并执行下述工单处理操作:

回复工单:回复工单时,如果勾选“发布为公开回复”,工单系统将回复内容通知客户;不勾选时,回复仅客服可以查看。
工单处理:分配技能组、分配坐席、修改帮助主题、优先级、状态。工单进度:查看工单进度。客户资料:查看客户资料。







筛选工单

在工单页面已为您创建一些默认工单筛选器,帮助您对工单进行分类管理。您还可以创建自定义的筛选器,以满足更具体的需求。

创建自定义筛选器步骤如下:

在工单页面,点击“自定义筛选”;
填写筛选器名称,选择筛选条件,如工单创建时间段、工单创建人、客户名称、工单编号、工单标题、分配技能组、分配坐席、帮助主题、工单状态,并点击“确定”。

创建成功后,可以根据自定义筛选器对工单进行筛选。您还可以编辑或删除自定义筛选器。 






工单通知

当工单创建成功、分配技能组或客服、得到回复、状态变更时,创建工单的客服将会收到系统消息。 






会话面板聊天窗口优化

会话面板聊天窗口优化,支持拖动扩展输入框,以显示更多正在输入的内容。 






留言支持根据更新时间进行筛选

留言页面显示留言的更新时间,并支持根据更新时间进行筛选和排序。更新时间指,留言分配的客服变更或留言状态变更的时间。
筛选:点击“自定义留言筛选”,选择“更新时间”范围,点击“筛选查询”,对留言进行筛选。排序:点击留言列表中“更新时间”右侧的箭头,使留言按照更新时间升序或降序排列。







客户中心显示客户的真实姓名

客户中心新增“名字”一列,显示客户的真实姓名。 






管理员模式

新增REST API渠道

环信移动客服新增REST API渠道。开通REST API渠道并配置服务器信息后,客服向客户回复的消息,将被环信转发到服务器的回调地址中。该功能可用于环信移动客服与第三方服务器之间的消息传递。

REST API渠道支持创建多个REST关联,每个REST关联均可作为环信与您的服务器之间收发消息的通道。

创建REST关联:
进入“管理员模式 > 渠道管理 > REST API”页面;点击“添加REST关联”按钮,填写关联名称、回调地址,并保存。

系统自动为您生成Client ID、Client Secret、POST API。Client ID和Client Secret用于向环信发送消息时的身份认证;消息API为您向环信发送消息时使用的REST API接口,方法为POST。 







关于REST API渠道的身份认证方式、消息格式,请参考:REST API渠道集成

注:REST API渠道为增值服务,如需开通,请提供租户ID并联系环信商务经理。

新增APP关联演示视频

APP关联信息页新增APP关联的演示视频,视频说明了APP关联的Client ID和IM服务号与即时通讯云的应用之间的关系,以及如何添加APP关联。

如果已有即时通讯云的应用,可以采取“关联IM账号”的方式创建APP关联。对应关系如下:

Client ID: APP关联的Client ID与即时通讯云的应用的Client ID一致;
IM服务号:APP关联的IM服务号对应即时通讯云的应用的一个IM用户。

进入“管理员模式 > 渠道管理 > 手机APP”页面,点击APP关联信息页中Client ID和IM服务号右侧的问号,可以查看相应的演示视频。

客户标签支持导入导出








客户标签支持下载模版、导入、导出,方便管理员对客户标签进行批量整理。

在导入客户标签时,会自动过滤已存在的客户标签,只导入新增的客户标签。 
attach]7544[/attach]
“不活跃会话超时自动结束”开关优化

优化“不活跃会话超时自动结束”开关,该开关不再对待接入会话生效。优化后,该开关打开时,对于客服的进行中会话,如果客户和客服在设定时间内均未回复消息,系统将自动发送提示语给客户,并结束会话。

如果需要自动结束超时的进行中会话,进入“管理员模式 > 设置 > 系统开关”页面,打开“不活跃会话超时自动结束”开关。
如果需要自动结束超时的待接入会话,进入“管理员模式 > 设置 > 系统开关”页面,打开“待接入超时结束会话”开关。

客服列表优化

优化“管理员模式 > 成员管理 > 客服”页面的客服列表,删除原导出日志按钮。所有客服的登录日志,均可以在“管理员模式 > 统计查询 > 客服时长统计”页面查看并导出。

Android客服工作台

当前版本:V3.0

新功能:

管理员模式,当前会话支持筛选、转接、关闭
留言支持批量分配

iOS客服工作台

当前版本:V2.1.9

新功能:

新增管理员模式,包含管理员首页的数据展示
支持查看通知详情

移动客服Android SDK

当前版本:V1.0.7

新功能:

新增发送和接收短视频功能

移动客服iOS SDK

当前版本:V1.1.0

新功能:

SDK全面升级为动态库,集成更简单,功能更全面
离线推送支持推送详情
优化升级HelpDeskUI

环信移动客服更新日志http://docs.easemob.com/cs/releasenote/5.20 

环信移动客服登陆地址http://kefu.easemob.com/ 查看全部
客服模式

支持自助开通工单功能以及客服直接处理工单

新增工单页面,支持自助开通工单功能,以及客服直接处理工单,包括新建工单、回复工单、分配工单、修改帮助主题、修改工单状态,筛选工单,等等。

工单功能为增值服务,如需开通,请在工单页面提交申请,环信商务经理会主动联系您。

申请开通工单功能

工单功能可以帮助您实现高效的跨部门协作,只需提交申请,并绑定邮箱,即可启动您的工单功能服务。

步骤如下:
  1. 在工单页面,点击“申请工单功能”;
  2. 填写姓名、电话、企业名称、邮箱,点击“下一步”;
  3. 填写帮助主题(帮助主题为工单的类别),点击“下一步”;
  4. 填写系统邮箱(用于接收和发送工单相关的邮件),点击“提交”。


提交申请后,请耐心等待,环信商务经理会尽快与您联系。 

01.png

新建工单

客服与客户聊天过程中,可以为客户创建工单。

步骤如下:
  1. 在会话页面,点击输入框上方的工单按钮;
  2. 填写工单标题,选择优先级、帮助主题、分配技能组、分配坐席,填写工单内容,勾选“附带访客信息”,并保存。


勾选“附带访客信息”时,工单包含对应的客户信息,可以在工单详情的“客户资料”页签查看。 

02.png


处理工单

客服可以对工单进行回复,分配工单给技能组或坐席,修改帮助主题、优先级、工单状态,查看工单进度,查看客户资料。

在工单页面,点击任意工单,即可查看工单详情。并执行下述工单处理操作:

回复工单:回复工单时,如果勾选“发布为公开回复”,工单系统将回复内容通知客户;不勾选时,回复仅客服可以查看。
  • 工单处理:分配技能组、分配坐席、修改帮助主题、优先级、状态。
  • 工单进度:查看工单进度。
  • 客户资料:查看客户资料。



03.png


筛选工单

在工单页面已为您创建一些默认工单筛选器,帮助您对工单进行分类管理。您还可以创建自定义的筛选器,以满足更具体的需求。

创建自定义筛选器步骤如下:

在工单页面,点击“自定义筛选”;
填写筛选器名称,选择筛选条件,如工单创建时间段、工单创建人、客户名称、工单编号、工单标题、分配技能组、分配坐席、帮助主题、工单状态,并点击“确定”。

创建成功后,可以根据自定义筛选器对工单进行筛选。您还可以编辑或删除自定义筛选器。 

04.png


工单通知

当工单创建成功、分配技能组或客服、得到回复、状态变更时,创建工单的客服将会收到系统消息。 

05.png


会话面板聊天窗口优化

会话面板聊天窗口优化,支持拖动扩展输入框,以显示更多正在输入的内容。 

06.png


留言支持根据更新时间进行筛选

留言页面显示留言的更新时间,并支持根据更新时间进行筛选和排序。更新时间指,留言分配的客服变更或留言状态变更的时间。
  • 筛选:点击“自定义留言筛选”,选择“更新时间”范围,点击“筛选查询”,对留言进行筛选。
  • 排序:点击留言列表中“更新时间”右侧的箭头,使留言按照更新时间升序或降序排列。



07.png


客户中心显示客户的真实姓名

客户中心新增“名字”一列,显示客户的真实姓名。 

08.png


管理员模式

新增REST API渠道

环信移动客服新增REST API渠道。开通REST API渠道并配置服务器信息后,客服向客户回复的消息,将被环信转发到服务器的回调地址中。该功能可用于环信移动客服与第三方服务器之间的消息传递。

REST API渠道支持创建多个REST关联,每个REST关联均可作为环信与您的服务器之间收发消息的通道。

创建REST关联:
  1. 进入“管理员模式 > 渠道管理 > REST API”页面;
  2. 点击“添加REST关联”按钮,填写关联名称、回调地址,并保存。


系统自动为您生成Client ID、Client Secret、POST API。Client ID和Client Secret用于向环信发送消息时的身份认证;消息API为您向环信发送消息时使用的REST API接口,方法为POST。 

09.png



关于REST API渠道的身份认证方式、消息格式,请参考:REST API渠道集成

注:REST API渠道为增值服务,如需开通,请提供租户ID并联系环信商务经理。

新增APP关联演示视频

APP关联信息页新增APP关联的演示视频,视频说明了APP关联的Client ID和IM服务号与即时通讯云的应用之间的关系,以及如何添加APP关联。

如果已有即时通讯云的应用,可以采取“关联IM账号”的方式创建APP关联。对应关系如下:

Client ID: APP关联的Client ID与即时通讯云的应用的Client ID一致;
IM服务号:APP关联的IM服务号对应即时通讯云的应用的一个IM用户。

进入“管理员模式 > 渠道管理 > 手机APP”页面,点击APP关联信息页中Client ID和IM服务号右侧的问号,可以查看相应的演示视频。

客户标签支持导入导出

10.png




客户标签支持下载模版、导入、导出,方便管理员对客户标签进行批量整理。

在导入客户标签时,会自动过滤已存在的客户标签,只导入新增的客户标签。 
attach]7544[/attach]
“不活跃会话超时自动结束”开关优化

优化“不活跃会话超时自动结束”开关,该开关不再对待接入会话生效。优化后,该开关打开时,对于客服的进行中会话,如果客户和客服在设定时间内均未回复消息,系统将自动发送提示语给客户,并结束会话。

如果需要自动结束超时的进行中会话,进入“管理员模式 > 设置 > 系统开关”页面,打开“不活跃会话超时自动结束”开关。
如果需要自动结束超时的待接入会话,进入“管理员模式 > 设置 > 系统开关”页面,打开“待接入超时结束会话”开关。

客服列表优化

优化“管理员模式 > 成员管理 > 客服”页面的客服列表,删除原导出日志按钮。所有客服的登录日志,均可以在“管理员模式 > 统计查询 > 客服时长统计”页面查看并导出。

Android客服工作台

当前版本:V3.0

新功能:

管理员模式,当前会话支持筛选、转接、关闭
留言支持批量分配

iOS客服工作台

当前版本:V2.1.9

新功能:

新增管理员模式,包含管理员首页的数据展示
支持查看通知详情

移动客服Android SDK

当前版本:V1.0.7

新功能:

新增发送和接收短视频功能

移动客服iOS SDK

当前版本:V1.1.0

新功能:

SDK全面升级为动态库,集成更简单,功能更全面
离线推送支持推送详情
优化升级HelpDeskUI

环信移动客服更新日志http://docs.easemob.com/cs/releasenote/5.20 

环信移动客服登陆地址http://kefu.easemob.com/