IM
1. IM基本概念与原理
1.1. TCP/IP UDP
1.2. 三次握手
1.3. 常见形式
1.4. IM原理
1.5. 注意点
2. 核心概念
2.1. 消息内容_IM服务端接口文档
2.2. 消息内容_Xstream自动生成
2.3. 项目应用:继承基类
2.4. 消息通道-连接对象
2.5. 消息发送
2.6. 消息接收
2.6.1. 监听器
3. 项目功能-Socket
3.1. 模块:启动页面
3.2. 模块:登录
3.3. 模块.好友
3.4. 模块.好友--上线更新
4. 模块:聊天
4.1. 后台Service运行消息接收
5. Provider复习--保存联系人
练习
- IM基本概念与原理
QQ 微信 陌陌
即时通讯
IM instant messager
通过消息通道 传输消息对象,一个账号发往另外一账号,只要账号在线,即时获取到消息。
消息通道:由TCP/IP UDP实现的通道。
-
- TCP/IP UDP
TCP/UDP:传输消息的时候,封装上ip信息。
ip | 网络上电脑的编号 192.168.10.100 |
port | 网络程序的编号 tomcat 8080 |
TCP | UDP |
大文件 | 64K |
可靠 | 不可靠 |
面向连接 | 不面向连接 |
效率低 | 效率高 |
1.打电话 2.运货 | 1.运货 |
-
- 三次握手
-
- 常见形式
在线直传 | 不经过服务器,Peer to Peer p2p |
在线代理 | 消息经过服务器 中转 到达目标账号 |
离线代理 | 消息经过服务器 中转 到达目标账号 对方不在线 消息暂存服务器的数据库,在其上线 再传发 |
离线扩展 | 将暂存消息 以其它 邮件 短信 转发给目标账号 |
-
- IM原理
注意事项:消息通道不能断开
- 用户登录,提交账号密码 经服务端确定
- 成功以服务端发回 好友信息
- 消息必须包含 from to content time
- 由服务器转发给目标账号
-
- 注意点
findViewId
客户端 | C++ java |
概念理解 含义 | Socket 套接字 插头 客户端程序 ServerSocket 插座 服务端程序 |
消息对象 |
|
- 核心概念
自主研发 | 开源服务器 |
socket | 借助开源 |
IM服务端接口文档 |
|
-
- 消息内容_IM服务端接口文档
IM服务接口文档:以文档的形式 规定 通讯消息的 字段与格式(xml/json)
消息对象:消息内容+附加字段的封装
xml | json |
黑马 |
|
<name>黑马</name> | {name:黑马} |
耗流量 | 节省流量 |
扩展性 xml msn | 相对较差 |
Xstream 将java对象xml互换 Xstream x=new XStream(); toXml 对象转xml fromXml xml转对象 | gson 将java 对象json字符串 GSON gson=new Gson(); toJson fromJson |
不生成get set 会使用效率降低 |
|
-
- 消息内容_Xstream自动生成
Junit
- junit3/4
- 功能清单配置
- 编写测试代码
Xstream
- 创建核心对象
- 调用toXml 或者fromXml
05-21 02:08:07.775: I/System.out(1204): <QQMessage>
05-21 02:08:07.775: I/System.out(1204): <content>今晚有空没</content>
05-21 02:08:07.775: I/System.out(1204): <type>chatp2p</type>
05-21 02:08:07.775: I/System.out(1204): <sendTime>08-141 02:08:07</sendTime>
05-21 02:08:07.775: I/System.out(1204): <fromNick>老王@qq</fromNick>
05-21 02:08:07.775: I/System.out(1204): <from>7</from>
05-21 02:08:07.775: I/System.out(1204): <to>10086</to>
05-21 02:08:07.775: I/System.out(1204): <fromAvatar>1</fromAvatar>
05-21 02:08:07.775: I/System.out(1204): </QQMessage>
// ① 创建核心对象
XStream x = new XStream();
x.alias("QQMessage", QQMessage.class);
QQMessage msg = new QQMessage();
msg.type = QQMessageType.MSG_TYPE_CHAT_P2P;
msg.content = "今晚有空没";
msg.fromNick = "老王@qq";
msg.from = 007;
msg.to = 10086;
// ② 调用toXml 或者fromXml
String xml = x.toXML(msg);
QQMessage m2 = (QQMessage) x.fromXML(xml);
System.out.println(m2.fromNick);
-
- 项目应用:继承基类
public class ProtocalObj {
public String toXml() {
XStream x = new XStream();
x.alias(this.getClass().getSimpleName(), this.getClass());
// ② 调用toXml 或者fromXml
return x.toXML(this);
}
public Object fromXml(String xml) {
XStream x = new XStream();
x.alias(this.getClass().getSimpleName(), this.getClass());
// ② 调用toXml 或者fromXml
return x.fromXML(xml);
}
public String toJson() {
Gson gson = new Gson();
return gson.toJson(this);
}
public Object fromJson(String json) {
Gson gson = new Gson();
return gson.fromJson(json, this.getClass());
}
}
-
- 消息通道-连接对象
消息通道:消息对象传输的通道. 路 桥 1.发送消息 2.接收消息
消息通道
try {
Socket client = new Socket(HOST, PORT);
} catch (Exception e) {
e.printStackTrace();
}// TODO Auto-generated method stub
-
- 消息发送
OutputStream 发送 |--DataOutputStream | writeUTF |
InputStream 接收 |--DataInputStream | readUTF |
QQMessage msg = new QQMessage();
msg.type = QQMessageType.MSG_TYPE_LOGIN;
msg.content = "101#test";
String xml=msg.toXml();
writer.writeUTF(xml);
writer.flush();
-
- 消息接收
// 等待消息
new Thread() {
public void run() {
while (true) {
try {
String xml = reader.readUTF();
System.out.println("接收到消息 :"+xml);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
}.start();
-
-
- 监听器
-
监听器:一个接口 含有抽象方法 接收参数
在即时通讯的项目 消息接收器-->抽象方法来处理
开发步骤
- 申明接口
- 抽象方法
- 定义参数
- 添加方法 移除
- 参数产生的地方调用
- 项目功能-Socket
- 模块:启动页面
- 继承
- 重写
- 配置
- 打开
开发步骤
- 布局 ImageView
- 等待3000
ThreadUtils.runInThread(new Runnable() {
@Override
public void run() {
try {
//检查更新
//初始
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
-
- 模块:登录
提交账号密码 给服务器验证 接收验证结果编写逻辑
ButterKnife 黄油刀 | 注解开发库 |
findViewById | @InjectView set 设置 |
findViewById setOnClickLisener | @OnClick |
ButterKnife.inject(this); | 初始化 xUtils |
配置eclipse | apt annotation process tool 设置jar |
开发步骤
- 布局 TableLayout
- 事件逻辑
发送
QQMessage msg = new QQMessage();
msg.type = QQMessageType.MSG_TYPE_LOGIN;
msg.content = username + "#" + password;
String xml = msg.toXml();
if (conn != null) {
try {
conn.sendMessage(xml);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private OnQQMessageReceiveListener listener = new OnQQMessageReceiveListener() {
@Override
public void onReceive(QQMessage msg) {
System.out.println(msg.toXml());
if (QQMessageType.MSG_TYPE_BUDDY_LIST.equals(msg.type)) {
ThreadUtils.runUIThread(new Runnable() {
@Override
public void run() {
MyApp.me=conn;
MyApp.username=username;
MyApp.account=username+"@qq";
Toast.makeText(getBaseContext(), "登录成功", 0).show();
startActivity(new Intent(getBaseContext(), MainActivity.class));
finish();
}
});
} else {
ThreadUtils.runUIThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getBaseContext(), "登录失败", 0).show();
}
});
}
}
};
-
- 模块.好友
显示所有好友联系人信息,受好友上线/下线的影响
开发步骤
- 布局 ListView
- 解析xml --> 集合
- 创建Adapter
- 设置给控件
private List<QQBuddy> list = new ArrayList<QQBuddy>();
private ArrayAdapter<QQBuddy> adapter = null;
private void setAdapter() {
if (list.size() < 1) {
return;
}
// adapter=new ArrayAdapter<QQBuddy>(上下文,行视图,数据集合);
adapter = new ArrayAdapter<QQBuddy>(this, 0, list) {
// 返回视图 显示了指定下标的数据
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
if (convertView == null) {
convertView = View.inflate(getBaseContext(), R.layout.item_buddy, null);
holder = new ViewHolder(convertView);
convertView.setTag(holder);
}else
{
holder=(ViewHolder) convertView.getTag();
}
QQBuddy buddy=list.get(position);
holder.nick.setText(buddy.nick+"");
holder.account.setText(buddy.account+"@qq");
return convertView;
}
};
listview.setAdapter(adapter);
}
static class ViewHolder {
@InjectView(R.id.head)
ImageView head;
@InjectView(R.id.nick)
TextView nick;
@InjectView(R.id.account)
TextView account;
public ViewHolder(View view) {
ButterKnife.inject(this, view);
}
}
-
- 模块.好友--上线更新
@Override
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
MyApp.me.removeOnQQMessageReceiveListener(listener);
}
private OnQQMessageReceiveListener listener = new OnQQMessageReceiveListener() {
@Override
public void onReceive(QQMessage msg) {
if (QQMessageType.MSG_TYPE_BUDDY_LIST.equals(msg.type)) {
QQBuddyList temp = new QQBuddyList();
temp = (QQBuddyList) temp.fromXml(msg.content);
list.clear();
list.addAll(temp.buddyList);
ThreadUtils.runUIThread(new Runnable() {
@Override
public void run() {
setAdapterOrNitify();
// 刷新ListView
}
});
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_contact);
MyApp.me.addOnQQMessageReceiveListener(listener);
- 模块:聊天
消息在消息通道中传输,发送+接收
开发步骤
- 静态UI (界面显示假数据)
- 假数据换成真数据
- Service运行消息接收程序
Actvity/Fragment | Service |
显示界面的控件 | 不包含界面,运行于后台的隐蔽程序 |
返回键关闭程序 | 后台接收消息 |
-
- 后台Service运行消息接收
public class ImService extends Service {
// 必须使用监听器
private OnQQMessageReceiveListener listener = new OnQQMessageReceiveListener() {
@Override
public void onReceive(final QQMessage msg) {
if (QQMessageType.MSG_TYPE_CHAT_P2P.equals(msg.type)) {
// Notification
ThreadUtils.runUIThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getBaseContext(), "好友消息:" + msg.content, 0).show();
}
});
}
}
};
@Override
public void onCreate() {
super.onCreate();
// Notification
Toast.makeText(getBaseContext(), "IM后台服务打开", 0).show();
MyApp.me.addOnQQMessageReceiveListener(listener);
}
@Override
public void onDestroy() {
super.onDestroy();
MyApp.me.removeOnQQMessageReceiveListener(listener);
}
@Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return null;
}
}
- Provider复习--保存联系人
开发步骤
- 创建Provider 1.继承 2.重写 3.注册 4.使用
- 设计表结构
- 建表语句
- 获取OpenHelper实例
- 获取数据库实例SqliteDatabase
- CRUD
- Junit
<provider
android:name="com.itheima.im3.provider.ContactProvider"
android:authorities="com.itheima.im3.provider.ContactProvider" >
</provider>
public class ContactProvider extends ContentProvider {
// Canonical全部类名 com.itheima.im3.core.ContactProvider
private static final String authority = ContactProvider.class.getCanonicalName();
// ① 创建Provider 1.继承 2.重写 3.注册 4.使用
// ② 设计表结构
// BaseColumns带id的字段接口
public static class Contact implements BaseColumns {
public static final String ACCOUNT = "account";
public static final String NICK = "nick";
public static final String AVATAR = "avatar";
public static final String SORT = "sort";// 拼音
}
// ③ 建表语句
public static final String TABLE = "contact";//
public static final String DB = "contact.db";// 拼
private class MyHelper extends SQLiteOpenHelper {
public MyHelper(Context context) {
super(context, DB, null, 1);
}
@Override
public void onCreate(SQLiteDatabase db) {
String sql = "create table contact(_id integer auto increment primary key ,account long ,nick text,avatar integer,sort text)";
db.execSQL(sql);
}
}
private MyHelper mMyHelper = null;
// ④ 获取OpenHelper实例
// ⑤ 获取数据库实例SqliteDatabase
// ⑥ CRUD
// ⑦ Junit
private SQLiteDatabase db = null;
@Override
public boolean onCreate() {
if (mMyHelper == null) {
mMyHelper = new MyHelper(getContext());
}
return mMyHelper == null ? false : true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
// TODO Auto-generated method stub
return null;
}
@Override
public String getType(Uri uri) {
// TODO Auto-generated method stub
return null;
}
public static final Uri CONTACT_URI = Uri.parse("content://" + authority + "/" + TABLE);
private static UriMatcher uris = null;
private static final int CANTACT = 0;
static {
uris = new UriMatcher(UriMatcher.NO_MATCH);
uris.addURI(authority, TABLE, CANTACT);
}
// content://authority/contact
// content://authority/contact/1
// ContentUris工具 1.uri +id 2. 获取id
@Override
public Uri insert(Uri uri, ContentValues values) {
switch (uris.match(uri)) {
case CANTACT:
db = mMyHelper.getWritableDatabase();
long id = db.insert(TABLE, "", values);
if (id != -1) {
uri = ContentUris.withAppendedId(uri, id);
}
break;
}
return uri;
}
ContentValues record = new ContentValues();
record.put(ContactProvider.Contact.ACCOUNT, 101);
record.put(ContactProvider.Contact.NICK, "隔壁老王");
record.put(ContactProvider.Contact.SORT, "GBLW");
record.put(ContactProvider.Contact.AVATAR, 0);
// getContext().getContentResolver().insert(访问地址, 一条记录);
getContext().getContentResolver().insert(ContactProvider.CONTACT_URI, record);
练习
Provider | 原理 contact CRUD +test |
Chat模块 | 代码写一遍 |
应用 | OA 客服 Chat 婚恋 医疗 企业级通讯软件 |