前言
- 本篇为开篇,会涉及很多基本的东西,所以篇幅较大,若是觉得行文啰嗦还请各位大佬不要打我。
- 本篇目的,是从0开始到实现单聊则打住,后续的特定需求会新开特定篇来写。
- 若是跟着文章走,遇见了坑请看文末的坑总结章节,相信很快你就能成功接入。
- 去融云开发平台申请key什么的我就不说了,会申请QQ号就会申请这个
- 有什么问题请直接留言指出,欢迎批评指正。
跑个官方Demo压压惊
不管怎样,接入三方SDK最快的方式——官方Demo
所以起手我们还是把Demo打开玩一玩
相关资料:
融云IM Demo
融云新手指导
融云Android SDK集成指南
融云Server开发指南
融云开发者平台
融云sdk下载地址
PS:API调试入口:我的控制台——>左侧栏目API调试
运行效果:
Demo在手,天下我有。
分析:
来一个感性的认知。
从这个Demo来看,它的使用场景基本是:
点击一个列表item ——> 进入点对点的聊天界面。
在这个聊天界面内有可以发送许多类型的消息。地图位置,图片,文本等等。
总结下特征就是:
- 聊天模式(点对点)
- 消息类型(多种)
信息很有限,不过也是最直观的东西,所以我准备以此作为一个切入点来进行聊天的学习。
知识点铺垫
虽然Demo已经跑起来了,但是我还是懵逼的,我们还需要了解下融云相关的知识点。
基本概念
基础概念篇幅太大,其中包含业务篇,开发篇,我准备划俩个相对重要的,其他的则不作赘述,具体参考:http://www.rongcloud.cn/docs/quick_start.html#basic_concept
App Key / Secret
App Key / Secret 相当于您的 App 在融云的帐号和密码。是融云 SDK 连接服务器所必需的标识,每一个 App 对应一套 App Key / Secret。
Token
- Token 即用户令牌,相当于您 APP 上当前用户连接融云的身份凭证。每个用户连接服务器都需要一个 Token,用户更换即需要更换 Token。每次初始化连接服务器时,都需要向服务器提交 Token。
- Token 称为用户令牌。
- Token 则是您 App 上的每一个用户的身份授权象征。
- 您可以通过提交 userId 等信息来获得一个该用户对应的 Token,并使用这个 Token 作为该用户的唯一身份凭证与其他用户进行通信。
- Token 的主要作用是身份授权和安全,因此不能通过客户端直接访问融云服务器获取 Token,您必须通过 Server API 从融云服务器 获取 Token 返回给您的 App,并在之后连接时使用。详细描述请参考 Server开发指南中的用户服务和获取Token方法小节。
官方流程图:
分析:
我们的最终目的是通过connect方法去连接聊天服务器,但是官方服务器需要我们带Token,但是token我们没有并且也不能自己维护token,所以我们就需要去请求服务器拿token,然而要拿这个token我们就需要从本地拿到我们登录时得到的userId作为一个请求参数去哪token,这样一个流程走下来我们就能去连接聊天服务器了。
那么我们如何获取一个测试token呢?
这就要用到官方提供的API调试工具了,接着往下看。
Api调试的的使用
利用Api调试获取测试token
最开始我就PS了,API调试入口:我的控制台——>左侧栏目API调试
这个东西在我们初期调试时很重要,因为你也可以看到token是通信必须,而我们在前期服务器没调试完成的情况下,如何来拿这个token呢?这个时候就需要用到api调试这个功能。
如图:
可以看到这里我们获取到的信息是:
userId:zj666
name:测试用户呵
portraitUri:https://ss3.baidu.com/-rVXeDTa2gU2pMbgoY3K/it/u=225869644,1383753585&fm=202&mola=new&crop=v1
请求结果:
{
"code":200,
"userId":"zj666",
"token":"wxS1DLbYEHlYRvEGldlHlLv9HaIuPBiUqpl0jm19YznutxcjH0D1Z2Yo81ijEZhZLPWs7qwNNlTGuKduIiTDBg=="
}
一顿操作猛如虎
来不及解释了,带上你的老年卡赶紧上车吧,滴,滴滴。
SDK引入
SDK下载地址:http://www.rongcloud.cn/downloads
既然是学习,我就全勾选下载了。
汇总
Name | Type | Desc | 引入姿势 |
---|---|---|---|
IMKit | 界面 | 融云 IM 界面组件 | Import Moudle |
CallKit | 界面 | 融云音视频界面组件 | Import Moudle |
IMLib | 库 | 融云 IM 通讯能力库 armeabi, armeabi-v7a, arm64-v8a, x86 | Import Moudle |
CallLib | 库 | 融云音视频核心组件 armeabi-v7a, x86 | Import Moudle |
LocationLib | 库 | 融云位置相关库,高德地图的低版本jar包。 | copy jar int your libs direct |
RedPacket | 库 | 融云红包相关库 | Import Moudle |
另外一点疑问,如图:
一般来说Kit和Lib都下载,因为它lib里边的库不全,但你用的话可以选择性的用,但是一点,用Kit必用Lib,但Kit不是必须的。
引入后的项目目录结构
配置文件
gradle文件依赖
dependencies {
//kit 内依赖了lib 所以不必再依赖lib 又因为第一行 fileTree ,所以不必再额外依赖location jar包
compile project(':IMKit')
compile project(':CallKit')
compile project(':RedPacket')
}
IMLib Module # AndroidManifest.xml
<meta-data
android:name="RONG_CLOUD_APP_KEY"
android:value="您的应用 AppKey" />
App Module # AndroidManifest.xml (位置依赖相关)
<meta-data
android:name="com.amap.api.v2.apikey"
android:value="高德地图的 AppKey" />
App Module # AndroidManifest.xml (FileProvider)
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="您的应用包名.FileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/rc_file_path" />
</provider>
<meta-data
android:name="com.amap.api.v2.apikey"
android:value="高德地图的 AppKey" />
初始化
public class IMApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
RongIM.init(this);
}
}
配置会话列表
不啰嗦,直接看代码,默认为Main为消息页载体:
布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.zj.imrydemo.MainActivity">
<fragment
android:id="@+id/conversationlist"
android:name="io.rong.imkit.fragment.ConversationListFragment"
android:layout_width="match_parent"
android:layout_height="0px"
android:layout_weight="1" />
</LinearLayout>
看见没,他这个是直接写死Fragment:
android:name=”io.rong.imkit.fragment.ConversationListFragment”
清单文件:
<application>
<!--会话列表-->
<activity
android:name=".MainActivity"
android:screenOrientation="portrait"
android:windowSoftInputMode="stateHidden|adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data
android:host="com.zj.imrydemo"
android:pathPrefix="/conversationlist"
android:scheme="rong" />
</intent-filter>
</activity>
</application>
这人注意下我是双Intent-Fillter,实际上只需要一个。另外看下注释,有个host得改成自己的包名。
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data
android:host="com.zj.imrydemo" //注意这儿喲,得写自己包名
android:pathPrefix="/conversationlist"
android:scheme="rong" />
</intent-filter>
看下效果吧:
连接聊天服务器
从上面的图也看到了,”无法连接到服务器”,那么这里我们就来说说如何连接到服务器。
一般的,我们就在进入这个页面之前进行连接,比如我们的闪屏页。
这里我就不写闪屏页了,懒得搞,知道这个意思就行。
String portraitUri = "https://ss2.baidu.com/6ONYsjip0QIZ8tyhnq/it/u=616492013,3957489063&fm=179&w=56&h=56&img.PNG";
String nickName = "蛟龙87";
String token = "wxS1DLbYEHlYRvEGldlHlLv9HaIuPBiUqpl0jm19YznutxcjH0D1Z2Yo81ijEZhZLPWs7qwNNlTGuKduIiTDBg==";
RongIM.connect(token, new RongIMClient.ConnectCallback() {
@Override
public void onTokenIncorrect() {
Log.d(TAG, "onTokenIncorrect");
}
@Override
public void onSuccess(String userId) {
Log.d(TAG, "ConnectCallback connect onSuccess=" + userId);
}
@Override
public void onError(RongIMClient.ErrorCode errorCode) {
Log.d(TAG, "ConnectCallback connect onError-ErrorCode=" + errorCode);
}
});
重新运行之后,观察输出结果:
04-18 17:46:31.908 27639-27639/com.zj.imrydemo D/RongYunAbout: ConnectCallback connect onSuccess=zj666
这只是连接上了服务器罢了,可以注意到返回的参数是我们的userId.
但这离我们实现单聊的目的还差一丢丢,咱接着往下走。
单聊实现
若你是一步步跟着坐过来的,你会发现你在api调试上发一条消息到本机,消息是能收到,但是呢,列表没动静。
言归正传,会产生上述问题的原因是,我们还少干了俩件事。
初始化ConversationListFragment
private void initConversationList() {
FragmentManager supportFragmentManager = getSupportFragmentManager();
ConversationListFragment conversationlistFragment = (ConversationListFragment) supportFragmentManager.findFragmentById(R.id.conversationlist);
conversationlistFragment.getActivity();
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.DISCUSSION.getName(), "false")//设置讨论组会话非聚合显示
.appendQueryParameter(Conversation.ConversationType.SYSTEM.getName(), "false")//设置系统会话非聚合显示
.build();
conversationlistFragment.setUri(uri);
}
是不是这波去拿静态fragment实例的操作有点骚?是的,因为我们xml里是静态定义的fargment,所以我们就这么拿,但也可以动态的去定义,动态的定义也是我们平时项目中常用的fragment用法,所以这里我也不多说了,注意下Fragment是v4的fragment以及其manager就行。
定义聊天详情页面
这一步也不能省的,因为如果你不定义这个,那么你点击的消息列表内就无法进入聊天详情页面。
- 新建一个Activity,(是FragmentActivity的子类)
- 定义它的布局,类似于上面回话列表的布局内一个静态的fragment
- 清单文件内进行配置
注意这三个点,然后我分别贴出相关代码:
activity_talk_detail.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.zj.imrydemo.TalkDetailActivity">
<fragment
android:id="@+id/conversation"
android:name="io.rong.imkit.fragment.ConversationFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>
清单内
<activity android:name=".TalkDetailActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data
android:host="com.zj.imrydemo"
android:pathPrefix="/conversation/"
android:scheme="rong" />
</intent-filter>
</activity>
TalkDetailActivity
package com.zj.imrydemo;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
public class TalkDetailActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_talk_detail);
}
}
能够坚持到了这一步,说明骚年你是个有耐心的人,你的努力终将得到回报,现在就把我的力量赐予你吧,骚年,敞开双臂接受我仁慈的一击吧。
Holes (遇见的坑)
Hole #1:Failed to resolve: com.android.support:support-v4:26.0.0
解决:
allprojects {
repositories {
jcenter()
mavenCentral()
maven { url "https://jitpack.io" }
// maven {
// url 'https://maven.google.com/'
// name 'Google'
// }
maven { url 'https://maven.google.com' }
maven {url 'http://developer.huawei.com/repo/'}
}
}
Hole #2:Installation failed with message INSTALL_FAILED_DUPLICATE_PERMISSION: Package cn.rongcloud.im attempting to redeclare permission cn.rongcloud.im.permission.C2D_MESSAGE already owned by com.aaa.bbb.
2个关键词:
- INSTALL_FAILED_DUPLICATE_PERMISSION
android编译运行时的结果码。DUPLICATE_PERMISSION,意为权限重复,什么权限重复?看下一条。
- Package cn.rongcloud.im attempting to redeclare permission cn.rongcloud.im.permission.C2D_MESSAGE already owned by com.aaa.bbb
Package cn.rongcloud.im尝试重新定义权限:cn.rongcloud.im.permission.C2D_MESSAG,但是它已经被com.aaa.bbb抢占了。但是这个权限究竟是用来干嘛的呢?
目前我了解是推送相关的需要这个权限。如果说它具备唯一性,那么可能会出现一个手机上,同时俩个app集成了融云的情况下(因为cn.rongcloud.im.permission是官方SDK自带的定义),有可能会导致后安装的app安装不上。如果是,那么这个问题是很严重的,本文着重走大流程,这个问题也就不展开了,所以这里暂时存疑,等后面测试的时候再来验证这个问题。
解决:
- 将与之冲突的包删除。
- DIY融云包内的权限名前缀(未验证可行与否)。
Hole #3:libSqlite
FATAL EXCEPTION: main
Process: com.zj.imrydemo:ipc, PID: 8201
java.lang.UnsatisfiedLinkError: dlopen failed: library "libsqlite.so" not found
去下载这个sqlite库放在IMkit的lib下边。
这里我提供了下载地址:https://download.csdn.net/download/user11223344abc/10359170
Hole #4:No static method getFont(Landroid/content/Context;II)
FATAL EXCEPTION: main
Process: com.zj.imrydemo, PID: 5752
java.lang.NoSuchMethodError: No static method getFont(Landroid/content/Context;II)
Landroid/support/v4/graphics/TypefaceCompat$TypefaceHolder; in class Landroid/support/v4/content/res/ResourcesCompat;
or its super classes (declaration of 'android.support.v4.content.res.ResourcesCompat' appears
in /data/app/com.zj.imrydemo-1/base.apk)
解决这个坑的方法为:统一各项版本号,一般情况下是统一app/build.gradle。
compile_sdk_version = 27
build_tools_version = '27.0.2'
target_sdk_version = 27
support_version = '27.0.2'
Hole #5:app moudle 内的清单文件找不到 provider 标签。
解决方式:
gradle.properties
android.enableAapt2=false
信息备忘
本文Demo:https://github.com/zj614android/RongYunIM_single_talk
融云开放平台账号:
账号:410那个Q
密码:123那个密码