首页 > 程序开发 > 移动开发 > Android >

android项目实践之融云聊天服务端与客户端的实现

2017-04-24

android项目实践之融云聊天服务端与客户端的实现,最近研究了即时通讯,当然用户是第三方IM。融云和网易云信在市场上的使用用户都挺多,但是我还是选择了融云并且研究了一番,也终于有些成果并跟大家分享。

android项目实践之融云聊天服务端与客户端的实现,最近研究了即时通讯,当然用户是第三方IM。融云和网易云信在市场上的使用用户都挺多,但是我还是选择了融云并且研究了一番,也终于有些成果并跟大家分享。下面是效果图。

这里写图片描述
这里写图片描述

一、开发前的准备

首先我们肯定是要去登录融云的官网去下载相关的SDK,并且创建相应的应用获取APP Key 和 App Secret。

这里写图片描述

最简单的聊天功能下载SDK只需要IMKit 与 IMLib就能实现。同时可以选一些附加功能这个根据自己的需求选择就行。这里的操作都很简单,现在讲项目这一块。

1、我们最初把SDK下载完之后选择两个库导入as中。步骤:file -> new -> import Module ->找到两个库并导入。导入重新构建后一定要记得在app项目的build.gradle中加上compile project(‘:IMKit’) 进行关联。

2、把上面选择需要功能的jar 和 os文件 (下载的SDK中有) 复制到app的Lib中。

这里写图片描述

3、在IMLib中的AndroidManifest文件中填入自己创建应用的App Key。

4、初始化融云SDK。建一个类继承Application(android启动先走Application)在OnCreate()方法中初始化,初始化语句:RongIM.init(this);。同时AndroidManifest中application取名为App

二、服务端连接融云获取Token

1、编写一个简单的测试界面。

这里写图片描述

2、我们来梳理一下思路:随便输入一个id -> 点击返回值按钮 -> 请求自己的服务器并传入id -> 自己的服务器把请求融云服务器并传入id -> 融云服务器进行响应并返回id对应的Token值给自己的服务器 -> 自己的服务器把ToKen返回给客户端 -> 客户端得到Token使用RongIM.connect()方法就可以连接融云服务器。希望下面的图能够清晰的帮你理解。

这里写图片描述

3、点击按钮进行请求自己的服务器,同时服务端代码的实现。

客户端请求:

private void postId(final String id) {

//创建一个OkHttpClient对象

OkHttpClient okHttpClient = new OkHttpClient();

FormBody body = new FormBody.Builder()

.add("id", id)

.build();

Request request = new Request.Builder().post(body).url(API.GET_TOKEN).build();

Call call = okHttpClient.newCall(request);

call.enqueue(new Callback() {

//请求失败时调用

@Override

public void onFailure(Call call, IOException e) {

Log.i(TAG, "onFailure: " + e);

}

//请求成功时调用

@Override

public void onResponse(Call call, Response response) throws IOException {

final String str = response.body().string();

userInfo = JSON.parseObject(str, UserInfo.class);

runOnUiThread(new Runnable() {

@Override

public void run() {

Toast.makeText(LoginActivity.this, "str" + str + ", token:" + userInfo.getToken(), Toast.LENGTH_SHORT).show();

editor.putString("loginToken", userInfo.getToken());

btnConn.setVisibility(View.VISIBLE);

}

});

}

});

}

服务端相应:

//获取用户token

@ResponseBody

@RequestMapping(value = "/getToken")

String getToken(UserItem uEntity, String id){

String token = RongUtils.getToken(id);

return token;

}

还有请求融云服务器的工具类在下面的Demo中会有就不一一贴出来。

4、通过获取的Token与融云服务器连接。

