今天我们来聊聊如何带团队。
在移动互联网飞速发展的今天,许多公司开始组建移动开发团队,许多技术人员开始学习和开发移动App,许多管理者也从其它领域转向移动。那么带领一只移动开发的团队和传统的团队(例如Web)有什么不一样,会遇到什么问题,有哪些需要特别注意的地方呢?这篇文章将会简单讲讲我的经验和体会。首先我们先谈谈移动产品的特点。
移动产品的特点
上图是我在微博上曾经看到过的一个功能,代表了移动App的一个使用场景。这是百度地图App的HUD(抬头显示)功能,在夜间利用手机屏幕把图像投影在汽车挡风玻璃上,让司机在汽车风挡前方就能实时看到导航信息,极大的增强了实用性和安全性。
我第一次看到这个创意时感到非常impressive,因为我完全没想到手机App可以这样做。百度地图App的这个idea很好的体现了移动产品灵活、创新性强的特点,也体现了移动App应用场景的丰富性。
我们的世界正在朝着智能移动终端、物联网的世界发展和前进,这极大的颠覆、扩展了我们之前对于互联网的认知,而在这个巨大的变化过程中,移动产品体现出了如下的特点:创新、活力、多变。做移动产品就是改变思维、拥抱变化的一个过程。
哪些创新给移动产品带来活力?
手机上有摄像头、传感器、话筒、触摸屏这些特性。我们来看看移动产品上有哪些创新给自身带来了活力。
Flipboard:极大的改善了我们移动阅读的体验,在排版和交互方式上有巨大创新。
Clear:这个Todo List软件完全弃用了按钮元素,所有操作都是通过多点触摸屏幕上的手势操作来完成。
Word Lens:通过摄像头翻译文字。只要将手机摄像头对准需要翻译的文字,几乎就在你看清屏幕显示内容的同时,它们就已经被翻译完毕,并且仍然完美的融入在原有环境当中,是增强现实的代表应用之一,这里是演示视频。
Real Racing:紧跟苹果新技术的步伐,对例如陀螺仪、多点触摸、Game Center、AirPlay等特性总是第一时间支持。
Talkbox:虽然被微信打败,但是它最先提出的语音对讲概念在当时震惊业界,也影响了许多应用。
微信:微信的摇一摇不仅非常实用,而且让产品充满亮点。
切水果:火爆一时的游戏,非常好的利用了手机屏幕的多点触摸功能。
最后是小众旅行应用Peaks.ar,通过摄像头拍摄山峰,就能给出山峰的具体信息,例如附近每座山峰的名称、距离多远、海拔多高,对旅行者非常有用,是摄像头和现实结合的很好创新应用。
Web与移动互联网产品的区别
从移动产品的特点我们来看看Web与移动移动互联网产品的区别在哪里。
- 业务模式
- Web vs Web + Client。互联网本质是服务,传统Web是内容为王,移动互联网是Web加客户端软件,一方面有Web的特性即可连接性,另外一方面有客户端的特点:迭代时间长、召回成本大。
- 思维模式
- 稳定 vs 灵活。相对于Web技术和产品的成熟和稳定,移动App更灵活,形态各异,各种新产品、新思路层出不穷,相比Web开发,移动产品需要技术人员更有产品sense,思路更为活跃,了解移动产品、关注移动技术的发展和方向,例如iOS7的发布,小米2s手机的发布等等。
- 产品要求
- 质量、性能、适配、流量、隐私。移动App对于质量、性能、适配问题的关注都是众所周知的,而更加重要的是用户对于移动互联网产品,在流量和隐私方面有着特殊的关注,许多用户不仅关注应用使用了多少流量,还经常会关注手机App所涉及的系统权限并加以限制和反馈。
- 开发模式
- RD + FE vs M-RD。Web开发一般包括RD和FE两个角色,而Mobile-RD需要掌握从界面、逻辑到功能层面的技术,从上到下的进行开发,采用分功能模块、纵向开发的方式效率更高。
- 重MRD vs 重交互。对于Web开发来说MRD非常重要,但是在移动App来说,更加强调交互,在交互方案确定后,只需要简单的MRD就可以进入开发阶段。
如何带移动团队
- 质量是红线
质量是客户端产品最重要的要素。和产品方向是产品的key point一样,质量是移动端研发人员的生命线,绝不能掉以轻心,一定要给团队反复灌输,还需要有强有力的制度和流程来保证。
- 如何培养打硬仗的能力?
作为管理者,首先要搭建靠谱的团队,这是通过严格招聘,包括结构化面试、开放性面试、技能与素质面试等过程,筛选出技术强的人加负责任的人,然后对他们提出要求和目标,通过一张一弛的项目迭代,不断锻炼和训练得到的。
- 知识的传承很重要
对于技术团队来说,知识的传承很重要。因为以前犯过的错误、遇到的问题,新员工还有可能重复遇到或出现,这个时候要通过知识的传承体系来解决,例如建立wiki、组织培训、定期宣讲的方式。
- 提升思维模式
因为移动互联网很新、发展非常迅速,所以我们很多时候要颠覆之前的思维模式来考虑问题(例如我之前的一篇文章《谈谈移动App的思维误区》)。这个时候就需要帮助大家提升思维模式。
福特造车的故事很多人都听过。100多年前,福特公司的创始人亨利·福特先生到处跑去问客户:“您需要一个什么样的更好的交通工具?”几乎所有人的答案都是:“我要一匹更快的马”。很多人听到这个答案,于是立马跑到马场去选马配种,以满足客户的需求。但是福特先生却没有立马往马场跑,而是接着往下问。
福特:“你为什么需要一匹更快的马?”
客户:“因为可以跑得更快!”
福特:“你为什么需要跑得更快?”
客户:“因为这样我就可以更早的到达目的地。”
福特:“所以,你要一匹更快的马的真正用意是?”
客户:“用更短的时间、更快地到达目的地!”
于是,福特并没有往马场跑去,而是选择了制造汽车去满足客户的需求。
你发现一个问题,我们自己的产品有,另外两个竞品也有,你应该如何看待这个问题?你的第一反应会不会是,这其实不是我们的问题?
有一段重要功能需要你负责从一个App挪到另一个App,代码非常相似,你觉得需要详细了解代码逻辑、详细自测吗?
思维模式决定了成就。不要先验假设,就是不能预先给个判断、下个结论,做结论不能想当然。我们现在很多研发人员经验越来越丰富,很多时候经验会帮助我们,不过很多时候也会阻碍我们,阻止我们发现真相,阻碍你成长为更NB的人。例如小明的爸爸有三个儿子的脑筋急转弯题目,以及福特的故事等等都是这方面的例子,另外还有很多新的科学发现和发明就是打破思维定势的结果。我们有时需要清空自己的思维定势,才能不那么狭隘和死板,才能让自己提升,才能迈出那一步让自己成长。你自己以后在回答“不可能、做不到、没问题”的时候,需要提醒自己再多想想会不会掩盖了真相,多提醒自己“这里会不会有问题?”
- 心态open,愿意试错
这点也非常重要,在移动互联网产品上,不是每个人都能够准确的知道每个功能所带来的效果,但是不尝试就不会有收获。我们要抱着试错的态度去快速投入,快速产出,快速验证效果,才能够去实现一些意想不到的效果。
- 培养研发人员移动产品的sense
上文提到过,做移动产品,需要每个成员对移动互联网的产品非常了解,一方面能够更好的通过技术去实现功能,另外一方面有助于发现产品上的问题,例如哪儿设计的不合理,哪里的用户体验不好,最后,我期望每个研发人员都有可能提出一些好的idea,一些创新点来帮助应用本身、团队甚至公司做出更有影响力的产品,在移动互联网上,这点其实表现的更加明显,来自研发人员的产品idea多的可以说不计其数。
- 建立团队的梦想和目标
最后一点最重要,要激发研发人员的主动性,主动去问,去推动,提升思维模式,关键点在于建立团队的梦想和目标。有目标和愿景的团队才是战斗力强的团队,个体才是积极向上的个人。没有梦想和目标的团队只是为了完成工作,在层次上要低一个层面。
团队问题的建议
对于我遇到过的许多其它团队的典型问题,我曾经有一些自己的建议。
- 快速建立了一个新团队,招了很多人,团队太大。
- 分析每个人的潜力加能力,由senior的工程师带领参与项目,迅速的形成传帮带的机制(有高阶导师带、安排有挑战性的工作和任务)。
- 建立多条研发线、规范项目流程(例如开展单元测试、集成测试、自动化测试)、安排技术创新或前瞻任务。
- 快速建立团队开发规范和体系,例如code review、知识体系、技术交流会等等。
- 关注质量问题:建立原型并延长测试时间(通过压缩开发时间、提前协调物料的交付时间)、反复强调灌输。
- 在精神层面,建立团队规划和树立愿景,在开发意识和思维模式上帮助团队提高。
- 没有产品sense/不用、不热爱自己的产品
- 以身作则,树立团队愿景,设计一些巧妙的制度或者机制(例如知识竞赛、安排一些任务的方式)、非不得已的时候避免惩罚。
- 逐步通过小的有成就的任务形成正回馈。
- 有员工能力不足或者意愿不足
- 个人自身境况分析
- 换位置、安排适当工作、引入新员工。我之前看过一场东亚四强赛国足对日本的比赛,中国队从1:3追到了3:3,印象非常深刻。当时比分落后时主教练做了一件事,换上了两个人:孙可和张稀哲。他们都是有冲劲的年轻人,有生力量的上场带动了全队,带来了活力,也改变了场上形式。所以要把机会给愿意尝试的人,给缺少机会的人,给积极的人。在局面是死水一潭时,不投石子就解决不了问题。
- 移动App的运营方式太过传统
- 找到合适的人来负责。
- 向好的项目和产品学习。我印象非常深刻的是在2011年春节的时候,微信曾经做过一个运营活动。它告诉用户在新年钟声敲响时,一起来使用摇一摇功能,如果同时有1万个人一起摇动,它就点亮广州塔上一层的灯光,如果同时有几十万人一起要动,那代表广州塔上所有的灯会同时点亮。我觉得这是一个很好的把产品功能和用户传播相结合的运营活动,把用户的行为通过可以看到、一起参与的方式体现出来,很有创意。所以我们要不停的从别人好的运营方式中学习,魔图就是一个很好的从别人身上汲取经验的案例。
最近在学习如何发通知,Notification通知,是Android信息提示的手段之一,下面说说如何发送标准的通知,自定义通知,还有清除所有通知
如图:
点击按钮“发送通知(标准)”,发送标准定义的通知
点击按钮“发送通知(自定义)”,发送自定义的通知
点击按钮“清除所有通知”,清除的通知
废话少说,直接看代码:
activity_main.xml界面代码:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".MainActivity" android:orientation="vertical"> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="发出通知(标准)" android:id="@+id/btn1" /> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="发出通知(自定义)" android:id="@+id/btn2" /> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="清除所有通知" android:id="@+id/btn3" /> </LinearLayout>
my.xml代码:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <ProgressBar android:layout_width="fill_parent" android:layout_height="wrap_content" android:max="100" android:progress="0" android:id="@+id/my_progress" /> </LinearLayout>
MainActivity.java代码:
package com.example.notificationdemo; import java.util.ArrayList; import java.util.Random; import android.app.Activity; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.RemoteViews; public class MainActivity extends Activity { /* * * Notification通知,是Android信息提示的手段之一 * 标准的通知 * 自定义通知 */ //ids用来保存通知的id ArrayList<Integer> ids=new ArrayList(); int index=0; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button btn1=(Button)findViewById(R.id.btn1); Button btn2=(Button)findViewById(R.id.btn2); Button btn3=(Button)findViewById(R.id.btn3); btn1.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { //1.创建通知对象 //参数1,是通知图标,参数2,是通知标题,参数3,通知发出的时间 Notification notification=new Notification(R.drawable.ic_launcher, "这是一个测试通知",System.currentTimeMillis()); //2.设置通知的内容主体 //PendingIntent是一个用于保存状态的意图 //由于通知可以脱离程序(发出之后将程序结束),所以我们需要一个对象来维持通知和程序之间的关系 PendingIntent pending=PendingIntent.getActivity(MainActivity.this, 1, new Intent(MainActivity.this,MainActivity.class), 0); notification.setLatestEventInfo(MainActivity.this, "通知标题", "这条信息是标准通知",pending); //设置通知不可被清除|被点击之后自动清除 notification.flags=Notification.FLAG_NO_CLEAR|Notification.FLAG_AUTO_CANCEL; //3.通过通知管理器将该通知发出 //NotificationManager是系统服务,可用来发送全局通知Notification NotificationManager manager=(NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE); Random rd= new Random(); //这个id表示通知的标识 int id=rd.nextInt(); manager.notify(id, notification); ids.add(id); } }); //自定义通知 btn2.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { final Notification notification=new Notification(R.drawable.ic_launcher, "这是自定义的通知", System.currentTimeMillis()); RemoteViews rv=new RemoteViews(MainActivity.this.getPackageName(), R.layout.my); notification.contentView=rv; notification.contentIntent=PendingIntent.getActivity(MainActivity.this, 1, new Intent(MainActivity.this,MainActivity.class), 0); notification.flags=Notification.FLAG_NO_CLEAR|Notification.FLAG_AUTO_CANCEL; final NotificationManager manager=(NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE); manager.notify(1, notification); //启动一个任务,模拟进度 new Thread(){ public void run(){ while(index<100){ notification.contentView.setProgressBar(R.id.my_progress, 100,index, false); index++; try{ sleep(new Random().nextInt(201)+300); }catch(Exception e){ e.printStackTrace(); } //更新通知信息,id相同情况下覆盖老信息 manager.notify(1, notification); } } }.start(); } }); //清除所有保存的通知 btn3.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { NotificationManager manager=(NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE); for(int i=0;i<ids.size();i++){ manager.cancel(ids.get(i)); } } }); } }
注释写的挺详细的了,如有疑问,欢迎留言
QML学习:对象和属性
本文博客链接:http://blog.csdn.net/jdh99,作者:jdh,转载请注明.
参考文档<<Qt及Qt Quick开发实战精解.pdf>>
环境:
主机:WIN7
开发环境:Qt
源代码:
import QtQuick 1.0 Rectangle { width:320 height:240 color:"blue" Image { source:"pics/1.jpg" anchors.centerIn: parent } Text { id:txt1 text:"Hello JDH!" font.pointSize: 24 font.bold: true anchors.centerIn: parent } }
运行效果:
说明:
代码中有1个对象:Rectangle
2个子对象:Image,Text
每个对象都可以设置属性,以及设置唯一ID
可以通过id访问对象,示例代码:
import QtQuick 1.0 Rectangle { width:320 height:240 color:"blue" Image { source:"pics/1.jpg" anchors.centerIn: parent } Text { id:txt1 text:"Hello JDH!" font.pointSize: 24 font.bold: true anchors.centerIn: parent } Text { id:txt2 x: -10 y: 199 text:txt1.text + "It is qml!" font.pointSize: 24 font.bold: true } }
运行效果: