安卓--即时通讯项目

1. 前言

聊天模块

xmpp  IM模块 越来越多的主流的app集成模块


自主研发

开源服务 im  xmpp  openfire

socket

xmpp

底层

框架

2. IM基本概念

QQ  微信  陌陌  IM/SNS

概念

instant  messager

通过底层协议实现的消息通道传输消息。由一个账号 发往另外一个账,只要对方在线,马上/实时接收到消息。

底层:TCP/UDP 

上层:http  smtp  ftp

消息通道:连接对象 发送消息 接收消息 I/O

实时无延时

2.1. TCP/IP UDP

转输数据的协议

将数据 封装一个信封,添加上ip地址。发送。

 

IP

网络上设备的编号 

PORT

端口 网络程序

 

 

TCP

UDP

1.电话确认 2.送货

1.送货

效率低

效率高

可靠性强

可靠性差

大文件

64K

面向连接: 三次握手

非面向连接

 

2.2. 三次握手

 

2.3. 常见形式

直接通讯

p2p  peer to peer 消息不经过服务器 直接发送

在线代理

消息经过服务器的转发 到达目标账号

离线代理

消息经过服务器的转发 对方不在线,暂存消息。在上线时  再转发到达目标账号

离线扩展

消息经过服务器的转发 对方不在线,暂存消息。以其他形式通知 目标账号  email msn sms

 

2.4.  IM原理

 

 

2.5. 注意点

理解含义

Socket 套接字

ServerSocket

Socket :java tcp/ip的实现 客服端

插头

ServerSocket :java tcp/ip的实现 服务端

插座

要求 

Android 客户端的理解

服务端不管: php  c++

自主开发的服务端

 

 

3. 核心概念

3.1. 消息内容_IM服务端接口文档

Http接口文档

IM接口文档:一个协议的文本形式 规定 字段+格式.

格式良好

xml

json

黑马

 

<message>黑马</message>

{name:黑马}

流量消耗大

流量消耗小

扩展好 pull sax  dom4j

扩展性

XStream  快速开发库

java对象与 xml互转

Xstream 

java对象 -->xml

toXml(对象);

xml->java对象

fromXml();

Gson  快速解析包

java对象与 json互转

Gson

java对象-->json 

toJson();

json-->java对象

fromJson(json,类)

 

3.2. 消息内容_Xstream自动生成

Junit测试

① Junit

② 功能清单配置

③ 编写测试用例

 

 <!-- 引用组件 -->

    <instrumentation

        android:name="android.test.InstrumentationTestRunner"

        android:targetPackage="com.itheima.im.socket" >

    </instrumentation>

 

    <application

        android:allowBackup="true"

        android:icon="@drawable/ic_launcher"

        android:label="@string/app_name"

        android:theme="@style/AppTheme" >

 

        <!-- 使用 -->

        <uses-library

            android:name="android.test.runner"

            android:required="true" />

 

QQMessage msg = new QQMessage();

msg.type = QQMessageType.MSG_TYPE_BUDDYLIST;

msg.content = "在不在";

// 创建核心类

XStream x = new XStream();

x.alias("QQMessage", QQMessage.class);

// 调用toxml

String xml = x.toXML(msg);

System.out.println(xml);

QQMessage msg2 = (QQMessage) x.fromXML(xml);

System.out.println(msg2.content);

 

3.3. 项目应用:继承基类

http

im

post   提交表单对象

Map  集合

消息对象

内容+附加字段

含有标签的表单

 

//强大 的对象 与 xml ,json

public class ProtocalObj {

public String toXml() {

// 创建核心类

XStream x = new XStream();

x.alias(getClass().getSimpleName(), getClass());

// 调用toxml

String xml = x.toXML(this);

return xml;

}

public Object fromXml(String xml) {

// 创建核心类

XStream x = new XStream();

x.alias(getClass().getSimpleName(), getClass());

// 调用toxml

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, getClass());

}

}

 

3.4. 消息通道-连接对象

传输消息通道发消息 接收消息

Socket客户端程序 

① 联网权限 

② Socket连接

 

I

readUTF

InputStream

  |--DataInputStream

O

writeUTF

OutputStream

  |-DataOutputStream

 

 

private DataInputStream reader=null;

private DataOutputStream writer=null;