if (getApplicationInfo().packageName.equals(App.getCurProcessName(getApplicationContext()))) {

RongIM.connect(userInfo.getToken(), new RongIMClient.ConnectCallback() {

@Override

public void onTokenIncorrect() {

}

@Override

public void onSuccess(String userid) {

//userid,是我们在申请token时填入的userid

System.out.println("========userid" + userid);

runOnUiThread(new Runnable() {

@Override

public void run() {

Toast.makeText(LoginActivity.this, "链接服务器成功!", Toast.LENGTH_SHORT).show();

editor.putString("loginToken", userInfo.getToken());

}

});

}

@Override

public void onError(RongIMClient.ErrorCode errorCode) {

}

});

}

接下来就可以通过写一个界面以及功能,其实融云封装的很好,阅读文档基本就能成功。下面就贴代码吧。。。

主界面MainActivity:

package com.song.rongyundemo;

import android.content.Context;

import android.content.Intent;

import android.graphics.Color;

import android.net.Uri;

import android.os.Bundle;

import android.support.v4.app.Fragment;

import android.support.v4.app.FragmentActivity;

import android.support.v4.app.FragmentPagerAdapter;

import android.support.v4.view.ViewPager;

import android.view.KeyEvent;

import android.view.MotionEvent;

import android.view.View;

import android.view.inputmethod.InputMethodManager;

import android.widget.ImageView;

import android.widget.RelativeLayout;

import android.widget.TextView;

import com.song.rongyundemo.adapter.ConversationListAdapterEx;

import com.song.rongyundemo.fragment.FriendFragment;

import com.song.rongyundemo.utils.DragPointView;

import com.song.rongyundemo.utils.NToast;

import java.util.ArrayList;

import java.util.List;

import io.rong.common.RLog;

import io.rong.imkit.RongContext;

import io.rong.imkit.RongIM;

import io.rong.imkit.fragment.ConversationListFragment;

import io.rong.imkit.manager.IUnReadMessageObserver;

import io.rong.imlib.RongIMClient;

import io.rong.imlib.model.Conversation;

@SuppressWarnings("deprecation")

public class MainActivity extends FragmentActivity implements

ViewPager.OnPageChangeListener,

View.OnClickListener,

DragPointView.OnDragListencer,

IUnReadMessageObserver {

public static ViewPager mViewPager;

private List mFragment = new ArrayList<>();

private ImageView mImageChats, mImageContact;

private TextView mTextChats, mTextContact;

private DragPointView mUnreadNumView;

/**

* 会话列表的fragment

*/

private ConversationListFragment mConversationListFragment = null;

private boolean isDebug;

private Context mContext;

private Conversation.ConversationType[] mConversationsTypes = null;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

mContext = this;

isDebug = getSharedPreferences("config", MODE_PRIVATE).getBoolean("isDebug", false);

initViews();

changeTextViewColor();

changeSelectedTabState(0);

initMainViewPager();

}

private void initViews() {

RelativeLayout chatRLayout = (RelativeLayout) findViewById(R.id.seal_chat);

RelativeLayout contactRLayout = (RelativeLayout) findViewById(R.id.seal_contact_list);

mImageChats = (ImageView) findViewById(R.id.tab_img_chats);

mImageContact = (ImageView) findViewById(R.id.tab_img_contact);

mTextChats = (TextView) findViewById(R.id.tab_text_chats);

mTextContact = (TextView) findViewById(R.id.tab_text_contact);

chatRLayout.setOnClickListener(this);

contactRLayout.setOnClickListener(this);

}

private void initMainViewPager() {

Fragment conversationList = initConversationList();

mViewPager = (ViewPager) findViewById(R.id.main_viewpager);

mUnreadNumView = (DragPointView) findViewById(R.id.seal_num);

mUnreadNumView.setOnClickListener(this);

mUnreadNumView.setDragListencer(this);

mFragment.add(conversationList);

mFragment.add(new FriendFragment());

FragmentPagerAdapter fragmentPagerAdapter = new FragmentPagerAdapter(getSupportFragmentManager()) {

@Override

public Fragment getItem(int position) {

return mFragment.get(position);

}

@Override

public int getCount() {

return mFragment.size();

}

};

mViewPager.setAdapter(fragmentPagerAdapter);

mViewPager.setOnPageChangeListener(this);

initData();

}

