微信回调模式下,测试粉丝发消息客服已经收到,客服回复消息没有收到回调,怎么回事?
【当一个技术男干上了市场和运营的工作】环信市场及运营VP 程开源在GrwoingIO数据增长沙龙演讲精选
在刚刚过去的上周,全国开启了速冻模式,迎来了入冬以来最冷一周,相信各位小伙伴们都感受到了本次寒潮满满的恶意!不知道你是宅在屋里和寒冷的冬季做着顽强的抵抗,还是奔波辗转在各个城市楼宇间为工作而努力着。
环信与你同在,在上周六零下17度的极端天气里,环信市场及运营VP 程开源在GrwoingIO数据增长沙龙
做了《一个SAAS企业的用户增长实践》 的分享,程开源先生以诙谐的演讲方式,丰富强硬的阅历知识给带大家带来了一场别开生面的演讲,现场业内大咖深入交流,气氛十分热烈。
现场图片
现场纪实
主持人:相信大家都听过环信,他们客户群有很多千万级的,都在使用环信的产品,我们今天的问答环节也是由环信提供的奖品,限量版的极客体恤衫,大家在提问或者朋友圈分享,都有机会参与这个活动。环信也是我们的客户,今天非常有幸请到环信市场及运营VP程开源来给大家分享。
程开源:大家好!我叫程开源,做过技术的同学都知道,这是一个技术词汇。我一不小心,我在环信负责市场和运营,一个技术宅,负责市场和运营。我在环信很快找到了自己的定位,找到了在环信的发展路径。今天的内容,如果在座有技术的同事,可以做一些参考,也可以干一点市场运营的事。我做过很多年的技术,同时我也做过四五年的销售,传统企业的销售,是卖软化的。我也做过大概三到四年的创业,我经历比较综合,我在环信的时候,虽然是负责一部分,但是我更愿意把它当成我自己的事业。我看全局,看整个环境,我们在运营过程当中的一些问题,怎么样使得我们的市场、产品、销售、研发还有我们的体系可以全部很好,是不是有一个公式,如果有五个变量的话,最后推导出来一个结果,这五个变量我们是否可以有一个图,有一个实时变化的图把它展示出来。我们通过不同部门的同事,不断去调校各项指标、各个变量,使得产生最好的业务成效。
这是我在环信一直在思考的问题,环信实际上是通过咖啡起家的一家公司,最近环信已经到B轮,从环信一条产品线发展到两条产品线,最初的一条产品线是即时通讯云,在即时通讯功能基础上,我们研发了在线客服云,及时聊天往商业迈进了一步,实现我们商业软件的变现过程。其实环信即时通讯云是个PaaS云,在线客服云是SaaS云。
在座都是一些2C的企业,很多数据分析师,我也认识不少,很多都是2C的公司,2C已经进入深水区了,在2B领域有越来越多的创业公司出现。有多少是做2B公司的同学?有不少,我相信随着越来越多的2B公司的出现,这些公司的特征一定是on line2B,是SaaS,意味着我们用户全生命周期的行为都是在线运行的,意味着用户的行为是可以被记录的,可以度量的,并且可以分析的。通过一定的干预,各个部门共同参与,这些指标是可以直接反馈出来的。于是我就想到,我们找到我们的规律,我们找到了我们的公式,找到一个方法,对于像我这种背景的,有技术背景,一个很重要的特征,我们必须要在公司找到目标感。我现在带市场和运营团队,我发现有些同事整天做,挺忙的,你不能说我什么吧,我也产生了很多工作内容。但是其实他的目标是什么?你做这些事情,目标难道是你很忙吗?我们要的是结果,这个结果我必须要可以度量。度量是什么?就是要用数据说话。如果没有数据,基本是不能存活的,这个公司。我们总想是不是能够把我们公司当中一些规律整理一下,变成程序化的东西。所有的目标是为了我们的增长增长再增长,从我们的市场指标、运营指标、产品指标各个方面。
我初步做了一个分析,我自己创业的时候是做2C的业务,我做销售的时候做的是传统2B的。我现在在环信是on line2B部分,我们在做传统2B,几乎是百万级的,单用户价值是小一万的,数据不精确,只是一个概数。我们更多时候关注用户的群体,比如我们关注女性用户,女性用户喜欢购物的,喜欢买化妆品的,买化妆品的品类当中又喜欢经常买衣服的,我们在做2C的时候,做用户群体分析就够了,这是我们很长时间,用百度的统计,用GA等等可能就够了,做群体分析,做统计分析往往就够了。
我们看传统2B的生意,用户是可以看到的,用户群体是小于一万的,单用户价值是大于十万的,往往公司关注的是单用户行为,我们是销售驱动。我们关注用户领导喜欢什么,几点上班,几点下班,是不是要堵他家门口,跟他搞好关系,他老婆喜欢什么包包,我是不是应该送点东西给他?后来我之所以到环信很重要的原因,我觉得这种事情已经非常不可扩展性,非常依赖销售个体的能力。这也是销售未来的一个出路,我们怎么样去用批量化的销售,销售更多的东西。我们选择on line2B,on line2B的特征,我们有用户群体在一万到一百万之间,单用户的价值是从一万到十万,我们关注和干预单用户行为的LI变得划算,以前2C的时候,关注单用户的行为是不划算的,本来就没赚多少钱,还要再关注和干预单用户行为,要花很多钱,是不划算的。我们on line2B,单用户的价值是一万到十万,是超出原来传统2C的,我们可以在每个用户花更多的成本做用户行为分析。最后我们会同时关注用户群体行为和单用户的行为,于是变成了运营+销售双轮驱动的模式。
我现在在做一些规划,从公司整个来讲,在思考这么几个问题。我们会思考什么样的推广手段最为高效?什么样的网站注册转化率最高?什么样的产品用户留存率最高?哪些用户最容易买单?哪些用户最容易流失?分别反映的就是拉新、转化、留存、变现和一些风险控制。在我们环信内部把用户分为好几类,我的观点和现在很多人不一样,我们多出了几类。第一类是潜在用户,第二类是到访用户,第三类是到访后又流失的用户,第四类是回访,但未注册的用户,第五类注册用户,付费用户。
下面这几排,我用不同的颜色标出来,其实是反映了公司不同的部门,比如绿色的是市场部门干的事,蓝色部分是我们运营部门的事,这个部分是产品的事,这块是销售的事。我们第一个关注的指标,最佳媒体投放的监测,我们要选出最佳的媒体投放。比如我们的手段,我们用各种不同的方式去测试,我们最佳的媒体投放策略是什么?最佳的投入产出比是什么?每个用户的到访成本是多少?非常不一样。我们最近在做我们今年的预算,我们去年做了很好的记录。今年我们市场部投放两千万,我们就有一个依据了,而不是领导拍脑袋,应该投多少,我们有很好的数据来做分析。
第二个就是在环信我们是把官网和产品页分开的。第二个指标我们可以做用户的行为监测,用户在官网的行为监测,用户在注册之前,会做大量的调研,看产品的功能特性,有哪些最佳案例,得过什么荣誉,媒体报道,看看你网站到底怎么样,这家公司怎么样,再来决定。官网本身是一个独立的产品,我们要去监测它的整个行为,落实到我们干预的措施上面,落地页,我下面列的每一项东西都可以展开讲,不讲那么多,只讲讲我们落地页的一些设计,社区建设、内容建设、AB测试,在官网建设当中就有大量的测试产生了。中间这一栏,这里面单独列出来对于到访又流失的用户,环信的数据是每天有几千个UP,几千个用户在环信网站上访问,每天有几百个用户注册,百分之七八十的用户我再也找不到他了,这是一个非常大的资源浪费,好不容易用户来了,结果大部分用户又走了。我们把这块独立做出来,到访后又流失的用户,我们这里做另外一个DSP,我们还有一些召回性的DSP。刚才覃超讲召回方式,通行的召回方式,邮件、通知,但是对于那种没有注册的用户,这两种方式都是不奏效的。我们通过数据的监测,用不同的手段召回来了又走了的用户。虽然用户没有注册,但是用户来了,就能记录下它的QQ号、手机号,涉及到用户隐私,我们会非常慎重。
第三个指标,注册转化率监测还有很多指标,包括产品满意度的监测,到注册用户这一栏,我们有更多的一些指标。在之前对潜在用户、新到访用户,到访后又流失的用户,注册用户,在市场和运营范畴里面,基本上传统的百度、GA差不多也是用的,但是对于单个用户的行为,比如注册用户、付费用户的行为是无法监测的,做起来很麻烦。我们每天把环信的用户ID传过去,做分析,是单用户的分析,每个用户的行为是带着他的ID号的,用户行为最终从哪里来,干了什么,在官网上干了什么,在我们的产品上做了什么,哪天来的,什么时候走的,访问了什么页面,都可以记录下来。到注册用户这个地方已经是带身份的,我们可以看到每个用户的产品使用满意度。通过用户的行为分析,我们可以看到每个用户最佳的销售时机,也就是用户的购买时机,也可以看到最易流失的用户时机,这时候我们需要不同部门的介入了。比如销售是不是要打电话问一下用户,是不是有什么不满意的地方,是因为产品不好呢还是技术支持做的不好还是误会造成的?通过对每个指标的干预,就可以改变这些指标。
对于付费用户来讲,也可以看到哪些用户最容易流失,这个图就像我最开始上来讲的,我们希望找到一种大的map,可以支撑整个运营。我们这个过程也在努力当中,我们发现摸到了一点门道,怎么通过一个大的map,从我们的市场运营、产品、销售到设计到研发,全都串起来,不再是独立的指标体系,是一个活的,随时变化的,通过一定的方式,是可以调整的一个体系。
这是我们用户在线的一个行为监测,以这个为核心串起来我们整个公司的业务。准备的比较仓促,没有更多内容带给大家,后续有机会再来分享一些我们在指标调整过程当中应该用什么样的方式和我们的一些实践是什么,未来有机会再分享给大家。谢谢!
点击下载演讲PPT ↓
收起阅读 »
Android使用实时语音功能是报错
Process: com.example.lvpeng.myapplication, PID: 26653
java.lang.UnsatisfiedLinkError: com.easemob.media.EIce
at com.easemob.chat.EMCallerJingleSession.makeCall(Unknown Source)
at com.easemob.chat.EMVoiceCallManager.syncMakeCall(Unknown Source)
at com.easemob.chat.EMVoiceCallManager.access$6(Unknown Source)
at com.easemob.chat.EMVoiceCallManager$3.run(Unknown Source) 收起阅读 »
环信移动客服网页端源码下载地址失效
环信移动客服网页端开发者版集成说明
一.集成方式
1.下载源码:https://github.com/easemob/kefu-webim/releases tag:easemob-webim-plugin-open1.0
2.解压源码包,将其放到您的服务器所指向的文件目录
3.将路径static/js/中的easemob.js 引入到</body>前即可完成集成 收起阅读 »
【开源啦】红包功能的实现 (内含源码下载)
下载APK体验
先上几张APP效果图感受下吧
接下来讲一下详细的实现思路(提供的环信发送红包的和抢红包的流程代码,到了付款和提现那块就是自己选择接入微信付款还是支付宝付款
)
1.继承EaseChatFragment并implement EaseChatFragmentListener
2.在registerExtendMenuItem中增加红包菜单,以及onExtendMenuItemClick中增加红包点击事件
3.sendRedMoney类实现发送红包,需要自己实现支付宝付款接口,在onActivityResult中发送红包的自定义扩展消息,可以以文本消息携带金额和祝福语的扩展,以及支付宝凭证的扩展,然后发送给对方,并向自己服务器发送红包数据(这部分接口由自己服务器提供并实现)
4.实现MyEaseCustomChatRowProvider接口,重点是getCustomChatRow这个方法实现红包自定义 消息对象的创建ChatRowRedMoney
5.关于红包消息界面的布局就看一下ChatRowRedMoney这个类
6.点击红包事件的实现第一种:onMessageBubbleClick这个方法中通过判断是红包消息启动领取红包的activity,同时return true;第二种:ChatRowRedMoney 中的onBubbleClick中实现红包点击处理,并去自己服务器请求对应的红包数据(这个接口由自己服务器提供并实现)
群组内抢红包的分配实现,可以通过客户端发送抢红包请求后,通过您的服务器来处理分配金额,通过回调发送到您的手机上展示您抢到的金额
点击下载源码: https://github.com/shiyiling/ShiEaseUICustomer
收起阅读 »
关于使用环信最新sdk集成小米推送的记录
这一项能够让继承了环信sdk的app在小米手机上即使离线也可以收到推送消息,这一功能大大增加了app用户的黏性,不过在集成中有很多用户会遇到一些问题,这里就把配置正确的方法贴出来,供大家参考一下
首先你要有小米的开发者账户,然后能够创建应用(开发者账户这一步自己解决)
环信官方配置推送文档地址:环信官方设置小米推送文档
小米推送服务地址:小米推送服务地址
首先声明一下:环信最新版SDK集成小米推送主要是针对小米设备,在有些小米设备上,这个推送的长连接服务是系统级别的服务,此时SDK就会启动小米的推送,当其他设备上这个长连接服务不是系统级别的,还是可以被杀死,所以和使用环信自己的长连接一样的效果,就不会启动小米推送,使用环信自身的长连接服务去接收消息,因此就算你在其他设备上配置了小米推送,一样不可用
这里创建了一个现在在写的demo,创建成功后就可以看到应用的信息,我们需要用到AppID、AppKey、AppSecret(这一点在环信文档也有说明)
得到这些后去下载小米推送的sdk,加入到自己的项目中,因为下边的配置需要小米推送的jar包
这些都得到了,然后就是要去环信的开发者后台上传证书了,这个有文档说明
接下来就是要配置app端
首先需要在自己的项目配置文件AndroidManifest.xml中添加小米推送的一些 Service 和 Receive
首先是在mainFest标签中加入权限,
(权限这里的包名一定要改成自己的、改成自己的、改成自己的)
<!--小米推送的权限-->
<permission
android:name="net.melove.demo.chat.permission.MIPUSH_RECEIVE"
android:protectionLevel="signature" />
<uses-permission android:name="net.melove.demo.chat.permission.MIPUSH_RECEIVE" />
然后再Application标签中加入Service 和Receive
<!--配置小米推送服务-->
<service
android:name="com.xiaomi.push.service.XMPushService"
android:enabled="true"
android:process=":pushservice" />
<service
android:name="com.xiaomi.mipush.sdk.PushMessageHandler"
android:enabled="true"
android:exported="true" />
<service
android:name="com.xiaomi.mipush.sdk.MessageHandleService"
android:enabled="true" />
<receiver
android:name="com.xiaomi.push.service.receivers.NetworkStatusReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<receiver
android:name="com.xiaomi.push.service.receivers.PingReceiver"
android:exported="false"
android:process=":pushservice">
<intent-filter>
<action android:name="com.xiaomi.push.PING_TIMER" />
</intent-filter>
</receiver>
<receiver
android:name="com.easemob.chat.EMMipushReceiver"
android:enabled="true">
<intent-filter>
<action android:name="com.xiaomi.mipush.RECEIVE_MESSAGE" />
</intent-filter>
<intent-filter>
<action android:name="ccom.xiaomi.mipush.MESSAGE_ARRIVED" />
</intent-filter>
<intent-filter>
<action android:name="com.xiaomi.mipush.ERROR" />
</intent-filter>
</receiver>
最后就是官方文档说的,在初始化sdk的时候加上小米推送的AppID以及AppKey的配置
String APP_ID = "小米 appid";
String APP_KEY = "小米 appkey";
EMChatManager.getInstance().setMipushConfig(APP_ID, APP_KEY);
配置玩了之后可以先用小米官方的推送工具测试下app是否能正常收到推送,测试通过开始测试接收环信的离线消息推送;
这个配置还是比较简单的,只要细心,能理解就一般不会出现问题,
有什么问题可以在环信的社区提问Imgeek社区
收起阅读 »
关注IT人的个人成长分享笔记
每个年初,每个人的手上都会有一张计划单,有关目标,有关梦想,有关爱好,这次的分享,摘客为大家带来的就是三张来自摘客的清单。
List1
人物:武学贤
身份:网络爬虫开发工程师,负责摘客的爬虫
属性:执着技术,低调腼腆,从容淡定
台词:不断保持学习的动力
计划清单:
1.掌握python
2.jvm调优
3.学习分布式负载均衡
城狮们的修炼之路
1.想清楚自己的发展方向,职业规划
从java 1995年推出,it 行业经过20年的翻天覆地的发展,已经出线,很多细分的岗位,各种技术也越来越深入,人的精力往往有限,结合自己的兴趣和知识结构,选择一个合适的发展发展方向很重要。
2.课程设计和工业化代码
计算机专业的学生,大学里都做过课程设计,基本为直接写代码,在自己的机器上跑起来就好了。很少有版本管理、设计文档、单元测试、代码样式,项目发布、持续集成,但这些内容是需要初级程序员逐步掌握的,例如maven、jenkins,都是企业级开发中需要用到的技术。
3.关注开源社区
关注自己领域优秀的开源项目,研读项目源码,开源社区的代码是经过程序员不断review的,有助于快速提高自己的代码水平。
4.学好英语
英语是程序员的母语,排名前20的开发语言都是用英语,越是越新的技术,文档资料几乎都是英语,学好英语更有利于学习一些国外的优秀资料。
5.解决问题
不要过于依赖前辈,要学会设置断点,调适自己的程序bug,通过调适发现问题,学会求助社区。
6.写技术博客
据统计有技术博客的程序员比不写技术博客的程序员薪资高10%。
List2
人物:刘培
身份:迅速成长的需求分析师和运营
属性:在运营方面独具慧眼,90后,新晋IT人
台词:不管做什么事情,拥有一个好的心态很重要
计划列表:
1.研究100个不同类型的app
2.用axure画出多交互页面
3.有自己的资料整理库
4.能更融洽的交流
需求与运营的相生相克
首先说说运营的意义:
需求的好坏决定一个产品的好坏,产品的好坏又直接决定了运营的好坏。简单的例子就是如果运营在推一个自己完全不喜欢的产品,那么负能量会不停积累,直至最后运营满腹怨气,产品不得不停止。所以如果运营在最开始就加入产品开发工作中,和需求分析师共同做好分工和调研,就是最好的状态。
一个项目的开始,运营从立项就加入。负责市场调研、用户调研、合作方联系,运营内容准备。只有这样,运营才能真实感觉到这是自己的产品,自己才是亲妈,才能在做的过程中不断的去修正产品的失误,不断的充满能量的完成这件事情。要不然,最后产品做完,木已成舟,运营要去运营并维护一个自己并不喜欢且并没有实际参与过的东西,产品发现你运营他的东西,你一点都不高兴。大家都在负能量。无非就是后妈的角色,没有人会喜欢。
接下来我们单说需求搜集及实现的全过程:
用户是需求之源,首先必须要有接地气且符合用户使用习惯的需求,才能做好一款产品。
在产品开发的前期,需要进行用户访谈来搜集需求。用户对产品提出一系列需求。需求分析师必须要将搜集来的需求整理分类删减评估,划分需求优先级并做成表格,以方便后期研讨及开发。
在产品开发过程中需求分析师主要完成与程序员沟通的工作,在这个过程中程序员按照需求来设置程序,若有沟通不畅,必定会增加开发成本。
其中若有需求更改,也必须要需求分析师把控。需求分析要做的偶事情不仅是搜集整理,还需要做用户访谈,深刻理解用户是需求之源。
一年说长不长,说短不短,想学的东西能学到,想参与的项目能参与就够了。我自己的座右铭就是:吃亏是福,牢记在心。
List3
人物:黄磊
身份:参与摘客推荐算法
属性:工作尽显风骚,生活尽显逗比
台词:早起的鸟儿有虫吃
计划清单:
1.能在JAVA上加深自己的功力
设计模式:http://www.cnblogs.com/java-my-life/
《深入理解Java虚拟机:JVM高级特性与最佳实践》
《HotSpot实战》
《Java并发编程实战》
《Java多线程编程核心技术》
《Effective Java中文版》
《深入分析Java Web技术内幕》
《大型网站技术架构核心原理与案例分析》
《大型网站系统与Java中间件实践》
《从Paxos到ZooKeeper 分布式一致性原理与实践》
《MySQL5.6从零开始学》
《Spring源码深度解析》
重点参考:http://www.cnblogs.com/xrq730/p/4994545.html?utm_source=tuicool&utm_medium=referral
2.勤锻炼,坚持每周一次的10km跑步
3.多花时间陪陪女朋友和家人,学习一些小的生活技能,提升生活品质
实习工作路上的打怪技能
1.尽早开始准备好简历
一份简历是对自己大学时代的一个交代和学习成果的总结,所以,一方面,可以思考自己想要一个什么样的简历?里面的内容是什么?未来自己想要做一份什么样的工作。另一方面,简历并不是一次写好就可以了。如何把所有的内容写在一张纸上?如何突出自己的优势?如何围绕目标岗位展现自己的核心竞争力?都是值得思考和一次次修改的内容。
2.关于如何写好一份简历
在知乎、豆瓣以及学长学姐的实际经验中可以得到各种非常好的简历。
3.尽早开始实习,特别是可以全职的实习
第一,找实习的过程中会一次一次的碰壁。可以思考为什么会不行?是简历上没有突出自己的优势?还是本身能力上还有不足,那如何加强?这一切都是为了在毕业季有好的工作机会时一把抓住。
第二,在公司做一个项目和在学校做一个项目的差异,会让自己思考这样的一份工作是不是自己真的喜欢的?是不是愿意每天花8小时在这上面。更本质的问题是,从实习经历中试错的低成本可以问自己是否真的喜欢这样的工作。
第三,相关实习经验的积累,一方面可以看到自己在这个岗位上深耕的不足,另一方面累积起来的实习经验和在公司的项目经验是未来求职时的巨大优势,特别是大公司和实际上线项目的实习。
4.如何找到一份好的实习
一份实习,是需要投入很多时间和精力的。如果没能从中得到预期想要的成长,那就很浪费了。所以,如何找到一份好的实习?一看公司大小和背景,二看在该岗位实习的成长度。
一,利用好校友资源和校内的bbs,特别是知名大公司的校友资源。
二,遗留在各大网络上的招聘信息,如新浪微博的博主,知乎,V2EX
三,各个垂直招聘平台,如拉勾网。
5.找准自己的着力点,构建自己的技能树
在实习和专业学习中会逐渐发现自己的优势,围绕自己的优势,如JAVA,做深,加强内功的修炼。而使之能逐渐快速掌握其他所需的各类附属技能。这点也是短期和长期目标的结合。
好啦,看完摘客的三张清单,是不是也想为自己定下一个新年计划了呢。
其实好多时候看着新年的计划单,又多又难,真的害怕任务完不成,也真的害怕过程艰辛。但毕竟我们想要的并不只是一个结果,更好的东西,是过程中我们对这个世界全新的认识,对我们自己更深的认识,而且谁说不会有意想不到的彩蛋呢~
摘客
随心阅读,极致体验
优质互联网好文个性推荐阅读平台:http://zkread.com
收起阅读 »
推荐几款实用的Android Studio 插件
android-butterknife-zelezny 是根据butterknife定制的一款插件,能够方便快速初始化,
注意:需要先导入butterknife包才能使用
2、Gsonformat
可根据json数据快速生成java实体类
3、Android Postfix Completion
4、AndroidAccessors
5、Lifecycle Sorter
可以根据Activity或者fragment的生命周期对其生命周期方法位置进行先后排序,快捷键Ctrl + alt + K
6、JsonOnlineViewer
7、CodeGlance
8、findBugs-IDEA
顾名思义,就是帮你一起找bug的,当然Android Studio也提供了代码审查的功能(Analyze-Inspect Code...)
以上所有插件都可在android Studio plugin 仓库中找到 收起阅读 »
环信:基于IM的移动端全媒体智能客服
环信是一家SAAS公司,我们以SAAS形式向企业提供客户服务软件。
当用户处于移动互联网时代,用户都去手机上了,在手机上提供什么样的客户服务软件呢。我们认为在手机上的客户服务软件只有一种形式,就是我们以前用的那种IM技术,就是说你用一个很自然的人机沟通得方法,你跟你的客服聊天,问他各种问题。这是一个在手机上唯一一个可行也是经过,微信陌陌这种大量验证过的可用的客户服务形式。
我们是从做产品出身,过渡到做移动APP 的客服。现在来说,我会把这个所谓云客服的市场分为4块。第一是APP的客服,第二块是社交媒体,主要是微信和微博,第三块是网页在线客服,第四块是呼叫中心,那我们是在APP的内置客服这块做的最好的。那我们有环信技术通讯云的技术优势,这个优势领先其他公司非常大。
大家很多人可能都在网页上买过东西,问过客服,那么其实会有这么一个问题,你把网页一关,除非你给客服留下电话号码QQ号,否则客服找不到你的,因为网页已经关掉了,聊天窗口关掉了。所以现在我们有一种技术,我可以告诉你说,只要消费者不卸载你的APP,哪怕把你的APP杀死了,没关系,任何时候我能让客服坐席反过来跟消费者说话。因为我们属于IM长连接技术,这个技术是保证了在你的手机APP和客服的服务器有一个永远的连接,哪怕你这个APP到后台这个连接也活着,这时候永远都能反过来找消费者,这个技术是一个非常巨大的进步。
我们后台做了很多工作,比如说大家有一个共享的快捷回复,只要问这个问题,你点回复就直接回复了。我们会发现其实在这种聊天客服过程中,很多问题都重复的,那么这种问题我们是有固定答案的,这种情况下不需要过真人,先过滤到我们的机器人,百分之六十到八十的问题机器人就替你回答了,当机器人回答不了这个问题的时候,这个问题才会从机器人这条线,进到第二线,进到人工这条线,人工开始回答。人工在回答的时候也有很多工具帮助你,比如说,消费者每说一句话,这句话都会进到机器人知识库,那知识库就会对这个问题做匹配,它就会对这句话做一些建议的回答。客服本来要打20个字,现在变成点一些鼠标,就发出去了,所以我们有很多这样的小工具,一些人工智能的技术,来帮助咱们客服来提高工作效率。
我们赚钱很简单,我们就是按照席位卖钱。比如说我有一个大用户,他在我这买了大概三万个席位,我们一个席位是1500一年的,他一算三万乘以一千五,应该付我多少钱付多少钱。
我们团队现在人也比较多,现在一百五十多个人,我们是一个特别技术驱动型的团队。在我看来,做一个创业公司,做一个SAAS公司,销售很重要,市场也很重要,但是其实我觉得最重要的还是产品。我们在过去几年看到了兴起的很多创业公司,他们是以强运营,以商业模式为主的创业公司,但是我们判断从去年开始到后面这一年,会出现一批新的创业公司,就是以技术为导向型的。 收起阅读 »
表情mm|表情云™全面兼容环信SDK,打造更完整的富媒体消息解决方案
2016年1月20日起,国内首家表情云服务平台——表情mm|表情云™将全面兼容环信IM SDK,所有接入环信IM服务的产品都可以下载表情mm环信版SDK,快速接入表情云服务,轻松拥有自己的表情商店。
接入效果演示——轻元素APP
轻元素是国内首个运动减肥打卡社交平台,上线之初就获得小米应用市场精品推荐,目前Android/iOS线上版均已接入环信IM SDK和表情mm SDK。
小表情图文混排,大表情一键发送
为了适应用户习惯,表情mm|表情云™实现了“小表情图文混排,大表情一键发送”的功能,使用体验上向手Q和微信靠拢。
表情mm|表情云™五大特点
1、 国内最专业的表情SDK
灵活的架构和开放层次,所有图片托管于CDN加速网络 ,是国内最稳定快速的表情云平台
优秀的内容接入体系,IP版权方,第三方表情作者都可以方便的参与内容贡献
易于扩展,易于定制,可以根据自身需求灵活组装与定制。同时提供默认的表情键盘与表情商店界面,真正实现几行代码,一分钟接入
2、 最接地气的表情商店
与同道大叔、搜狐视频、魔鬼猫、咸蛋超人等深受90后用户喜爱的IP进行深度订制合作
拥有四年表情制作经验,对市场理解深入
3、 高品质·高逼格
微信·陌陌·搜狗输入法·华谊兄弟等巨头的合作伙伴,绘制包括巴萨球星系列·坏猴子·私人订制·有一天·小王子在内的正版官方表情
强大的IP资源,远离繁杂的授权纷争
高端•高质•高辨识度
4、 一站式解决表情问题
专门团队追随网络热点,超快企划超快更新
与多家大型影视公司、视频网站达成深度合作,对重点企划抢先了解、抢先制作
节约宝贵的开发时间,省去昂贵的外包费用,没有更新不及时的后顾之忧,开发者所有的表情问题,都可以得到一站式的解决
5、 整合数据服务
通过表情数据,了解用户对表情、动漫、明星的喜好,能为公司的界面风格、官方形象、活动设计、礼品包装、明星合作、推广阵地、增值服务、周边设计等方面提供有价值的参考
除了文字和语音,表情一直是即时通讯中重要的一部分,表情mm|表情云™和环信即时通讯云,此次强强联合,将为开发者提供更全面的即时通讯服务,更有效的富媒体消息解决方案,进一步提升用户体验,增强产品在市场上的竞争力。
下载地址:点击下载 收起阅读 »
不同技能的测试工程师是如何正确的进行自动化测试
很多人理解的自动化就是把手工测试case用脚本和工具转变成app自动化测试。也就是说把手工测试的每一个步骤用脚本来模拟,从而执行testcase。那么自动化的所有问题就归结于,如何用工具和脚本来转化手工操作步骤了。还有很多非常senior的,但是不会coding的手工测试工程师强调case的design能力是如何如何重要,自动化相对来说不是那么重要。
我这里可以肯定的说,没有好的编程功底,你也不可能设计出非常好的testcase,自动化的开发也不应该是仅仅把手工操作用脚本来模拟,而是应该大幅度的改变testcase,使得能够用最好的方式来进行自动化。那些手工测试人员所谓的设计case的重要性,和他们设计case的高水平,实际上只是在他们的知识范围之内产生的观点。下边我用一个小例子来说明,编程能力在自动化过程中起的作用到底有多大。基本上来讲,有多强的开发水平,就有多强的自动化设计,实现水平。自动化开发和产品的开发实际上都是一样的,都是有需求,你来实现。当然,不同水平的人,实现起来的效果是千差万别的。这也就是为什么开发有高手,有低手,自动化测试的开发也同样有低手,有高手。自动化测试水平没有上限,你要学会发挥自己的无穷潜力。
不多说了,现在说一下我们要自动化什么问题。我们有两个计算机帐号,A和B。我们需要用B帐号进行系统的设置,也就是测试的准备工作,然后用A帐号来进行测试。下边来说一下不同水平的人是如何进行自动化的。
1. 手工测试人员
Log on B
Configure
Log out
Log on A
Test
2. 初级自动化人员(直接把手工case转成自动化)
Set autologon B
Set autorun
Record test status: 0
Logout
Check status
if(status==0)
{
Configure
Set autologon A
Record test status:1
Logout
}
if(status==1)
{
Test
}
这个级别的人,需要懂得脚本编程,需要懂得系统设置,autologon and autorun。
3. 有一定经验的自动化人员(改变手工测试case以利于自动化的更简单,可靠的实现)
不需要log out and log on
利用Windows命令Runas
用高级语言调用Runas
利用重定向来输入Password
这个级别的人,需要懂得高级语言,重定向,Windows系统命令Runas
4. 中级自动化人员(具有更丰富的开发经验,可以用程序代替UI和系统命令)
不需要Runas命令
利用.NET的Process对象
用B的身份生成一个Process来进行配置工作
这个级别的人,要比较熟悉高级语言,比较熟悉高级语言的类库,懂得操作系统的内核基本概念
5. 高级自动化人员(精通高级语言,精通操作系统内核)
不需要多生成一个进程
用本线程impersonate用户B
利用.NET WindowsIdentity 对象
必须要调用Windows API,LogonUser
这个级别的人,要精通C/C++和Java,C#等高级语言,精通Windows内核的知识和Windows API
从以上的例子可以看到,针对同一个testcase,不同的测试人员,从手工到高级自动化,由于自己知识面的原因,会设计出非常不同的case出来。越高级的自动化越灵活,稳定,可靠,也更需要掌握更多的开发和内核的知识。因此,我们看到很多人在强烈的否定自动化,你先看看他到底在哪个层次中。越下边层次的自动化人员,由于技术的原因,碰到的问题会越多,能解决的问题却越少,因此对自动化的抱怨也就越大了。这些都是可以理解的,不过以此来否定自动化,我觉得还是不太应该,毕竟自己技术还不过关。
收起阅读 »
【原来配置环信apns就这么简单】内含各种问题点详讲
配置环信apns推送消息的准备工作配置证书:
链接1:http://docs.easemob.com/doku.php?id=start:300iosclientintegration:10prepareforsdkimport
工程中需要写的代码:
链接2:http://docs.easemob.com/doku.php?id=start:300iosclientintegration:80apns
如果按照以上方法配置完以后,测试的时候,如果还是收不到apns推送消息的话,按照下面步骤进行排查。注 意:(app在后台静默3分钟以上或者杀掉app,长连接断开才会走apns推送,3分钟以内的话要想收到消息通知,需要实现本地通知,环信demo是实 现本地通知的方法在 MainViewController.m类 - (void)showNotificationWithMessage:(EMMessage *)message,这个方法是在接收消息的回调中被调用的。具体的请查看demo。还需要注意一点的是,看看自己是否设置了全局免打扰,就是说在某个时段不接收apns推送消息,一般新集成的是不会设置的,设置代码在上面第二个链接2中)另外还要注意的是请确保导出p12时使用的电脑和创建 CertificateSigningRequest.certSigningRequest文件的电脑是同一台,导出证书的时候要直接点击导出,不要点击下面的内容导出,确认申请的证书是否带有推送功能。
1.检查下你后台绑定的证书名称和你工程里面的名称是不是对应的 ,初始化appkey的方法 填写的证书名称 (如图)
2.看下devicetoken是否传给了SDK,然后在环信管理后台看下IM用户是否显示了证书名称,如果显示了,说明devicetoken传给SDK,绑定成功了。
// 将得到的deviceToken传给SDK (真机上获取,打印下deviceToken)
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken{
[[EaseMob sharedInstance] application:application didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
} (如图)
3.调用rest接口,查看下登录的用户,绑定的证书名称和devicetoken是不是正确。这里说下查看的方法。
1)首先看下这个链接http://docs.easemob.com/doku.p ... users
调用rest接口,需要你的appkey ,Client Id,Client Secret(到环信管理后台查看),获取 到token
2)例子:
curl -X POST
"https://a1.easemob.com/easemob-demo/chatdemoui/token" '{"grant_type":"client_credentials","client_id":"YXA6wDs- MARqEeSO0VcBzaqg11","client_secret":"YXA6JOMWlLap_YbI_ucz77j-4-mI0dd"}'
这 个是环信的,改成自己的话,将“easemob-demo/chatdemoui” 换成自己appkey #号前后两 部分,"client_id"和"client_secret"后面的参数换成自己的Client Id与Client Secret对应 的参数,替换完成之后,复制粘贴到终端上,点击回车运行,如果没有出错会获取 到"access_token"后面的参数。
3)然后调用链接里面的 给IM用户的添加好友的接口
例 子:curl -X POST -H "Authorization: Bearer YWMtP_8IisA-EeK- a5cNq4Jt3QAAAT7fI10IbPuKdRxUTjA9CNiZMnQIgk0LEU2"
'https://a1.easemob.com/easemob-demo/chatdemoui/users/jliu/contacts/users/yantao'
YWMtP_8IisA- EeK-a5cNq4Jt3QAAAT7fI10IbPuKdRxUTjA9CNiZMnQIgk0LEU2这个参数换成自己获 取到的"access_token"后面的参数,‘easemob-demo/chatdemoui’换成自己的 appkey,‘jliu’和 ‘yantao’替换成自己的环信 ID,‘yantao’这个环信ID一定要是登录状 态的,只有在登录状态才会获取到他绑定的证书名称和devicetoken。替换完成之后,复制粘贴 到 终端上,点击回车,正确的话,会从得到的信息中看到"notifier_name"和"device_token"这 两个参数,就是证书名称和 devicetoken,检查下是否正确。
4.测试推送证书的时候,首先登录两个环信ID(其中一个账号需要真机登录),相互收发消息,如果没有 问题的话,那么将真机上的app直接杀掉(双击 home键,找到对应的app杀掉),然后给之前真机登录的环信ID发消息,如果一切都正常的话,那么会收到apns推送消息,通知栏会有提示。
5. 如果还是收不到推送的话,可以将p12证书,证书密码,devicetoken,还有是什么环境的证书,这些信息发给环信的技术支持,帮着测试下证书。一般收 不到推送,都是证书的问题,需要重新配置。(如果是生产环境的证书,需要你的app上传到AppStore或者ad hoc打包,才能测试。)
收起阅读 »
集成环信3.0 处理UI上展示昵称 头像的方法
首先明确一下,环信只是即时通讯的消息引擎。环信本身不提供用户体系,环信既不保存任何APP业务数据,也不保存任何APP的用户信息。
根据环信ID来绑定用户的昵称,头像,方便维护。 环信3.0 demo中,是用parse来管理昵称,头像的(parse是管理昵称,头像的一个三方库,将头像,昵称上传到parse服务器,在从parse服务器获取),从自己服务器获取的话就按照下面的方法参考一下吧。
1. 服务器维护昵称,头像的方案先看下这个链接: http://docs.easemob.com/im/490integrationcases/10nickname
2. 从自己服务器获取到用户的昵称,头像后,会话列表类的替换,在EaseConversationCell.m类,- (void)setModel:(id<IConversationModel>)model中 [self.avatarView.imageView sd_setImageWithURL:[NSURL URLWithString:_model.avatarURLPath] placeholderImage:_model.avatarImage]; 这个方法就是来展示头像的
3. 聊天类的替换,在EaseBaseMessageCell.m类,- (void)setModel:(id<IMessageModel>)model中
if (model.isSender) {
UIImage *placeholderImage = [UIImage imageNamed:@"123"];
self.avatarView.image = placeholderImage;
} else {
if (model.avatarURLPath) {
[self.avatarView sd_setImageWithURL:[NSURL URLWithString:model.avatarURLPath] placeholderImage:model.avatarImage];
} else {
self.avatarView.image = model.avatarImage;
}
}
if (model.isSender) 我自己加的判断 区分发送者和接受者的头像(isSender判断是不是当前登录者),如果想在这个类中想要获取到对方的环信ID,那么引入 #import <EMMessage.h>头文件, EMMessage *message = model.message; NSString *username = message.from;就可以获取到了,然后自己在根据环信ID自己做处理,展示。 如果是群聊的话,想要获取到群成员在群里发送消息人的环信ID,通过message.groupSenderName 获取到。
4.联系人类的替换,在EaseUserCell.m类,- (void)setModel:(id<IUserModel>)model中,self.titleLabel.text = _model.buddy.username; [self.avatarView.imageView sd_setImageWithURL:[NSURL URLWithString:_model.avatarURLPath] placeholderImage:_model.avatarImage];
具体的到3.0demo中 自己看一下吧, 仅仅给提供个参考。 收起阅读 »
年底了,你们公司搞年会了吗?年会节目都是啥样的呢?
这一年,时间就在不经意间悄然逝去。
一年的时间,改变来的太快。每每辞旧迎新的时刻,互联网人依然停不下来忙碌加班的脚步(心疼我自己)~~
一年的时间,产品更新迭代。互联网行业的迷人之处就在于快速成长,
我们终究是收获了!
一年的时间,业务有好有坏。但因为有小伙伴们互相的宽容与支持,我们终究是在这个“互联网寒冬”中挺过来了。
在这里,环信预祝各位小伙伴们在新的一年蓬勃发展,日胜一日。
昨天接到通知,今年年会在本月下旬的时候吃饭+年会,每个部门出1-2个节目娱乐一下,放假前搞一个晚会。
又到了提节目的时候,去年可是为了年会节目几度失眠呀!
感觉唱歌太单调了,跳舞不会,准备点啥节目好呢?大家年会是怎么过的啊?
年会到了,不妨我们停下脚步,好好享受这难得的恬静时光。
这里上传几张去年环信年会照片给亲们感受下,向过去致敬!
年底了,你们公司有搞年会吗?节目有哪些?有抽到什么礼物呢? 收起阅读 »
[huanxin-sdk] 环信 Rest Api Node sdk
The node sdk of Huanxin Rest API for high performance
详细用法请移步:test/,建议使用 redis 存储token
'use strict'; var Huanxin = require('huanxin-sdk');
var huanxin = new Huanxin({
org_name : 'your_org_name',
app_name : 'your_app_name',
client_id : 'your_client_id',
client_secret : 'your_client_secret',
tokenSet: function(err, data){
/**
* data = {
* access_token: '环信返回的token值',
* expires_in: '过期时间(秒),按照当前返回是60天,但是实际是7天就会过期,不可用',
* application: '应用id'
* }
*/
// 518400 设置6天过期
redisClient.setex('HXTOKEN_TEST1', 518400, data.access_token, function(err, res){ DEBUG_HUAXIN('TokenSet err %j, res %s', err, res);
});
},
tokenGet: function(callback){
// token 缓存层,内部不会调用 getToken 方法实时获取
/**
* @return {String} token
*/
redisClient.get('HXTOKEN_TEST1', callback);
}
});
huanxin.getToken(function(err, data){
//...something to do ...
});
项目下载:
NPM:huanxin-sdk
GitHub:huanxin-sdk
收起阅读 »
服务端调用rest接口示例demo
服务端怎么集成?
--写http请求调rest接口,对语言没限制,不需要导jar
有没有觉得这句回复很熟悉?
看到经常有服务端、后台 集成不知道怎么弄的,这里就把环信服务器端API的示例代码整理出来供大家参考
大家在使用过程中有什么问题 建议欢迎直接提出。
这里收录几个社区贡献 的demo ,大家有自己写的也可以联系我放在这里展示!
Java实现环信服务器端接口:http://www.imgeek.org/article/825307464
php和nodejs服务端代码示例参考:http://www.imgeek.org/article/825307461
关于环信.net服务端demo的一点补充(解决出错无法返回json格式的问题):http://www.imgeek.org/article/825307540
Node.js调用rest接口http://www.imgeek.org/article/825307544
.net调用rest接口http://www.imgeek.org/article/825308176
收起阅读 »
关于环信.net服务端demo的一点补充(解决出错无法返回json格式的问题)
.net的demo链接地址如下:
https://github.com/easemob/emchat-server-examples/blob/master/emchat-server-dotnet/EaseMobDemo.cs
发现示例demo有一个比较关键的问题没有解决,
就是EaseMobDemo的public string ReqUrl(string reqUrl, string method, string paramData, string token)
这个函数,如果stream流读取resp发生错误的话,无法返回json类型的字符串,而是直接400,500之类的错误,
结果无法获得json字符串格式。
例如:注册用户时,如果用户已经存在,按照原来的代码
会返回 {"远程服务器返回错误: (400) 错误的请求。"}
这个不是json格式,无法用程序反序列化。
后来发现Java的服务端demo,如果出错是可以返回json格式的错误代码。
向环信客服咨询,也没人知道怎么做。
经过研究把代码做了一下修改,前面代码不变,只是把错误捕捉的部分作了修改,增加了一个函数
public string ReqUrl(string reqUrl, string method, string paramData, string token)
{
try
{
HttpWebRequest request = WebRequest.Create(reqUrl) as HttpWebRequest;
request.Method = method.ToUpperInvariant();
request.ContentType = "application/json";
if (!string.IsNullOrEmpty(token) && token.Length > 1)
{
request.Headers.Add("Authorization", "Bearer" +" "+ token);
}
if (request.Method.ToString().Trim() != "GET" && !string.IsNullOrEmpty(paramData) && paramData.Length > 0)
{
byte buffer = Encoding.UTF8.GetBytes(paramData);
request.ContentLength = buffer.Length;
request.GetRequestStream().Write(buffer, 0, buffer.Length);
}
using (HttpWebResponse resp = request.GetResponse() as HttpWebResponse)
{
using (StreamReader stream = new StreamReader(resp.GetResponseStream(), Encoding.UTF8))
{
return stream.ReadToEnd();
}
}
}
catch (WebException e)
{
return GetErrorInfo(e);
}
}
///
/// 增加此方法,用户可以返回json格式错误。
///
///
///
private string GetErrorInfo(WebException ex)
{
string errorinfo = "";
HttpWebResponse resp = (HttpWebResponse)ex.Response;
if (ex.Status == WebExceptionStatus.ProtocolError)
{
using (StreamReader streamreader = new StreamReader(resp.GetResponseStream(), Encoding.UTF8))
{
errorinfo = streamreader.ReadToEnd();
}
}
return errorinfo;
}
这样就是发生错误,也返回json格式。
以下是注册已经存在的用户,错误返回格式
{
"error":"duplicate_unique_property_exists",
"timestamp":1452661004439,
"duration":0,
"exception":"org.apache.usergrid.persistence.exceptions.DuplicateUniquePropertyExistsException",
"error_description":"Application fbd110b0-884d-11e5-8f9c-e94cec2e0ad0 Entity user requires that property named username be unique, value of fan exists"
}
为其他开发者少走弯路,提供一定的参考。 收起阅读 »
环信扩展消息的UI问题
程序员黑客马拉松等你来战
关于猿圈科技
猿圈(www.oxcoder.com)是一家专业的技术测评网站,通过平台帮助企业高效省时地识别最优秀的编程技术人才。同时帮助程序员提升编程能力,用代码脱颖而出,在获得工作机会的同时,识别程序员在编程过程中的不足,并且提供量身定制的学习板块。
关于环信
环信(www.easemob.com)成立于2013年4月,是一家全通讯能力云服务提供商。产品包括全球最大的即时通讯云PaaS平台——环信即时通讯云,以及全球首创的全媒体智能云客服平台——环信移动客服。
关于码易众包
码易众包,致力于提供移动互联网产品设计、开发、运营及推广服务,是一个高质量交付平台,让产品开发更快,让程序员更赚钱。公司目前是国家和中关村高新技术企业,并在北京股权交易中心挂牌成功,入选中关村“金种子工程”企业。
比赛信息
报名要求: 程序猿
活动时间: 2015.12.23-2016.1.23
活动形式: 通过猿圈网站的在线测评平台进行编程挑战;
报名截止: 01月20日(1月20日晚12点报名通道关闭)
选手在本报名页面完成后,会在报名当日晚9点前收到比赛通知邮件,按照邮件内提示即可开始初赛环节。
初赛的前二百名编程高手,届时主办方相关工作人员电话通知,进行线上决赛并角逐出前5名。
比赛奖品
一等奖奖金3000元。二等奖,三等奖奖金若干。
前5名每人将获得一块精美手表。
前5名每人奖励猿圈价值1199元的学习卡一张。
前50名每人将获得麦步科技提供的100元购物券一张。
前200名参与最终的跨年颁奖晚会,将获得知名企业offer,技术大牛经验分享等。
活动现场另有精心礼品相送。
日程安排
本次活动包括线上初赛,线上决赛,跨年颁奖大会三部分,其中初赛,决赛环节,通过猿圈网站的在线测评平台进行编程挑战。
报名初赛: 12月23日----01月20日
线上决赛: 01月10日----01月22日
线下颁奖: 01月23日
黑客马拉松,等你来战!
用你的缜密,用你的创思,用你的明慧改变人生轨迹吧!
让这世界与时代见证你的勤奋,踏实和卓越。
时代召唤着程序员们,名企盼望着技术宅们。
让我听到你们摩拳擦掌想要重出江湖的声音吧!
一语言世界,编程无极限!
立即报名:http://form.mikecrm.com/f.php?t=JU8shq
致谢
感谢深圳市麦步科技有限公司和BeeCloud公司为对本次活动的大力支持!
麦步科技是一家智能穿戴设备行业的创新企业,主要从事智能穿戴类软、硬件的研发与生产,主要产品包括麦步微信计步器、麦步智能手表、麦步APP等。
BeeCloud为开发者提供一站式支付解决方案。通过提供易用友好的支付SDK,几行代码高效实现网页、APP支付功能,并提供可靠稳定可靠的分布式云后端服务,保障支付流程安全流畅。 收起阅读 »
开发一个APP要100万?用互联网思维只要1万
近期经常看到一些关于开发一个App要多少钱的文章出来,有的说App开发要100万,有的说要60万,其实这个不叫开发App,那叫做项目或者创业开公司要多少钱才对,而这种情况下之所以要这么多钱,也是基于一个对行业啥都不懂的情况下来计算的,所有的东西都需要你花钱来完成的。
我们可以想象一下,你对移动互联网一窍不通,但是你想开个公司,创业做个App,那么还真是啥都要重新招,你需要招聘四个客户端(两个负责ios、两个负责android),两个服务端,一个设计师,一个产品经理,一个人事,一个行政,甚至还需要负责扫地的阿姨……除此之外,还需要注册公司,租办公室,再加上招人又不是一时半会能招到,产品需求一时半晌还确定不了,再加上万一有点程序员太菜,又做不了,加上看错人的失误成本,这样算下来,几百万都打不住。如果你是这样一个创业小白,建议你还是别去创业了,因为这样的创业方法实在太笨了,就是App做出来,创业也会笨死的。
这样的思维方式其实还是传统思维方式,传统思维就是需要什么东西都来自己做,比如生产一个汽车,你要生产轮胎,你需要生产汽车的挡风玻璃,汽车的所有零件你都需要生产,只有这样才能实现你制造汽车的梦想,但是很多人不知道,这样的传统思维早已经过去了,在当今的移动互联网时代,如果还以传统思维来做互联网,那是必定失败的。
而真实的移动互联网是什么?就是你需要生产汽车,但是已经有各个公司已经造出了各种各样的零件,你只要把他们组装在一起就好了。如果用到App开发上来说,就是开发一个App已经很简单了,你只需要把需要App的那些组成部分找个工程师组装起来即可。
下面笔者就给大家来举个例子,说明开发一个App要多少钱,大家可以猜猜,这个App是我认识的一个90后小鲜肉做的,一个校园点餐App,通过这个App大学生可以向学校周围的餐馆订餐,订餐时能看到学校周边每家饭店的特色菜、优惠、价格等等。
下面我就分解下这个App的成本构成,以及开发相关的功能模块。
开发工具:HBuilder 免费
前端:MUI 免费
后端:野狗 免费
统计:友盟SDK 免费
推送:个推 免费
推广渠道:360手机助手 免费
开发周期:3周
开发成本:由于基于mui开发App,生成流应用的同时,iOS、Android原生版、浏览器版、微信App和百度直达号版也随之生成,实现了多端发布,开发成本不到1万块。
开发人数:1人。
……
这个App使用了目前市面比较流行的工具,比如HBuilder;以及集成了很多免费的功能模块,比如友盟的统计、个推的推送。而且这些工具和功能模块都是免费的,所以开发成本是零。另外在开发人员的配置上,由于使用了MUI前端工具,使得一次开发,就完成了ios、和其他终端的生成,减少了开发人员的数量,使得1人就可以完成。所以在跨平台方面和版本适配方面,开发成本也大大降低。这种对移动互联网各种工具的详细了解和熟练使用,使得App的开发成本整体下降很多,最后核算下总的成本仅有1万左右。app测试想省点就自己来,想高效就用testbird。
另外,大家可能说这个App很简单,不需要太多的成本,想添加更多的功能,这些都没有问题,在当下的App的开发中有各种各样的功能插件可以让我们免费使用比如融云的IM,可以让你的App具有即时通讯的功能,后端的leancloud可以让你瞬间就有后端的统计、推送等各种功能、还有安全相关的360加固保,可以保护你的App代码,防止被反编译等等,当下的很多2B企业都提供了这些功能模块,在开发App的过程中,我们只要把这些功能模板组装起来即可……这样算下来,我们可以看出,开发一个App其实要不了多少钱。
举这个例子的目的其实就是告诉大家,做开发需要有互联网思维,促进开发资源的合理使用,这样才能降低开发成本。现在的很多App动不动就上百兆非常大,装的多了,手机卡的厉害,用户体验很差,主要原因还是很多开发工程师并不熟练,很多项目缺少资深的项目经理,在开发过程中把关于我们、以及帮助这些页面,还有很多用户使用少的模块,都写在了本地,使得整个App开发完成后,安装包非常大,一般都在50M以上(大家可以打开自己的苹果手机看看自己下载的App占了多大的空间),推广困难,更新维护麻烦,体验很差,使得大量的推广费用花出去了,转化却是很低,这些都是开发导致的问题。
所以说开发App需要对移动互联网行业做深入的了解,选择更优化的App开发方法,合理搭配开发人力,降低开发成本,比如把一些核心功能写成本地端的,把一些不常用的模块写成H5的,将原生和H5混合使用,这样就可以降低开发成本,提高App的产品体验,优化App包的大小,这样降低了App的推广成本。
当然了,有了互联网思维,你要想注册公司,交社保,法务咨询……在当下的创业环境里,都可以寻找到这样很多的2B服务商来帮你完成,这些服务商都专注一块领域,做的比较深,值得使用,也就是说你可以足不出户,只要把这些优质的服务机构整合在一块,让他们帮你办事,你就可以做一个项目出来。
在移动互联网时代,做项目,做App就要用移动互联网思维,不仅要纵向了解,还要横向了解泛度和深度都要做到,这样我们去做项目创立公司才能节省成本,否则只能是一叶障目,不见森林。 收起阅读 »
iOS开发经常用到的图片加载库简介
下面就简单介绍一下SDWebimage的简单用法:
1.在库中找到UIImageView+WebCache.h文件,调用这个方法- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder;可以实现图片的异步缓存加载,并且可以同时添加占位符图片。
2.在UIImage+GIF.h文件中,这个方法+ (UIImage *)sd_animatedGIFNamed:(NSString *)name,可以实现异步加载本地DIF图片。
除了这些简单的用法之外SDWebimage还封装了许多加载图片的方式,比如说通过设置其中的一些属性,就可也实现图片的不同缓存方式,以及加载图片时的进度显示等等。
收起阅读 »
一个简单的俄罗师方块
简单的俄罗师方块下面是部分程序,我把整体的上传到附件了
#import
#import "AppController.h"
#import "cocos2d.h"
#import "EAGLView.h"
#import "AppDelegate.h"
#import "RootViewController.h"
@implementation AppController
@synthesize window;
@synthesize viewController;
#pragma mark -
#pragma mark Application lifecycle
static AppDelegate s_sharedApplication;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
// Add the view controller's view to the window and display.
window = [[UIWindow alloc] initWithFrame: [[UIScreen mainScreen] bounds]];
EAGLView *__glView = [EAGLView viewWithFrame: [window bounds]
pixelFormat: kEAGLColorFormatRGBA8
depthFormat: GL_DEPTH_COMPONENT16
preserveBackbuffer: NO
sharegroup: nil
multiSampling: NO
numberOfSamples:0 ];
// Use RootViewController manage EAGLView
viewController = [[RootViewController alloc] initWithNibName:nil bundle:nil];
viewController.wantsFullScreenLayout = YES;
viewController.view = __glView;
// Set RootViewController to window
if ( [[UIDevice currentDevice].systemVersion floatValue] < 6.0)
{
// warning: addSubView doesn't work on iOS6
[window addSubview: viewController.view];
}
else
{
// use this method on ios6
[window setRootViewController:viewController];
}
[window makeKeyAndVisible];
[[UIApplication sharedApplication] setStatusBarHidden: YES];
cocos2d::CCApplication::sharedApplication()->run();
return YES;
}
- (void)applicationWillResignActive:(UIApplication *)application {
cocos2d::CCDirector::sharedDirector()->pause();
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
cocos2d::CCDirector::sharedDirector()->resume();
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
cocos2d::CCApplication::sharedApplication()->applicationDidEnterBackground();
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
cocos2d::CCApplication::sharedApplication()->applicationWillEnterForeground();
}
- (void)applicationWillTerminate:(UIApplication *)application {
}
#pragma mark -
#pragma mark Memory management
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
cocos2d::CCDirector::sharedDirector()->purgeCachedData();
}
- (void)dealloc {
[super dealloc];
}
@end
收起阅读 »
APP测试基本流程
一、 测试周期
app测试周期一般为两周,根据项目情况以及版本质量可适当缩短或延长测试时间。正式测试前先向主管或产品经理确认项目排期。
二、测试资源
测试任务开始前,检查各项测试资源。
产品功能需求文档
产品原型图
产品效果图
行为统计分析定义文档
测试设备(ios3.1.3-ios5.0.1;Android1.6-Android4.0;Winphone7.1及以上;Symbian v3/v5/Nokia Belle等)
其他(例如有秒杀专题的项目,需要规划秒杀时间表;有优惠券使用的项目,需要申请添加优惠券数据;支付宝/银联支付功能的项目,需要提前申请支付宝/银联账户等等)
二、测试要点
接收版本
本人觉得,这个过程可以直接略过。非专业测试着,不喜勿拍。
UI测试
A) 确保手头的原型图与效果图为当前最新版本。
B) 确保产品UI符合产品经理制定的原型图与效果图。
C) 一切界面问题以效果图为准,若有用户体验方面的建议,必须先以邮件或口头的形式询问产品经理。
D)由于测试环境中的数据为模拟数据,测试时必须预先想到正式环境中可能出现的数据类型。
功能测试
A) 确保手头的功能需求文档为当前最新版本。
B) 确保所有的软件功能都已实现且逻辑正常。
C) 一切功能问题以需求文档为准,若有用户体验方面的建议,必须先以邮件或口头的形式询问产品经理。个人建议,用户体验方面的建议,优先级放在修复bug之后。
D)若有些功能在技术上难以实现或者由于排期的原因无法在短时间内实现,必须得到产品经理的确认,而不是单单只听开发人员的技术解释。此处确认最好以邮件形式存在。
E)所有的“外部原因”问题,都需要尽早地督促开发人员与客户服务端人员联系协调解决。并在之后的测试报告中予以体现。
F)所有的“设计如此”、“延期处理”问题,都需要和产品经理确认后再进行验证。并在之后的测试报告中予以体现。
G)测试下单时,注册的测试账号必须符合公司规范;收货地址必须包含“测试”关键字,最好每次下单的名称中含有日期,以便查询;在正式环境中下单后必须取消该订单等。
兼容测试/性能测试
A) 确保软件在所有兼容机型上都能正常使用(ios一般需要兼容7或者6, ios5可以不用,用户使用率已经低于5%以下)
B) 对于低端性能兼容机上独有的问题(例如ios5以下、Android1.6以下),若在技术上难以修改或者由于排期的原因无法在短时间内改进,必须在测试日报中注明,并得到技术平台主管、产品经理以及运营人员的确认,最好以邮件的形式得到确认)
C) 性能测试方面必须满足硬件压力条件下的测试需要(例如多线程,用户常用的app都要后台运行的环境中测试。)
D)网络响应用户体验方面的性能测试,需要保证在wifi、3g、2g网络下的切换效果。比如wifi切换到2g,网络响应的速度以及切换界面。
后台订单统计测试
A) 核对“客户端相关启动查询”项,此项数据就是经常说的“激活量”,非常重要。测试时必须保证该项中的各数据均正确,且每次启动软件都会有相应的统计记录。
B) 核对“订单查询”项,测试时必须保证各数据均正确,且每次成功下单后都会有相应的统计记录。
C) 需要注意的是,在成功下单之后,后台会做判断将该订单划到测试订单范围,测试人员必须到“订单查询(测试)”模块中核对订单统计记录信息。
用户行为统计测试
A)确保手头的行为统计分析定义文档为最新版本,且与开发人员手中的文档一致。
B)确保产品经理在文档中所定义的页面在该产品中都是存在的。
C)尽可能真实地模拟用户行为。
D)核对统计日志,确保各项操作所对应的页面ID以及操作ID都是正确的。
回归测试
A)软件最终上线前,需对产品进行回归测试,测试内容包含之前所有的测试项目
B)回归测试不再对细节进行测试,而是类似于对产品进行验收,从客户正常使用的角度对产品进行再一轮的整体测试。
C)只有在回归测试通过之后,才对产品进行提交。
三、测试日报及产品上线报告
测试人员每天需对所测项目发送测试日报。
测试日报所包含的内容为:
A)对当前测试版本质量进行分级。
B)对较严重的问题进行例举,提示开发人员优先修改。
C)对版本的整体情况进行评估。
产品上线前,测试人员发送产品上线报告
现使用Testbird进行app自动化测试,省时省事省钱~~~
整理:人人都是产品经理 lemongrass
转载自:人人都是产品经理 收起阅读 »
解决很多人的程序崩溃问题,可以追踪到程序崩溃的位置
如何自动定位到程序崩溃的地方(iOS Xcode 自带crash 崩溃问题的追踪方法)
这里不知道图片为啥粘不上去,只有传到附件了,下面的图都是按照上面先后顺序排列的,我把整个文档传到附件里
一、Exception breakpoint 的添加
1、切换到breakpoint 视图界面(第一张图)
2、点击最底端的"+"按钮,添加Add Exception BreakPoint,这个就是捕获所有的exception, 貌似stackoverflow上说,bad_access那种错误无法捕获的,这个用于捕获那些SIGSEGV 的错误。 (第二张图)
3、添加完成之后的界面(第三张图)
二、Symbolic breakpoint的添加 (第四张图)
第二步选择的时候选 Add Symbolic BreakPoint
第三步截图;添加完成之后添加上objc_exception_throw
收起阅读 »
Android V2.2.5 2015-12-31版本更新
2015-12-31 环信发布了最新的安卓SDK版本V2.2.5,大家可以关注一下,新版本的功能优化点如下:
1)实时通话新增弱网监测、暂停或打开音频视频流等API(相应增加的方法可查看文档)。
2)优化实时通话音质,完善了语音编码算法,提高了语音清晰度。
3)在小米手机上,im离线时支持使用小米推送来进行消息的推送。
4)GCM优化,手机切到后台一段时间后,在支持GCM的app及手机上sdk会主动断掉和im长连接,
消息通过GCM推送到客户端,使手机更省电 。
5)修复实时通话对方拒绝时,有时候不显示拒绝的bug。
欢迎大家下载体验,下载地址http://www.easemob.com/downloads
使用过程中,有任何问题建议欢迎在下方评论直接回复留言! 收起阅读 »
教你如何从零开始,用环信ios sdk实现即时视频和聊天
说说需求:开发一个可以进行即时视频聊天软件.
1. 最近比较忙,考完试回到公司就要做这个即时通信demo.本来是打算用xmpp协议来做视频通信的,想了想要搞后台,还要搭建服务器.一开始没明白是怎么样的一种形式.(现在想了想,其实就是自己写个服务器,然后放在服务器上而已了""脑袋被驴踢了).让后问boss服务器是我自己写还是怎样?然后boss让我先做个环信的demo,搞完再搞xmpp.
[服务器的安装包]
2. 环信,什么鬼?
- 1.集成IOS SDK前的准备工作:
(如果需要推送消息,则要到苹果官网上制作证书,再到环信后台制作推送证书.
详细请看http://www.easemob.com/docs/ios/IOSSDKPrepare/#registerDeveloper)
注册环信开发者账号并创建后台应用,
登陆地址:https://console.easemob.com/?comeFrom=easemobHome
注册和登陆就不多说了,只介绍创建应用: 一般我们选择开放注册.
![创建应用]
![得到appkey]
- 2.然后开始创建工程:
下载环信Demo及SDK:http://www.easemob.com/sdk/
解压缩iOSSDK.zip后会得到以下目录结构:
将EaseMobSDK拖入到项目中,并添加SDK依赖库
添加 以后,在AppDelegate中注册SDK
```
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{//registerSDKWithAppKey:为应用标示,apnsCertName为推送证书;(如果没用推送证书,这里可以随便)
[[EaseMob sharedInstance] registerSDKWithAppKey:@"easemob-demo#chatdemo" apnsCertName:apnsCertName];
// 需要在注册sdk后写上该方法
[[EaseMob sharedInstance] application:application didFinishLaunchingWithOptions:launchOptions];
return YES;
}
```
3.下面我们开始主要界面的详解:
先上图吧.(因为视频通信需要两台真机,而我只能和iPhone对模拟器,所以没有视频图,但demo已经过测试可以视频聊天)
- 1. 要进行对话,必须先有用户名,这样才能让对方找到你.(先注册,再登录.退出(用于测试))
![登录页面]
登录页面的代码比较简单.
```
//异步注册账号
[[EaseMob sharedInstance].chatManager asyncRegisterNewAccount:name.text
password:password.text
withCompletion:
^(NSString *username, NSString *password, EMError *error) {
if (!error) {//注册成功,显示马上登录
UIAlertView* alert = [[UIAlertView alloc] initWithTitle:nil
message:@"注册成功,请登陆"
delegate:nil
cancelButtonTitle:@"OK"
otherButtonTitles:nil];
[alert show];
alert = nil;
}else{
//输出错误信息.
}
} onQueue:nil];
//////////////////////////////////////////////////////////////////////
// //异步登录账号
[[EaseMob sharedInstance].chatManager asyncLoginWithUsername:name.text
password:password.text
completion:
^(NSDictionary *loginInfo, EMError *error) {
if (loginInfo && !error) {
//设置是否自动登录
[[EaseMob sharedInstance].chatManager setIsAutoLoginEnabled:YES];
// 旧数据转换 (如果您的sdk是由2.1.2版本升级过来的,需要家这句话)
[[EaseMob sharedInstance].chatManager importDataToNewDatabase];
//获取数据库中数据
[[EaseMob sharedInstance].chatManager loadDataFromDatabase];
//获取群组列表
[[EaseMob sharedInstance].chatManager asyncFetchMyGroupsList];
#warning 开始跳转,然后开始聊天
NSLog(@"登录成功");
TTAlertNoTitle(@"登录成功");
[self thisToChatViewController];
//发送自动登陆状态通知
//[[NSNotificationCenter defaultCenter] postNotificationName:KNOTIFICATION_LOGINCHANGE object:@YES];
}
else
{//输出错误信息.(太多,所以详见demo)
}
} onQueue:nil];
```
- 2.登录后,有挺多的控件,
- 1.返回键:点击时会调用-(void)dealloc方法,用做登出操作.
- 2.UITextField: 你想要发送对象的名称,例如8080(下面将用8080做例子).
- 3.send:建立一个与8080的会话.
- 4.cheak:用于测试(现在没用了)
- 5.hehe黄色的lable: 当有收到消息的时候,文字内容就会发生改变,显示还有多少条未读信息.
- 6.最下面是UITableview,显示会话联系人.
![聊天列表]
```
首先要将代理设置好
[[EaseMob sharedInstance].chatManager removeDelegate:self];
//注册为SDK的ChatManager的delegate
[[EaseMob sharedInstance].chatManager addDelegate:self delegateQueue:nil];
[[EaseMob sharedInstance].callManager addDelegate:self delegateQueue:nil];
//最后一个为即时通讯的代理,(即时视频,即时语音)
当离开这个页面的时候,要讲代理取消掉,不然会造成别的页面接收不了消息.
[[EaseMob sharedInstance].chatManager removeDelegate:self];
[[EaseMob sharedInstance].callManager removeDelegate:self];
这样以后,就可以使用代理方法来作一些事情了
1.接收消息
-(void)didReceiveMessage:(EMMessage *)message
2. 未读消息数量变化回调
-(void)didUnreadMessagesCountChanged
3.实时通话状态发生变化时的回调,(如果没有实现这个函数,视频聊天邀请就会接收不到.)
- (void)callSessionStatusChanged:(EMCallSession *)callSession changeReason:(EMCallStatusChangedReason)reason error:(EMError *)error
```
- 3.(已经更新)因为是demo,就先用UITextView来显示对话,在UITextField中输入文字,点击send,即可发送,对方发送过来的内容也可以看到.点击视频聊天,大概要等5-10s对方才能收到请求.
```
发送消息
-(void) send:(UIButton *)sender{
// conversation= [[EaseMob sharedInstance].chatManager conversationForChatter:@"ozxozx" conversationType:eConversationTypeChat];
EMChatText *txtChat = [[EMChatText alloc] initWithText:sendContext.text];
EMTextMessageBody *body = [[EMTextMessageBody alloc] initWithChatObject:txtChat];
// 生成message
EMMessage *message = [[EMMessage alloc] initWithReceiver:userName bodies:@[body]];
message.messageType = eMessageTypeChat;
EMError *error = nil;
id
// [chatManager asyncResendMessage:message progress:nil];
[chatManager sendMessage:message progress:nil error:&error];
if (error) {
UIAlertView * a = [[UIAlertView alloc] initWithTitle:@"error" message:@"发送失败" delegate:nil cancelButtonTitle:@"好" otherButtonTitles:nil, nil];
[a show];
}else {
textview.text = [NSString stringWithFormat:@"%@\n\t\t\t\t\t我说:%@",textview.text,sendContext.text];
}
}
```
```
//从会话管理者中获得当前会话.并将会话内容显示到textview中
-(void) addtext{
//1.
EMConversation *conversation2 = [[EaseMob sharedInstance].chatManager conversationForChatter:userName conversationType:0] ;
NSString * context = @"";//用于制作对话框中的内容.(现在还没有分自己发送的还是别人发送的.)
NSArray * arrcon;
NSArray * arr;
long long timestamp = [[NSDate date] timeIntervalSince1970] * 1000 + 1;//制作时间戳
arr = [conversation2 loadAllMessages]; // 获得内存中所有的会话.
arrcon = [conversation2 loadNumbersOfMessages:10 before:timestamp]; //根据时间获得5调会话. (时间戳作用:获得timestamp这个时间以前的所有/5会话)
// 2.
for (EMMessage * hehe in arrcon) {
id
NSString *messageStr = nil;
//3.
messageStr = ((EMTextMessageBody *)messageBody).text;
// [context stringByAppendingFormat:@"%@",messageStr ];
if (![hehe.from isEqualToString:userName]) {//如果是自己发送的.
context = [NSString stringWithFormat:@"%@\n\t\t\t\t\t我说:%@",context,messageStr];
}else{
context = [NSString stringWithFormat:@"%@\n%@",context,messageStr];
}
}
textview.text = context;
}
```
1 .EMConversation *conversation2 :会话对象,里面装着当前对话的双方的各种消息(EMMessage).
2 . EMMessage 消息.
```
一个message的内容(对方发来的)
{"messageId":"83265683047055820","messageType":0,"from":"ozx8899","bodies":
["{\"type\":\"txt\",\"msg\":\"反对党\"}"]
,"isAcked":true,"to":"ozxozx","timestamp":1436951601011,"requireEncryption":false}
```
3.```((EMTextMessageBody *)messageBody)即为"bodies":["{\"type\":\"txt\",\"msg\":\"反对党\"}"]
即:消息为文本,信息内容为:反对党```
##视频聊天
```
-(void)openTheVideo:(UIButton *)btn{
BOOL isopen = [self canVideo];//判断能否打开摄像头,(太多,详见demo)
EMError *error = nil;
EMCallSession *callSession = nil;
if (!isopen) {
NSLog(@"不能打开视频");
return ;
}
//这里发送异步视频请求
callSession = [[EaseMob sharedInstance].callManager asyncMakeVideoCall:userName timeout:50 error:&error];
//请求完以后,开始做下面的
if (callSession && !error) {
[[EaseMob sharedInstance].callManager removeDelegate:self];
CallViewController *callController = [[CallViewController alloc] initWithSession:callSession isIncoming:NO];
callController.modalPresentationStyle = UIModalPresentationOverFullScreen;
[self presentViewController:callController animated:NO completion:nil];
}
if (error) {
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"error", @"error") message:error.description delegate:nil cancelButtonTitle:NSLocalizedString(@"ok", @"OK") otherButtonTitles:nil, nil];
[alertView show];
alertView = nil;
}
}
```
视频聊天的代码主要在CallViewController中.如果需要改变视频通话时的界面,就要改此控制器中的布局内容.
最要值得主要的是在dealloc函数中一定要加下面两句
```
[[EaseMob sharedInstance].callManager removeDelegate:self];
[[NSNotificationCenter defaultCenter] postNotificationName:@"callControllerClose" object:nil];
```
1.注销callManager的代理 2.通知消息中心,即时对话已经完成了
如果没有第二句,那么你的程序就只能进行一次即时通讯.
并且要在CallViewController将要返回的控制器中加上函数,注册当前控制器为callManager的代理.
```
- (void)callControllerClose:(NSNotification *)notification
{
[[EaseMob sharedInstance].callManager addDelegate:self delegateQueue:nil];
}
```
如果开发过程中遇到问题,不妨看看是不是两个主要的代理没有设定好:
[EaseMob sharedInstance].chatManager ; //这是会话管理者,获取该对象后, 可以做登录、聊天、加好友等操作
[EaseMob sharedInstance].callManager ;//这是即时通讯(语音聊天和视频聊天)的管理者.
demo可以在:https://github.com/ouzhenxuan/huanxinDemo 下载
使用前,请到http://www.easemob.com/downloads 把iOS版的SDK下载到工程中,即可使用
简化版的demo:https://github.com/ouzhenxuan/shipinchat
如果感觉对你有帮助,请点个star.最近十分需要.你的star是我工作的支持. 收起阅读 »
火车票余票的查询+上拉加载,下拉刷新
利用json解析网络接口,将解析出来的数据用视图显示出来,这里下拉刷新与上拉加载可以运用到多个地方,解决了数据一下子不能展示更多的缺陷
下面知识部分代码,其他代码已传到附件了
#import "AppDelegate.h"收起阅读 »
#import "AppViewController.h"
@interface AppDelegate ()
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
AppViewController *tt = [[AppViewController alloc]init];
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.backgroundColor = [UIColor whiteColor];
self.window.rootViewController = tt;
[self.window makeKeyAndVisible];
return YES;
}
#import "AppViewController.h"
#import "CarViewController.h"
#import "AppDelegate.h"
@interface AppViewController ()
@end
@implementation AppViewController
- (void)viewDidLoad {
[super viewDidLoad];
CarViewController *tt1 = [[CarViewController alloc]init];
UINavigationController *nar = [[UINavigationController alloc]initWithRootViewController:tt1];
UIApplication *app = [UIApplication sharedApplication];
AppDelegate *app2 = app.delegate;
app2.window.rootViewController = nar;
}
#import "CarViewController.h"
#import "AppDelegate.h"
#import "CalendarHomeViewController.h"
#import "CalendarViewController.h"
#import "ChaViewController.h"
//CalendarMonthHeaderView.m
#import "Color.h"
#define WIDTH [[UIScreen mainScreen]bounds].size.width
#define HEIGHT [[UIScreen mainScreen]bounds].size.height
#define H [[UIScreen mainScreen]bounds].size.height/100
#define W [[UIScreen mainScreen]bounds].size.width/100
@interface CarViewController ()
{
UITextField *goText;
UITextField *toText;
CalendarHomeViewController *chvc;
UILabel *dateLable1;
NSString *strDate;
NSString *strWeek;
id obj;
//NSMutableString *isStr;
}
@end
@implementation CarViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
// self.navigationController.navigationBar.backgroundColor = COLOR_THEME;
self.navigationController.navigationBar.barTintColor = COLOR_THEME;
self.navigationItem.title = @"车票查询";
self.view.backgroundColor = [UIColor colorWithRed:96.0/255 green:105.0/255 blue:144.0/255 alpha:0.31];
//出发点
goText = [[UITextField alloc]initWithFrame:CGRectMake(8*W, 20*H, 26.7*W, 7.5*H)];
goText.backgroundColor = [UIColor groupTableViewBackgroundColor];
goText.placeholder = @"出发地";
goText.font = [UIFont systemFontOfSize:24];
goText.textAlignment = NSTextAlignmentCenter;
goText.layer.cornerRadius = 10.0;
goText.delegate = self;
toText = [[UITextField alloc]initWithFrame:CGRectMake(66*W, 20*H, 26.7*W, 7.5*H)];
toText.backgroundColor = [UIColor groupTableViewBackgroundColor];
toText.placeholder = @"目的地";
toText.textAlignment = NSTextAlignmentCenter;
toText.font = [UIFont systemFontOfSize:24];
toText.delegate = self;
toText.layer.cornerRadius = 10.0;
[self.view addSubview:goText];
[self.view addSubview:toText];
//箭头
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.frame = CGRectMake(40*W,21*H, 19*W, 5*H);
[button setImage:[UIImage imageNamed:@"1"] forState:UIControlStateNormal];
[button addTarget:self action:@selector(change) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];
//选择日期
UIButton *but2 = [[UIButton alloc]initWithFrame:CGRectMake(0, 36*H, self.view.frame.size.width, 50)];
but2.backgroundColor = COLOR_THEME;
but2.titleLabel.textColor = [UIColor whiteColor];
but2.tag = 2;
//[but2 setTitle:@"选择日期" forState:UIControlStateNormal];
[but2 addTarget:self action:@selector(click:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:but2];
UILabel *dateLable = [[UILabel alloc]initWithFrame:CGRectMake( 8*W, 2*H, 22*W, 4*H)];
dateLable.text = @"出发日期";
dateLable1 = [[UILabel alloc]initWithFrame:CGRectMake( 40*W, 2*H, 60*W, 4*H)];
[dateLable1 setTextColor:[UIColor whiteColor]];
[dateLable setTextColor:[UIColor whiteColor]];
[but2 addSubview:dateLable];
[but2 addSubview:dateLable1];
//NSString *s = strDate;
//查询按钮
UIButton *but = [[UIButton alloc]initWithFrame:CGRectMake(37*W, 60*H, 100, 50)];
but.backgroundColor = COLOR_THEME;
but.titleLabel.textColor = [UIColor whiteColor];
[but setTitle:@"查询" forState:UIControlStateNormal];
[but addTarget:self action:@selector(cha) forControlEvents:UIControlEventTouchUpInside];
[but.layer setCornerRadius:10.0];
[self.view addSubview:but];
}
-(BOOL)textFieldShouldReturn:(UITextField *)textField{
[textField resignFirstResponder];
return YES;
}
-(void)change{
NSString *st1 = goText.text;
NSString *st2 = toText.text;
goText.text = st2;
toText.text = st1;
}
-(void)cha{
ChaViewController *cha = [[ChaViewController alloc]init];
cha.goStr = goText.text;
cha.toStr = toText.text;
cha.strDate1 = strDate;
cha.strWeek1 = strWeek;
[self presentViewController:cha animated:YES completion:nil];
}
-(void)click:(UIButton *)but
{
if (!chvc) {
chvc = [[CalendarHomeViewController alloc]init];
chvc.calendartitle = @"飞机";
[chvc setAirPlaneToDay:365*3 ToDateforString:nil];//飞机初始化方法
}
chvc.calendarblock = ^(CalendarDayModel *model){
NSLog(@"\n---------------------------");
NSLog(@"1星期 %@",[model getWeek]);
NSLog(@"2字符串 %@",[model toString]);
NSLog(@"3节日 %@",model.holiday);
strDate = [model toString];
strWeek = [model getWeek];
if (model.holiday) {
//[but setTitle:[NSString stringWithFormat:@"%@ %@ %@",[model toString],[model getWeek],model.holiday] forState:UIControlStateNormal];
dateLable1.text = [NSString stringWithFormat:@"%@ %@ %@",[model toString],[model getWeek],model.holiday];
}else{
//[but setTitle:[NSString stringWithFormat:@"%@ %@",[model toString],[model getWeek]] forState:UIControlStateNormal];
dateLable1.text = [NSString stringWithFormat:@"%@ %@",[model toString],[model getWeek]];
}
};
[self.navigationController pushViewController:chvc animated:YES];
}
入手环信,代码块知识点用的多,发帖回顾下
#import
int c = 0;
int main(int argc, const char * argv[]) {
@autoreleasepool {
//block声明格式:返回值类型 (^blaock名字) (形参列表)
int (^myFun)();
//block实现: block 名字 = ^(形参列表){};
myFun = ^(){
NSLog(@"这是一个代码块");
return 1;
};
//调用:block名称(实参列表),有返回值的block,可以用一个变量进行接收
int a = myFun();//空括号不能少!!
NSLog(@"%d",a);
//有返回值,有形参,声明和实现放一起
int (^myBlock)(int a, int b) = ^(int a, int b){
return 1+b;
};
//调用
int sum = myBlock(10,20);
NSLog(@"%d",sum);
//返回值类型是字符串 NSString *(^名字)(形参列表)
NSString *(^myBlock1) (NSString *s) = ^(NSString *s){
NSLog(@"字符串:%@",s);
return s;
};
myBlock1(@"123");
//有一个局部变量,要在block进行值的改变
__block int b = 0;
void (^myBlock2)() = ^(){
b++;
};
//有一个全局变量,在block进行值的改变
void (^myBlock3)() = ^(){
c++;
};
}
return 0;
}
block 作为函数参数
block作为形参使用,在函数中使用block方法运算数据
#import
//block作为函数的参数
//函数返回值类型 函数名(block的声明格式)
void fun(int (^bsasa)(int a,int b)){
int sum = bsasa(3,2);
NSLog(@"%d",sum);
}
void fun1(NSString *(^myBlock)(NSString *s),NSString *s1){
NSLog(@"---%@",myBlock(s1));
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
//当一个block作为函数的参数是,其返回值类型、形参个数及类型要与函数形参格式保持一致
int (^myBlock) (int c, int d) = ^(int a, int b){
NSLog(@"--%d,%d",a,b);
return a-b;
};
//函数形参是block,调用时,直接传block的名字就行
fun(myBlock);
//调用的另一种方式:内联
//内联block格式:^返回值类型 (形参列表){}
fun(^int(int a, int b) {
return a*b;
});
fun1(^NSString *(NSString *s) {
return s;
}, @"123");
}
return 0;
}
block作为方法的参数
Person.h
#import
//使用typedef声明block:typedef 返回值类型 (^名字)(形参列表)
typedef int (^myblockType)(int a);
@interface Person : NSObject
//block作为方法的参数:(返回值类型 (^)(形参列表))参数名(blcok名字)
-(void)myblock:(int (^)(int a))block;
-(void)sengStr:(NSString *)name andblock:(NSString *(^)(NSString *))myblock1;
-(void)useblockType:(myblockType)sss;
-(void)myname:(NSString *)name;
@end
Person.m
#import "Person.h"
@implementation Person
-(void)myblock:(int (^)(int))block{
int a = block(10);
NSLog(@"%d",a);
}
-(void)sengStr:(NSString *)name andblock:(NSString *(^)(NSString *))myblock1{
myblock1(name);
}
-(void)useblockType:(myblockType)sss{
int res = sss(10);
NSLog(@"---%d",res);
}
-(void)myname:(NSString *)name{
NSString *str = [name stringByAppendingString:@"+-"];
NSLog(@"%@",str);
}
@end
main.m
#import
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc] init];
[p sengStr:@"这儿" andblock:^NSString *(NSString *s) {
NSLog(@"我在:%@",s);
return s;
}];//内联形式直接实现block、
[p useblockType:^int(int a) {//typedef 声明
return a;
}];
//传入一个名字,然后进行拼接,名字--;
[p myname:@"aa"];//
[p sengStr:@"aa" andblock:^NSString *(NSString *s) {//
return [s stringByAppendingString:@"--"];
}];
}
return 0;
}
收起阅读 »
基于react native 和 环信的实时通话的免费“店话” 项目 开源了
基于react native 和 环信的实时通话的免费“店话” 项目开源啦!
用“店话”搜索想要找的店铺电话,也可以上传自己的店铺信息和在线的客户免费语音通话,语音通话的流量仅仅需要3-5kb/s啊,还不快快下载!
扫描二维码下载体验(apk在附件已上传,可以直接安装哦)
源代码:https://github.com/vasth/dianhua(如果喜欢可以点下方赞赏按钮哦☺)
APP运行效果图展示:
点击下载APK ↓
收起阅读 »
看到很多人都急需一个ios简单集成 的demo ,这里就献丑上传一个
项目已经上传到附件,感兴趣的可以下载看看,有不明白的可以在下面跟帖
附件查看不到demo 的可以通过连接下载,下载地址:点击下载 收起阅读 »
iOS通过终端进行导入第三方库
先确认电脑中是否安装有Ruby(ruby是安装cocoapods的前提)。
打开终端: 打开launchpad –其它-终端。
打开后 运行代码“rvm list”(查询已经安装的Ruby);如果先终端列出了list得话说明Mac中已经安装了ruby 就可以直接安装cocoapods了。
记住到这的时候如出现在command not found:请阅读下面ruby安装文档,如果你的终端运行代码“rvm list”出现
rvm rubies
# No rvm rubies installed yet. Try 'rvm help install'.
scsysdeMac-mini-6:~ scsys$ :请跳过ruby安装
接下来就是你安装cocoapods,好了这是前期的工作,后面就是按照cocoapods文档中方法走吧
收起阅读 »
Chatbox - 轻量级jQuery聊天窗插件
之前做WebIM的时候苦于没有现成的聊天窗组件,所以自己封装了一个jQuery插件。该插件仅仅只是一个前端组件,不涉及后端通讯。后端请自行实现,比如可以使用环信的IM云,后期我有空可以实现一个DEMO分享出来。
1. 轻量级动画特效以及友好的界面
2. 支持多窗口和实例
3. 完善的回调函数以实现自定义功能
4. 多种调用方式
5. 良好的封装以及扩展性
6. 每个聊天窗对象实例以data属性的形式附加在聊天窗DOM对象上(如果你想获得某个特定插件的实例,可以直接从页面元素中获取:$('{boxId}').data('chatbox'))
项目地址:
http://haozki.me/Chatbox/
以上地址包含完整的API文档和Live Demo。可以直接看效果。
开源中国收录地址:
http://www.oschina.net/p/Chatbox
下载地址:
https://github.com/haozki/Chatbox/archive/master.zip
收起阅读 »
score.js - jQuery星级评分插件
一年前写的jQuery星级评分插件,现在分享出来。代码不过200来行,有完善API文档说明。
1.完善的API
2.丰富的回调接口,实现灵活的功能控制
3.轻量级代码,使用更高效
4.支持自定义图标
5.支持Font-awsome扩展
项目地址:
http://haozki.me/score.js/
以上地址包含API文档和Live Demo。可以直接看效果。
开源中国收录地址:
http://www.oschina.net/p/score-js
下载地址:
https://github.com/haozki/score.js/archive/master.zip
收起阅读 »
使用环信提供的EaseUI库集成客服demo
使用环信提供的 EaseUI 库集成客服demo,方便开发者参考集成环信的客服功能;项目有详细的注释,适合新手以及老鸟参考研究, 此demo使用最新的环信提供的 EaseUI 库集成环信的客服聊天
源代码:https://github.com/lzan13/EaseUICustomer
运行截图如下:
关于EaseUI库
EaseUI 库可以去环信官网下载界面下载sdk,里边包含有EaseUI库 官网下载sdk
或者去环信的github上去下载:官网下载sdk
特点
使用了Android Material Design 样式开发,将Android新设计与环信的EaseUI结合,
需要注意的地方(重要)
使用时需要把配置文件里的appkey改成自己的,以及开屏页SplashActivity类里默认数据改成自己的 使用时需要把配置文件里的appkey改成自己的,以及开屏页SplashActivity类里默认数据改成自己的 使用时需要把配置文件里的appkey改成自己的,以及开屏页SplashActivity类里默认数据改成自己的 重要的事情说三遍 在使用过程中有什么疑问可以去环信官方社区 `imgeek.org` 去发帖[imgeek 社区][imgeek]
实现的功能
全局消息的监听,在DemoHelper类实现,搜索 onEvent 方法
通知栏消息提醒,这里使用 EaseUI 定义封装好的通知,这里只是稍微设置了下,单条消息会显示消息内容,多条消息会显示消息条数,以及小图标的设置
用户轨迹图文消息混排
满意度消息的收发
通知栏提醒
首先说下EaseUI重构客服demo的环境以及工具版本:
AndroidStudio version:1.5.0 SDKTools version:24.4.x Build-tools version:23.0.2 Compile SDK version:API 22(5.1) Minimum SDK version:API 15(4.0.3) Gradle version:2.4 jdk version:1.7
AndroidStudio等工具地址:
Android 官方下载 Android官网
国内网友收集 AndroidDevTools
Gradle(AndroidStudio有时自动下载不成功,所以要自己下载) gradle v2.4下载 收起阅读 »
信息的分类,在做购物类的app很实用这个模块,将具有相同的特征信息进行归纳,类似于好友列表的原理
1.建一个你需要的viewcontroller,demo上是直接用ViewController,你需要导入MultilevelMenu文件
2.接下来,就是你对数据的进行操作,最好是将你的数据通过字典,数组像结合进行整合,第二种方法就是通过plist文件进行操作,注意plist文件的操作,个人觉得plist高大上一点。
3.就是将你的数据进行一层一层的解析,获取不同层次的方法:rightMeun * meun=[[rightMeun alloc] init]; meun.meunName = arr;//第一层获取 //酒水的分类 //日常用品分类//食品分类//水果 //饮料在这里我是简单用字典与数组进行数据处理:然后一层一层解析,具体的方法和怎么使用都在下面的demo中。
4.在你使用之前,一定要将MultilevelMenu文件和Vendor文件加到工程中去,如果想改变cell的大小,菜单的颜色呀,你都可以在MultilevelMenu.h找到属性。效果图见附件和demo,工程中更加详细
收起阅读 »
一个基于公司招聘的小demo
好友列表展开、收起、点击修改、原理实现
iOS集成支付宝
Ios上架流程
1、首先创建钥匙串配置文件,进入钥匙串
2、选择从证书颁发机构请求证书:
3、填写信息
4、保存配置信息
5、进入苹果开发者网页:https://developer.apple.com/
6、点击member center,如下图:
7、输入账号,进入开发者主页,并选择证书选项:
8、进入证书页,首先创建证书
9、选择production,并点击+
10、选择需要创建的证书类型,在这里我们选择production中的appstore and ad hoc,如果有推送功能,则重复以上步骤,选择第二个:
11、点击continu,直到进入Cenerate界面,点击choose file,上传之前创建好的
12、选择钥匙串授权文件,点击继续:
13、生成之后,我们会跳转到Download界面,点击界面中的“Download”下载下来,双击我们生成的.cer文件,一定要双击,双击后它会默认安装到钥匙串中。(推送证书重复之前步骤)
14、之后点击左边目录中的“Identifiers”下的“App IDs”,点击+:
15、填写创建证书所需要的信息(如果需要推送则选择第一个,固定标识):
16、点击continue,然后submit,如果有推送功能,则创建推送证书,创建推送证书过程中,会有个选择appids的选项,选择对应的appids:
17、注册手持设备,点击左边目录中的“Devices”,同样点击右上方的“十”号,进行添加。
18、输入设备名称以及udid,我们可以通过iTunes获取设备的udid:
19、点击continue,然后点击注册,完成设备的条件
20、创建Provisioning Profiles,也就是手机上使用的证书,点击最左边目录栏,选择“Provisioning Profiles”目录下的“All”,同样点击右上方的“十”号进入证书添加界面
21、选择APP Store,点击continue,选择我们之前创建的App ID,点击continue:
22、在下面选项里面选择我们之前生成的授权发布证书的名字,点击continue:
23、接下来输入证书名字,改名字将会在xcode中显示,然后点击Create来创建证书,最后下载,双击进行安装。
二、xcode打包发布的ipa
1、修改项目中TARGETS中的Bundle identifier,与我们之前创建的App ID中的标识保持一致:
2、分别设置TARGETS和PROJECT里面Build Settings中对应的Code Signing属性:
3、选择IOS Device,然后点击xcode菜单中的Product,选择Archive,进行打包:
4、打包完成后进入Archive界面,我们可以直接submit到appStore,也可以选择导出到本地,通过上传工具再上传:
5、点export,选择打包ipa的用途,我们选第一个,发布到appstore:
6、点击next,进入开发者账号的选择,如果之前设定好,会直接显示,反之则会提示输入账号和密码:
7、点击choose,会出现对应的应用信息,然后点击export,取个名字,直接保存即可
三、iTunes connect中创建app
1、进入苹果开发者网页:https://developer.apple.com/,进入member center,选择iTunes Connect:
3、点击+,选择新建IOS App,然后在弹出框填写自己的app信息:
4、点击创建,进入app详情页,上传自己的截图、app的展示图、app的描述等。
5:用application loader将ipa文件上传到iTunes connect,随后在app详情页的build处点击+,选择自己上传的ipa文件即可。
收起阅读 »
iOS 通过 REST API 接口上传文件(图片)
官方文档中提到了通过通过 REST API 接口上传文件的问题。这里我简单介绍一下通过REST API 接口上传图片的问题。
我们知道由于iOS无法通过html表单来上传图片,因此想要上传图片,必须实现http请求,而不能像其他语言那样通过html表单的post就能上传。这个文档中貌似没有说清楚
上传图片的http post请求的格式是这样的:
第一行是指定了http post请求的编码方式为multipart/form-data(上传文件必须用这个)。
boundary=AaB03x说明了AaB03x为分界线。比如 --AaB03x 就是一个分界线的意思
content-disposition: form-data; name="field1"
Hello Boris!
这句话声明了请求中的一个字段的名称,如field1 以及字段的值,如Hello Boris!
这里类似form表单中的
中间的空行是必须的。一般是文件的一些属性
不同的字段之间用分界线分开,分界线需要单独一行,如 --AaB03x--
分界线的下一行,是下一个字段
content-disposition: form-data; name="pic"; filename="boris.png"
Content-Type: image/png
... contents of boris.png ...
--AaB03x--
这里声明了变量pic,也就是我们要传的文件,上传文件的时候需要在后边指定filename:filename="boris.png"
并且需要在下一行指定文件的格式:Content-Type: image/png
... contents of boris.png ... 这里是boris.png的二进制内容(也就是data类型),如 <89504e47 0d0a1a0a 0000000d 49484452 000000b4 000000b4 08020000 00b2af91 65000020 00494441 5478012c dd79b724 6b7616f6 8c888c88 8c9c8733 55ddb1d5 6a0db486 06218401 ......
在http post请求的结尾,需要有一个分界线,但是是前后都有--的:--AaB03x--
以上的这些格式,是http的规范,每个空行,空格都是必须的。
下边是iOS的实现代码
/*---------------------------------上传图片-------------------------------------------*/
//分界线的标识符
NSString *TWITTERFON_FORM_BOUNDARY = @"AaB03x";
//上传的接口
NSURL *url = [NSURL URLWithString:@"https://a1.easemob.com/环信ID/APP名字/chatfiles"];
//要上传的图片,得到data
NSData *data = UIImagePNGRepresentation([UIImage imageNamed:@"defain"]);//这是我的本地图片
//声明file字段,文件名为defain.png, 声明上传文件的格式
NSString* strBodyBegin = [NSString stringWithFormat:@"--%@\nContent-Disposition: form-data; name=\"%@\"; filename=\"%@\"\nContent-Type: %@\n\n", TWITTERFON_FORM_BOUNDARY, @"file", @"defain.png", @"image/png"];
//声明结束符:--AaB03x--
NSString* strBodyEnd = [NSString stringWithFormat:@"\n--%@--",TWITTERFON_FORM_BOUNDARY];
NSMutableData *httpBody = [NSMutableData data];
//表单开始
[httpBody appendData:[strBodyBegin dataUsingEncoding:NSUTF8StringEncoding]];
//填入数据
[httpBody appendData:data];
//表单结束
[httpBody appendData:[strBodyEnd dataUsingEncoding:NSUTF8StringEncoding]];
/*
注意:这里我没有用下面的字段(它主要用来描述text格式的文本信息)
content-disposition: form-data; name="field1"
Hello Boris!
*/
NSMutableURLRequest* httpPutRequest = [[NSMutableURLRequest alloc] init];
[httpPutRequest setURL:url];
//设置请求方法
[httpPutRequest setHTTPMethod:@"POST"];
[httpPutRequest setTimeoutInterval: 60000];
//设置HTTPHeader中token的值
[httpPutRequest setValue:@"Bearer **************************************************" forHTTPHeaderField:@"Authorization"];
//设置访问权限
[httpPutRequest setValue:@"true" forHTTPHeaderField:@"restrict-access"];
//设置Content-Length
[httpPutRequest setValue:[NSString stringWithFormat:@"%@", @(httpBody.length)] forHTTPHeaderField:@"Content-Length"];
//设置HTTPHeader中Content-Type的值
[httpPutRequest setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@",TWITTERFON_FORM_BOUNDARY] forHTTPHeaderField:@"Content-Type"];
//将表单添加到请求体
httpPutRequest.HTTPBody = httpBody;
//异步请求
[NSURLConnection sendAsynchronousRequest:httpPutRequest queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if (connectionError == nil) {
id obj = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
NSLog(@"服务器返回信息%@",obj);
}
}];
//服务器返回信息
2016-01-03 14:30:20.272 Fuhome-App[1356:154571] 服务器返回信息{
action = post;
application = "************";
applicationName = **********;
duration = 81;
entities = (
{
"share-secret" = "*********";
type = chatfile;
uuid = "***********";
}
);
organization = ******;
path = "/chatfiles";
timestamp = 1451802620175;
uri = "https://a1.easemob.com/******/*******/chatfiles";
}
收起阅读 »
关于demo3.0表情,文字混合输入问题
#pragma mark - DXFaceDelegate解决也很简单,我再判断表情时,同时使用了下面的方法。这个方法我加在了NSString的扩展中。
- (void)selectedFacialView:(NSString *)str isDelete:(BOOL)isDelete
{
NSString *chatText = self.inputTextView.text;
NSMutableAttributedString *attr = [[NSMutableAttributedString alloc] initWithAttributedString:self.inputTextView.attributedText];
if (!isDelete && str.length > 0) {
NSRange range = [self.inputTextView selectedRange];
[attr insertAttributedString:[EaseEmotionEscape attStringFromTextForInputView:str] atIndex:range.location];
self.inputTextView.text = @"";
self.inputTextView.attributedText = attr;
// self.inputTextView.text = [NSString stringWithFormat:@"%@%@",chatText,str];
}
else {
if (chatText.length > 0) {
NSInteger length = 1;
if (chatText.length >= 2) {
NSString *subStr = [chatText substringFromIndex:chatText.length-2];
if ([_defaultEmoji containsObject:subStr]) {
length = 2;
}
}
self.inputTextView.attributedText = [self backspaceText:attr length:length];
}
}
[self textViewDidChange:self.inputTextView];
}
+ (BOOL)stringContainsEmoji:(NSString *)string注意:下面的代码为更新版本,上面的代码还是太久远了,下面的也是网上找到的,感觉还比较新。
{
__block BOOL returnValue = NO;
[string enumerateSubstringsInRange:NSMakeRange(0, [string length])
options:NSStringEnumerationByComposedCharacterSequences
usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) {
const unichar hs = [substring characterAtIndex:0];
if (0xd800 <= hs && hs <= 0xdbff) {
if (substring.length > 1) {
const unichar ls = [substring characterAtIndex:1];
const int uc = ((hs - 0xd800) * 0x400) + (ls - 0xdc00) + 0x10000;
if (0x1d000 <= uc && uc <= 0x1f77f) {
returnValue = YES;
}
}
} else if (substring.length > 1) {
const unichar ls = [substring characterAtIndex:1];
if (ls == 0x20e3) {
returnValue = YES;
}
} else {
if (0x2100 <= hs && hs <= 0x27ff) {
returnValue = YES;
} else if (0x2B05 <= hs && hs <= 0x2b07) {
returnValue = YES;
} else if (0x2934 <= hs && hs <= 0x2935) {
returnValue = YES;
} else if (0x3297 <= hs && hs <= 0x3299) {
returnValue = YES;
} else if (hs == 0xa9 || hs == 0xae || hs == 0x303d || hs == 0x3030 || hs == 0x2b55 || hs == 0x2b1c || hs == 0x2b1b || hs == 0x2b50) {
returnValue = YES;
}
}
}];
return returnValue;
}
+ (void)load {收起阅读 »
VariationSelectors = [NSCharacterSet characterSetWithRange:NSMakeRange(0xFE00, 16)];
}
- (BOOL)isEmoji {
if ([self rangeOfCharacterFromSet: VariationSelectors].location != NSNotFound) {
return YES;
}
const unichar high = [self characterAtIndex: 0];
// Surrogate pair (U+1D000-1F9FF)
if (0xD800 <= high && high <= 0xDBFF) {
const unichar low = [self characterAtIndex: 1];
const int codepoint = ((high - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000;
return (0x1D000 <= codepoint && codepoint <= 0x1F9FF);
// Not surrogate pair (U+2100-27BF)
} else {
return (0x2100 <= high && high <= 0x27BF);
}
}
如何通过REST接口获取token
NSURL *url = [NSURL URLWithString:@"https://a1.easemob.com/企业ID/应用app名称/token"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
NSDictionary *body = @{@"grant_type":@"client_credentials",@"client_id": @"填写Client Id",@"client_secret":@"填写Client Secret"};
[request setHTTPBody:[body JSONData]];//JSONData为一个库的方法,该库在下面附件中
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
//连接,异步
[NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if (connectionError == nil) {
id obj = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
NSLog(@"服务器返回信息%@",obj);
NSString* token = [obj objectForKey:@"access_token"];
if(token)
{
// access_token 保存一下(可以把token保存到本地)
[[NSUserDefaults standardUserDefaults] setObject:token forKey:@"access_token"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
}
NSLog(@"错误信息%@",connectionError);
}]; 收起阅读 »
【有奖征集】环信开发文档Feedback
我们专门开设一个帖子征集开发文档的不足之处。请大家不吝指正,毫无保留的提出意见和建议。
开发文档的地址是:http://docs.easemob.com
不管是界面,内容,还是结构。任何跟文档站相关的统统可以吐槽。我们将统一收集整理,并不断改进我们的产品。
当然,这一切也许还会有意想不到的惊喜哦~ 我们将对有价值的回复提供现金赞赏!
这种主动求吐槽,还派发奖赏的玩法好像也就我们一家了吧~
GO! GO! GO! 收起阅读 »
推荐一款编程字体,让代码看着更美
在IDE(集成开发环境)中进行程序开发时,字体的选择看上去并不是那么的重要。但是,一款优雅的字体在整体视觉上能让代码看上去更美,营造一个积极的气氛,眼睛不再那么累,心情就会显得不错,那么你的开发效率自然也会有所提升。
今天,就给大家推荐一款字体,一款美美的字体,也是个人长久使用的字体。在这之前,先简单介绍一下Eclipse的默认字体 —— Consolas。既然被Eclipse选为默认字体,Consolas必然已经得到了广大编程人员的认可,优势之处无需多言,但美中不足的是,Consolas显示的中文比英文要小,如图所示:
通常,为了便于操作,我们总是希望在PC一屏中显示更多的代码,于是便采取缩小字号的方式。但是,在Eclipse默认字体下,中文汉字、中文符号等就显得特别小以至于很难看清,眼睛非常之累。
相比之下,本文中的主角要出现了 —— YaHei.Consolas.1.12.ttf 。这是一款集成了微软雅黑的Consolas,非常漂亮,具体效果,有图有真相:
是不是有一种焕然一新的感觉呢?是不是显得特别大气呢?接下来就看一下在Eclipse中的安装教程吧。
首选下载 YaHei.Consolas.1.12.ttf 字体并复制到电脑控制面板中的字体目录下,Google Code 官方下载地址(翻墙):
https://code.google.com/p/uigroupcode/downloads/detail?name=YaHei.Consolas.1.12.zip&can=2&q=
CSDN资源下载地址:
http://download.csdn.net/detail/wenbitianxiafeng/9384674
在Eclipse中,选择[Windows] --> [Preference] --> [General] --> [appearance] --> [Colors and Fonts] --> [Basic] --> [Text Font],进入字体编辑窗口:
点击 [Edit],可以看到Eclipse默认的是Consolas字体:
在字体列表中选中 YaHei.Consolas.1.12.ttf ,确定并应用即可。
然后就可以尽情的享受这款字体带来的愉悦吧。
PS:如果大家有更好的编程字体,欢迎评论推荐~ 收起阅读 »