public void connect(View view) {

new Thread(){

public void run() {

String ip = "192.168.15.97";

int port = 5224;

try {

//4.0

Socket socket = new Socket(ip, port);

//获取输入流

reader=new DataInputStream(socket.getInputStream());

writer=new DataOutputStream(socket.getOutputStream());

catch (UnknownHostException e) {

// TODO Auto-generated catch block

e.printStackTrace();

catch (IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

};

}.start();

}

 

3.5. 消息发送

 

public void sendmessage(View view)

{

//101 test   101#test

new Thread(){

public void run() {

QQMessage msg=new QQMessage();

msg.type=QQMessageType.MSG_TYPE_LOGIN;

msg.content="101#test";

try {

writer.writeUTF(msg.toXml());

catch (IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

};

}.start();

}

 

3.6. 消息接收

创建一个等待线程 。等待消息的到来

 

核心类

1.打开通道

2.关闭通道

3.发送消息

4.接收消息

3.7. 监听器

 

 

Button

 

OnClickListener  onClick 接口

 

setOnClickListener(监听器)

add N  set  1

事件:点击

事件:消息接收事件

 

监听器好处:事件处理程序写在核心类的外边 灵活低耦合的目的。

自定义控件 事件监听器

① 创建一个接口

② 响应方法 接收参数

③ 添加 /移除方法

④ 事件产生的地方执行

 

public class QQConnnection extends Thread {

private String ip;

private int port;

 

// Alt+Shift+S

 

public QQConnnection(String ip, int port) {

super();

this.ip = ip;

this.port = port;

}

 

// 1.打开通道

// 2.关闭通道

// 3.发送消息

// 4.接收消息

private Socket client = null;

private DataInputStream reader;

private DataOutputStream writer;

 

private boolean isRunning = true;

 

// 打开连接

public void connect() throws Exception {

client = new Socket(ipport);

reader = new DataInputStream(client.getInputStream());

writer = new DataOutputStream(client.getOutputStream());

while (isRunning) {

String xml = reader.readUTF();

QQMessage msg = new QQMessage();

msg = (QQMessage) msg.fromXml(xml);

if (msg != null) {

// 登录代码处理

// 解析好友列表

// 下线成功

for (OnReceiveMsgListener listener : listeners) {

listener.onReceive(msg);

}

}

}

}

 

// 添加多个监听

private List<OnReceiveMsgListener> listeners = new ArrayList<OnReceiveMsgListener>();

 

public void addOnReceiveMsgListener(OnReceiveMsgListener listener) {

listeners.add(listener);

}

 

public void removeOnReceiveMsgListener(OnReceiveMsgListener listener) {

listeners.remove(listener);

}

 

// 断开

public void disconnect() {

isRunning = false;

if (writer != null) {

try {

writer.close();

catch (IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

if (reader != null) {

try {

reader.close();

catch (IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

if (client != null) {

try {

client.close();

catch (IOException e) {

e.printStackTrace();

}

}

}

 

// 发送消息

public void sendMessage(QQMessage msg) throws Exception {

String xml = msg.toXml();

if (writer != null) {

writer.writeUTF(xml);

}

}

}

 

Command设计模式

 

http

im

String json=get();/post();

void sendmessage();

只能接收一次

通过监听器接收参数 N

addListener

removeListener

 

 

 

4. 项目功能-Socket

4.1. 模块:启动页面

拆分法 :分离出所有控件元素,帮我们分析界面

ThreadUtils.runInThread(new Runnable() {

@Override

public void run() {

try {

Thread.sleep(3000);

//1.广告 

//2.初始工作  检测新版本

startActivity(new Intent(getBaseContext(),LoginActivity.class));

finish();

catch (InterruptedException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

});

4.2.  模块:登录

提交账号密码给服务器端验证

① 布局 layout TableLayout

② 编写事件的逻辑

 

TableLayout

表格布局  子标签 代表一行

TableRow

代表表格的行   一行可以有多列

findViewById

@InjectView   set

findViewById  setOnClickListener

@OnClick

ButterKnife 黄油刀 注解开发库

 

ButterKnife.inject(this)

apt  annotation process tool

 

 

 

public class LoginActivity extends Activity {

@InjectView(R.id.username)

EditText username;

@InjectView(R.id.pwd)

EditText pwd;

@InjectView(R.id.login)

Button login;

@Override

protected void onCreate(Bundle savedInstanceState) {

// TODO Auto-generated method stub

super.onCreate(savedInstanceState);

 

setContentView(R.layout.activity_login);

 

// 初始化注解生效

ButterKnife.inject(this);

System.out.println(login);

}

@OnClick(R.id.login)

public void login(View view) {

Toast.makeText(this"登录", 0).show();

 

}

}

2

登录 url  http://ip:port/pro/login

ip port

post  form

sendMessnage QQMessage

username  password

username#test  content

  String json= HttpUtil.post();

void sendMessage(); 使用监听器接收

登录成功提示

 

 

保持 连接  长连接

 

① 创建消息通道

② 发送消息

③ 创建监听接收消息

④ 提示:成功/失败

 

 

ThreadUtils.runInThread(new Runnable() {

@Override

public void run() {

if (conn == null) {

try {

// 192.168.15.97 5225

conn = new QQConnnection("192.168.15.97", 5225);

conn.addOnReceiveMsgListener(listener);

conn.connect();// start等待线程

catch (Exception e) {

e.printStackTrace();

}

}

}

});

 

private OnReceiveMsgListener listener = new OnReceiveMsgListener() {

@Override

public void onReceive(final QQMessage  msg) {

// 子线程

ThreadUtils.runUIThread(new Runnable() {

@Override

public void run() {

if (msg.type.equals(QQMessageType.MSG_TYPE_BUDDYLIST)) {

System.out.println(msg.content);

String json = msg.content;

// 保存消息通道

MyApp.conn = conn;

// 保存账号

MyApp.username = usernameString;

Toast.makeText(getBaseContext(), "登录成功!", 0).show();

startActivity(new Intent(getBaseContext(), MainActivity.class));

finish();

else {

Toast.makeText(getBaseContext(), "账号或者密码出错!", 0).show();

}

}

});

 

}

};

private QQConnnection conn = null;

4.3. 模块.好友

 

//BaseAdapter x4

//  |--ArrayAdapter x1

public class ContactAdapter extends ArrayAdapter<QQBuddy> {

 

public ContactAdapter(Context context, List<QQBuddy> objects) {

super(context, 0, objects);

}

 

// 1. static 2.ButterKnife.inject(this, view);

static class ViewHolder {

@InjectView(R.id.head)

ImageView head;

@InjectView(R.id.title)

TextView title;

@InjectView(R.id.desc)

TextView desc;

 

public ViewHolder(View view) {

ButterKnife.inject(this, view);

}

 

}

 

// 返回行视图,显示指定下标的数据

@Override

public View getView(int position, View convertView, ViewGroup parent) {

// 数据

QQBuddy info = getItem(position);

ViewHolder holder = null;

// 视图 1.inflate 内存

if (convertView == null) {

// 打气 inflate 将xml转换成对象

// convertView=View.inflate(上下文, 视图文件, null);

convertView = View.inflate(getContext(), R.layout.item_contactnull);

holder = new ViewHolder(convertView);

// 2.findViewById

convertView.setTag(holder);

else {

holder = (ViewHolder) convertView.getTag();

}

holder.title.setText(info.nick);

if(MyApp.username.equals(info.account+""))

{

holder.title.setText("[我自己]");

}

holder.desc.setText(info.account+"@qq.com");

return convertView;

}

 

}

 

 

 

4.4. 模块.好友--上线更新

上线/下线

 

① 刷新页面

② 接收到消息的时候刷新  监听器:拦截处理

 

101-->199   test

 

MyApp.conn.addOnReceiveMsgListener(listener);

// 获取意图

Intent intent = getIntent();

String json = intent.getStringExtra("json");

System.out.println(json);

QQBuddyList temp = (QQBuddyList) new QQBuddyList().fromJson(json);

setAdapterOrNitifyDataSetChange(temp);

}

 

@Override

protected void onDestroy() {

super.onDestroy();

MyApp.conn.removeOnReceiveMsgListener(listener);

}

 

private OnReceiveMsgListener listener = new OnReceiveMsgListener() {

@Override

public void onReceive(final QQMessage msg) {// 子线程

ThreadUtils.runUIThread(new Runnable() {

@Override

public void run() {

// 控件 null

if (QQMessageType.MSG_TYPE_BUDDYLIST.equals(msg.type)) {

String json = msg.content;

QQBuddyList temp = (QQBuddyList) new QQBuddyList().fromJson(json);

setAdapterOrNitifyDataSetChange(temp);

}

}

});

}

};

// new Thread().start();

public void setAdapterOrNitifyDataSetChange(QQBuddyList temp) {

list.buddyList.clear();

list.buddyList.addAll(temp.buddyList);

if (adapter == null) {

if (list.buddyList.size() < 1) {

return;

}

// 06-12 03:05:15.215: I/System.out(2120):

// {"buddyList":[{"account":101,"nick":"QQ 1","avatar":0}]}

adapter = new ContactAdapter(thislist.buddyList);

contactlistview.setAdapter(adapter);

else {

adapter.notifyDataSetChanged();

}

}

 

private QQBuddyList list = new QQBuddyList();

 

 

4.5. 模块:聊天

① 布局Layout ListView 行视图多种类型

② 创建Activity

③ 获取 nick account

④ 静态UI  假数据-->循环显示数据

⑤ 假数据换真数据

 

public class ChatAdapter extends ArrayAdapter<QQMessage> {

 

public ChatAdapter(Context context, List<QQMessage> objects) {

super(context, 0, objects);

}

 

// 行视图的种类数

@Override

public int getViewTypeCount() {

return 2; // 0 ,1

}

 

// 0发送 1接收

// 根据数据返回指定下标的视图类型

@Override

public int getItemViewType(int position) {

long me = Long.parseLong(MyApp.username);

QQMessage msg = getItem(position);

if (me == msg.from) {

return 0;// 发送

}

return 1;// 接收

}

 

@Override

public View getView(int position, View convertView, ViewGroup parent) {

int type = getItemViewType(position);

if (type == 0)// 发送

{

 

return View.inflate(getContext(), R.layout.item_chat_sendnull);

 

else // 接收

{

return View.inflate(getContext(), R.layout.item_chat_receivenull);

}

}

 

}

 

优化

@Override

public View getView(int position, View convertView, ViewGroup parent) {

int type = getItemViewType(position);

if (type == 0)// 发送

{

ViewHolder holder = null;

if (convertView == null) {

convertView = View.inflate(getContext(), R.layout.item_chat_sendnull);

holder = new ViewHolder(convertView);

convertView.setTag(holder);

else {

holder = (ViewHolder) convertView.getTag();

}

QQMessage msg = getItem(position);

holder.time.setText(msg.sendTime);

holder.content.setText(msg.content);

return convertView;

 

else // 接收

{

 

ViewHolder holder = null;

if (convertView == null) {

convertView = View.inflate(getContext(), R.layout.item_chat_receivenull);

holder = new ViewHolder(convertView);

convertView.setTag(holder);

else {

holder = (ViewHolder) convertView.getTag();

}

QQMessage msg = getItem(position);

holder.time.setText(msg.sendTime);

holder.content.setText(msg.content);

 

return convertView;

}

}

 

 发送消息

 

@OnClick(R.id.send)

public void send(View view) {

 

String inputMessage = input.getText().toString().trim();

input.setText("");

 

// 创建消息

final QQMessage msg = new QQMessage();

msg.type = QQMessageType.MSG_TYPE_CHAT_P2P;

msg.content = inputMessage;

msg.from = Long.parseLong(MyApp.username);

msg.to = toChatAccount;

messages.add(msg);

setAdapterOrNotify();

ThreadUtils.runInThread(new Runnable() {

 

@Override

public void run() {

try {

MyApp.conn.sendMessage(msg);

catch (Exception e) {

e.printStackTrace();

}

 

}

});

 

}

 

消息接收

 

private OnReceiveMsgListener listener = new OnReceiveMsgListener() {

public void onReceive(final QQMessage msg) {//子线程

ThreadUtils.runUIThread(new Runnable() {

@Override

public void run() {

if (msg.type.equals(QQMessageType.MSG_TYPE_CHAT_P2P)) {

messages.add(msg);

setAdapterOrNotify();

Toast.makeText(getBaseContext(), "好友消息:"+msg.content, 0).show();

}

}

});

 

}

};

 

protected void onDestroy() {

super.onDestroy();

MyApp.conn.removeOnReceiveMsgListener(listener);

};

 

private List<QQMessage> messages = new ArrayList<QQMessage>();

 

@Override

protected void onCreate(Bundle savedInstanceState) {

// TODO Auto-generated method stub

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_chat);

ButterKnife.inject(this);

 

MyApp.conn.addOnReceiveMsgListener(listener);

 

4.6. 后台Service运行消息接收

QQ软件 所有的界面退出 还是通接收到消息

Activity/Fragment  窗口片段

Service

返回关闭当前的页面,聊天核心程序。杀死

没有界面,隐蔽,长期运行的程序 

 

 

//四大组件 1.继承 2.重写  3.配置 4.打开

public class ChatService extends Service {

 

@Override

public void onCreate() {

super.onCreate();// Notification QQ

Toast.makeText(this"聊天..后台服务.", 0).show();

MyApp.conn.addOnReceiveMsgListener(listener);

}

 

private OnReceiveMsgListener listener=new OnReceiveMsgListener() {

@Override

public void onReceive(final QQMessage msg) {//子线程

ThreadUtils.runUIThread(new Runnable() {

@Override

public void run() {

 

if(msg.type.equals(QQMessageType.MSG_TYPE_CHAT_P2P))

{

Toast.makeText(getBaseContext(), "好友消息."+msg.content, 0).show();

}

}

});

}

};

@Override

public void onDestroy() {

super.onDestroy();

MyApp.conn.removeOnReceiveMsgListener(listener);

}

 

@Override

public IBinder onBind(Intent intent) {

return null;

}

 

}

 

 

Provider复习--保存联系人

练习

Chat功能

 

Provider 练习

 

 


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值