private Fragment initConversationList() {

if (mConversationListFragment == null) {

ConversationListFragment listFragment = new ConversationListFragment();

listFragment.setAdapter(new ConversationListAdapterEx(RongContext.getInstance()));

Uri uri;

uri = Uri.parse("rong://" + getApplicationInfo().packageName).buildUpon()

.appendPath("conversationlist")

.appendQueryParameter(Conversation.ConversationType.PRIVATE.getName(), "false") //设置私聊会话是否聚合显示

.appendQueryParameter(Conversation.ConversationType.GROUP.getName(), "true")//群组

.appendQueryParameter(Conversation.ConversationType.PUBLIC_SERVICE.getName(), "false")//公共服务号

.appendQueryParameter(Conversation.ConversationType.APP_PUBLIC_SERVICE.getName(), "false")//订阅号

.appendQueryParameter(Conversation.ConversationType.SYSTEM.getName(), "true")//系统

.appendQueryParameter(Conversation.ConversationType.DISCUSSION.getName(), "true")

.build();

mConversationsTypes = new Conversation.ConversationType[]{Conversation.ConversationType.PRIVATE,

Conversation.ConversationType.GROUP,

Conversation.ConversationType.PUBLIC_SERVICE,

Conversation.ConversationType.APP_PUBLIC_SERVICE,

Conversation.ConversationType.SYSTEM,

Conversation.ConversationType.DISCUSSION

};

listFragment.setUri(uri);

mConversationListFragment = listFragment;

return listFragment;

} else {

return mConversationListFragment;

}

}

@Override

public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

}

@Override

public void onPageSelected(int position) {

changeTextViewColor();

changeSelectedTabState(position);

}

private void changeTextViewColor() {

mTextChats.setTextColor(Color.parseColor("#abadbb"));

mTextContact.setTextColor(Color.parseColor("#abadbb"));

}

private void changeSelectedTabState(int position) {

switch (position) {

case 0:

mTextChats.setTextColor(Color.parseColor("#0099ff"));

break;

case 1:

mTextContact.setTextColor(Color.parseColor("#0099ff"));

break;

}

}

@Override

public void onPageScrollStateChanged(int state) {

}

long firstClick = 0;

long secondClick = 0;

@Override

public void onClick(View v) {

switch (v.getId()) {

case R.id.seal_chat:

if (mViewPager.getCurrentItem() == 0) {

if (firstClick == 0) {

firstClick = System.currentTimeMillis();

} else {

secondClick = System.currentTimeMillis();

}

RLog.i("MainActivity", "time = " + (secondClick - firstClick));

if (secondClick - firstClick > 0 && secondClick - firstClick <= 800) {

mConversationListFragment.focusUnreadItem();

firstClick = 0;

secondClick = 0;

} else if (firstClick != 0 && secondClick != 0) {

firstClick = 0;

secondClick = 0;

}

}

mViewPager.setCurrentItem(0, false);

break;

case R.id.seal_contact_list:

mViewPager.setCurrentItem(1, false);

break;

}

}

@Override

protected void onNewIntent(Intent intent) {

super.onNewIntent(intent);

if (intent.getBooleanExtra("systemconversation", false)) {

mViewPager.setCurrentItem(0, false);

}

}

