最近研究了即时通讯,当然用户是第三方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。
<application>
<meta-data
android:name="RONG_CLOUD_APP_KEY"
android:value="你的app key" />
4、初始化融云SDK。建一个类继承Application(android启动先走Application)在OnCreate()方法中初始化,初始化语句:RongIM.init(this);。同时AndroidManifest中application取名为App
".App"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
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请下载哦~
融云Demo下载