android项目实践之融云聊天服务端与客户端的实现
来源:互联网 发布:端口80被占用 编辑:程序博客网 时间:2024/06/06 00:50
最近研究了即时通讯,当然用户是第三方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> <!-- imlib config begin --> <meta-data android:name="RONG_CLOUD_APP_KEY" android:value="你的app key" />
4、初始化融云SDK。建一个类继承Application(android启动先走Application)在OnCreate()方法中初始化,初始化语句:RongIM.init(this);。同时AndroidManifest中application取名为App
<application android:name=".App" android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme">
二、服务端连接融云获取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<Fragment> 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<List<Conversation>>() { @Override public void onSuccess(List<Conversation> 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<TypingStatus> 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下载
- android项目实践之融云聊天服务端与客户端的实现
- poll之客户端与服务端聊天
- 聊天室的服务端和客户端实现多人聊天
- 文件下载之断点续传(客户端与服务端的实现)
- RSA客户端(android/ios)与服务端的通信实现
- Android实现Thrift服务端与客户端
- java socket 服务端与客户端聊天
- 实践Android客户端与服务端之间使用JSON交互数据。
- android服务端与客户端
- android客户端简单的聊天程序实现
- Android客户端简单的聊天程序实现
- android 客户端简单的聊天程序实现
- TCP中的服务端与客户端的实现
- socket实现服务端与客户端的通讯
- Fresco之客户端与服务端的交互
- Android客户端与服务端的交互方式
- java实现客户端与服务端互传信息聊天(带界面)
- Android aidl项目中服务端与客户端aidl文件不一致引起的问题
- 2017年4月11日携程笔试题 乘积最大
- IO标准库——③文件输入输出
- IO标准库——④内存输入输出
- 剑指offer-面试题37-两个链表的第一个公共结点
- 二路归并(JAVA实现)
- android项目实践之融云聊天服务端与客户端的实现
- PHP任务录之小试身手--渐入佳境
- Android DrawerLayout使用总结
- 一张图说明unity3d shader 入门
- 深夜切题——Keyboard of a Mobile Telephone
- hbase性能调优
- 第十五届北京师范大学程序设计竞赛 [(6+1)/11,待补]
- Linux命令笔记(一)——ls和cat
- 剑指offer----链表中环的入口节点