protected void initData() {

final Conversation.ConversationType[] conversationTypes = {

Conversation.ConversationType.PRIVATE,

Conversation.ConversationType.GROUP, Conversation.ConversationType.SYSTEM,

Conversation.ConversationType.PUBLIC_SERVICE, Conversation.ConversationType.APP_PUBLIC_SERVICE

};

//未读消息

RongIM.getInstance().addUnReadMessageCountChangedObserver(this, conversationTypes);

//用户头像

RongIM.setUserInfoProvider(new RongIM.UserInfoProvider() {

@Override

public io.rong.imlib.model.UserInfo getUserInfo(String userId) {

if (userId.equals("5")) {

return new io.rong.imlib.model.UserInfo(userId, "宋泉柯", Uri.parse("http://192.168.191.1:8080/petServer/upload/head_bg.jpg"));

}

return null;

}

}, true);

//刷新用户

// RongIM.getInstance().refreshUserInfoCache(new UserInfo(connectResultId, nickName, Uri.parse(portraitUri)));

}

@Override

public void onCountChanged(int count) {

if (count == 0) {

mUnreadNumView.setVisibility(View.GONE);

} else if (count > 0 && count < 100) {

mUnreadNumView.setVisibility(View.VISIBLE);

mUnreadNumView.setText(String.valueOf(count));

} else {

mUnreadNumView.setVisibility(View.VISIBLE);

mUnreadNumView.setText("...");

}

}

@Override

public boolean onKeyDown(int keyCode, KeyEvent event) {

if (keyCode == KeyEvent.KEYCODE_BACK) {

moveTaskToBack(false);

return true;

}

return super.onKeyDown(keyCode, event);

}

@Override

public boolean onTouchEvent(MotionEvent event) {

if (null != this.getCurrentFocus()) {

InputMethodManager mInputMethodManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);

return mInputMethodManager.hideSoftInputFromWindow(this.getCurrentFocus().getWindowToken(), 0);

}

return super.onTouchEvent(event);

}

@Override

public void onDragOut() {

mUnreadNumView.setVisibility(View.GONE);

NToast.shortToast(mContext, "清理成功");

RongIM.getInstance().getConversationList(new RongIMClient.ResultCallback>() {

@Override

public void onSuccess(List conversations) {

if (conversations != null && conversations.size() > 0) {

for (Conversation c : conversations) {

RongIM.getInstance().clearMessagesUnreadStatus(c.getConversationType(), c.getTargetId(), null);

}

}

}

@Override

public void onError(RongIMClient.ErrorCode e) {

}

}, mConversationsTypes);

}

}

需要注意的是很多人说头像昵称不知道怎么实现其实很简单,如下代码:

//用户头像

RongIM.setUserInfoProvider(new RongIM.UserInfoProvider() {

@Override

public io.rong.imlib.model.UserInfo getUserInfo(String userId) {

if (userId.equals("5")) {

return new io.rong.imlib.model.UserInfo(userId, "宋泉柯", Uri.parse("http://192.168.191.1:8080/petServer/upload/head_bg.jpg"));

}

return null;

}

}, true);

会话界面ConversationActivity:

package com.song.rongyundemo;

import android.annotation.TargetApi;

import android.content.Intent;

import android.content.SharedPreferences;

import android.net.Uri;

import android.os.Bundle;

import android.os.Handler;

import android.os.Message;

import android.support.v4.app.FragmentActivity;

import android.support.v4.app.FragmentTransaction;

import android.text.TextUtils;

import android.util.Log;

import android.view.MotionEvent;

import android.view.View;

import android.view.inputmethod.InputMethodManager;

import android.widget.Button;

import android.widget.TextView;

import com.song.rongyundemo.fragment.ConversationFragmentEx;

import com.song.rongyundemo.utils.NToast;

import java.util.Collection;

import java.util.Iterator;

import java.util.Locale;

import io.rong.imkit.RongIM;

import io.rong.imkit.fragment.UriFragment;

import io.rong.imkit.userInfoCache.RongUserInfoManager;

import io.rong.imlib.MessageTag;

import io.rong.imlib.RongIMClient;

import io.rong.imlib.TypingMessage.TypingStatus;

import io.rong.imlib.model.Conversation;

import io.rong.imlib.model.UserInfo;

import io.rong.message.TextMessage;

import io.rong.message.VoiceMessage;

/**

* 会话页面

* 1,设置 ActionBar title

* 2,加载会话页面

* 3,push 和 通知 判断

*/

public class ConversationActivity extends FragmentActivity implements View.OnClickListener {

private String TAG = ConversationActivity.class.getSimpleName();

/**

* 对方id

*/

private String mTargetId;

/**

* 会话类型

*/

private Conversation.ConversationType mConversationType;

/**

* title

*/

private String title;

/**

* 是否在讨论组内,如果不在讨论组内,则进入不到讨论组设置页面

*/

private boolean isFromPush = false;

private Button btnBack;

private Button btnRight;

private TextView tvTitle;

private SharedPreferences sp;

private final String TextTypingTitle = "对方正在输入...";

private final String VoiceTypingTitle = "对方正在讲话...";

private Handler mHandler;

public static final int SET_TEXT_TYPING_TITLE = 1;

public static final int SET_VOICE_TYPING_TITLE = 2;

public static final int SET_TARGET_ID_TITLE = 0;

@Override

@TargetApi(23)

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.conversation);

sp = getSharedPreferences("config", MODE_PRIVATE);

btnBack = (Button) findViewById(R.id.btn_left);

btnBack.setOnClickListener(this);

btnRight = (Button) findViewById(R.id.btn_right);

btnRight.setOnClickListener(this);

tvTitle = (TextView) findViewById(R.id.tv_title);

Intent intent = getIntent();

if (intent == null || intent.getData() == null)

return;

mTargetId = intent.getData().getQueryParameter("targetId");

mConversationType = Conversation.ConversationType.valueOf(intent.getData()

.getLastPathSegment().toUpperCase(Locale.US));

title = intent.getData().getQueryParameter("title");

Log.e(TAG, "mConversationType: " + mConversationType + ", title: " + title +", mTargetId" + mTargetId);

NToast.shortToast(this,"mConversationType: " + mConversationType + ", title: " + title +", mTargetId: " + mTargetId);

setActionBarTitle(mConversationType, mTargetId);

if (mConversationType.equals(Conversation.ConversationType.PRIVATE) | mConversationType.equals(Conversation.ConversationType.PUBLIC_SERVICE) | mConversationType.equals(Conversation.ConversationType.DISCUSSION)) {

btnRight.setBackground(getResources().getDrawable(R.drawable.icon1_menu));

}

isPushMessage(intent);

mHandler = new Handler(new Handler.Callback() {

@Override

public boolean handleMessage(Message msg) {

switch (msg.what) {

case SET_TEXT_TYPING_TITLE:

tvTitle.setText(TextTypingTitle);

break;

case SET_VOICE_TYPING_TITLE:

tvTitle.setText(VoiceTypingTitle);

break;

case SET_TARGET_ID_TITLE:

setActionBarTitle(mConversationType, mTargetId);

break;

default:

break;

}

return true;

}

});

RongIMClient.setTypingStatusListener(new RongIMClient.TypingStatusListener() {

@Override

public void onTypingStatusChanged(Conversation.ConversationType type, String targetId, Collection typingStatusSet) {

//当输入状态的会话类型和targetID与当前会话一致时,才需要显示

if (type.equals(mConversationType) && targetId.equals(mTargetId)) {

int count = typingStatusSet.size();

//count表示当前会话中正在输入的用户数量,目前只支持单聊,所以判断大于0就可以给予显示了

if (count > 0) {

Iterator iterator = typingStatusSet.iterator();

TypingStatus status = (TypingStatus) iterator.next();

String objectName = status.getTypingContentType();

MessageTag textTag = TextMessage.class.getAnnotation(MessageTag.class);

MessageTag voiceTag = VoiceMessage.class.getAnnotation(MessageTag.class);

//匹配对方正在输入的是文本消息还是语音消息

if (objectName.equals(textTag.value())) {

mHandler.sendEmptyMessage(SET_TEXT_TYPING_TITLE);

} else if (objectName.equals(voiceTag.value())) {

mHandler.sendEmptyMessage(SET_VOICE_TYPING_TITLE);

}

} else {//当前会话没有用户正在输入,标题栏仍显示原来标题

mHandler.sendEmptyMessage(SET_TARGET_ID_TITLE);

}

}

}

});

}

/**

* 判断是否是 Push 消息,判断是否需要做 connect 操作

*/

private void isPushMessage(Intent intent) {

if (intent == null || intent.getData() == null)

return;

enterFragment(mConversationType, mTargetId);

}

private ConversationFragmentEx fragment;

/**

* 加载会话页面 ConversationFragmentEx 继承自 ConversationFragment

*

* @param mConversationType 会话类型

* @param mTargetId 会话 Id

*/

private void enterFragment(Conversation.ConversationType mConversationType, String mTargetId) {

fragment = new ConversationFragmentEx();

Uri uri = Uri.parse("rong://" + getApplicationInfo().packageName).buildUpon()

.appendPath("conversation").appendPath(mConversationType.getName().toLowerCase())

.appendQueryParameter("targetId", mTargetId).build();

fragment.setUri(uri);

FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();

//xxx 为你要加载的 id

transaction.add(R.id.rong_content, fragment);

transaction.commitAllowingStateLoss();

}

/**

* 设置会话页面 Title

*

* @param conversationType 会话类型

* @param targetId 目标 Id

*/

private void setActionBarTitle(Conversation.ConversationType conversationType, String targetId) {

if (conversationType == null)

return;

if (conversationType.equals(Conversation.ConversationType.PRIVATE)) {

setPrivateActionBar(targetId);

} else if (conversationType.equals(Conversation.ConversationType.SYSTEM)) {

tvTitle.setText("系统消息");

} else {

setTitle("聊天");

}

}

/**

* 设置私聊界面 ActionBar

*/

private void setPrivateActionBar(String targetId) {

if (!TextUtils.isEmpty(title)) {

if (title.equals("null")) {

if (!TextUtils.isEmpty(targetId)) {

UserInfo userInfo = RongUserInfoManager.getInstance().getUserInfo(targetId);

if (userInfo != null) {

tvTitle.setText(userInfo.getName());

}

}

} else {

tvTitle.setText(title);

}

} else {

tvTitle.setText(targetId);

}

}

@Override

protected void onDestroy() {

super.onDestroy();

}

@Override

public void onBackPressed() {

super.onBackPressed();

finish();

}

@Override

public boolean onTouchEvent(MotionEvent event) {

if (null != this.getCurrentFocus()) {

InputMethodManager mInputMethodManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);

return mInputMethodManager.hideSoftInputFromWindow(this.getCurrentFocus().getWindowToken(), 0);

}

return super.onTouchEvent(event);

}

@Override

public void onClick(View v) {

switch (v.getId()) {

case R.id.btn_right:

if (mConversationType == Conversation.ConversationType.PUBLIC_SERVICE

|| mConversationType == Conversation.ConversationType.APP_PUBLIC_SERVICE) {

RongIM.getInstance().startPublicServiceProfile(this, mConversationType, mTargetId);

} else {

UriFragment fragment = (UriFragment) getSupportFragmentManager().getFragments().get(0);

//得到讨论组的 targetId

mTargetId = fragment.getUri().getQueryParameter("targetId");

Intent intent = null;

if (mConversationType == Conversation.ConversationType.PRIVATE) {

// intent = new Intent(this, PrivateChatDetailActivity.class);

intent.putExtra("conversationType", Conversation.ConversationType.PRIVATE);

}

intent.putExtra("TargetId", mTargetId);

if (intent != null) {

startActivityForResult(intent, 500);

}

}

break;

case R.id.btn_left:

finish();

}

}

}

主要的代码已经在上面,最后可以通过融云控制台API调试与你输出的id进行聊天。具体demo请下载哦~

相关文章
最新文章
热点推荐