前言
本篇为开篇,会涉及很多基本的东西,所以篇幅较大,若是觉得行文啰嗦还请各位大佬不要打我。本篇目的,是从0开始到实现单聊则打住,后续的特定需求会新开特定篇来写。若是跟着文章走,遇见了坑请看文末的坑总结章节,相信很快你就能成功接入。去融云开发平台申请key什么的我就不说了,会申请QQ号就会申请这个有什么问题请直接留言指出,欢迎批评指正。跑个官方Demo压压惊
不管怎样,接入三方SDK最快的方式——官方Demo
所以起手我们还是把Demo打开玩一玩
相关资料:
融云IM Demo
融云新手指导
融云Android SDK集成指南
融云Server开发指南
融云开发者平台
融云sdk下载地址
PS:API调试入口:我的控制台——>左侧栏目API调试
运行效果:
Demo在手,天下我有。
分析:
来一个感性的认知。
从这个Demo来看,它的使用场景基本是:
点击一个列表item ——> 进入点对点的聊天界面。
在这个聊天界面内有可以发送许多类型的消息。地图位置,图片,文本等等。
总结下特征就是:
聊天模式(点对点)消息类型(多种)
信息很有限,不过也是最直观的东西,所以我准备以此作为一个切入点来进行聊天的学习。
知识点铺垫
虽然Demo已经跑起来了,但是我还是懵逼的,我们还需要了解下融云相关的知识点。
基本概念
基础概念篇幅太大,其中包含业务篇,开发篇,我准备划俩个相对重要的,其他的则不作赘述,具体参考:/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:zj666name:测试用户呵portraitUri:/-rVXeDTa2gU2pMbgoY3K/it/u=225869644,1383753585&fm=202&mola=new&crop=v1请求结果: {"code":200,"userId":"zj666","token":"wxS1DLbYEHlYRvEGldlHlLv9HaIuPBiUqpl0jm19YznutxcjH0D1Z2Yo81ijEZhZLPWs7qwNNlTGuKduIiTDBg=="}
一顿操作猛如虎
来不及解释了,带上你的老年卡赶紧上车吧,滴,滴滴。
SDK引入
SDK下载地址:/downloads
既然是学习,我就全勾选下载了。
汇总
另外一点疑问,如图:
一般来说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-dataandroid:name="RONG_CLOUD_APP_KEY"android:value="您的应用 AppKey" />
App Module # AndroidManifest.xml (位置依赖相关)
<meta-dataandroid:name="com.amap.api.v2.apikey"android:value="高德地图的 AppKey" />
App Module # AndroidManifest.xml (FileProvider)
<providerandroid:name="android.support.v4.content.FileProvider"android:authorities="您的应用包名.FileProvider"android:exported="false"android:grantUriPermissions="true"><meta-dataandroid:name="android.support.FILE_PROVIDER_PATHS"android:resource="@xml/rc_file_path" /></provider>
<meta-dataandroid:name="com.amap.api.v2.apikey"android:value="高德地图的 AppKey" />
初始化
public class IMApplication extends Application {@Overridepublic void onCreate() {super.onCreate();RongIM.init(this);}}
配置会话列表
不啰嗦,直接看代码,默认为Main为消息页载体:
布局文件:
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="/apk/res/android"xmlns:app="/apk/res-auto"xmlns:tools="/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"tools:context="com.zj.imrydemo.MainActivity"><fragmentandroid: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><!--会话列表--><activityandroid: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" /><dataandroid: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" /><dataandroid:host="com.zj.imrydemo" //注意这儿喲,得写自己包名android:pathPrefix="/conversationlist"android:scheme="rong" /></intent-filter>
看下效果吧:
连接聊天服务器
从上面的图也看到了,“无法连接到服务器”,那么这里我们就来说说如何连接到服务器。
一般的,我们就在进入这个页面之前进行连接,比如我们的闪屏页。
这里我就不写闪屏页了,懒得搞,知道这个意思就行。
String portraitUri = "/6ONYsjip0QIZ8tyhnq/it/u=61649,3957489063&fm=179&w=56&h=56&img.PNG";String nickName = "蛟龙87";String token = "wxS1DLbYEHlYRvEGldlHlLv9HaIuPBiUqpl0jm19YznutxcjH0D1Z2Yo81ijEZhZLPWs7qwNNlTGuKduIiTDBg==";RongIM.connect(token, new RongIMClient.ConnectCallback() {@Overridepublic void onTokenIncorrect() {Log.d(TAG, "onTokenIncorrect");}@Overridepublic void onSuccess(String userId) {Log.d(TAG, "ConnectCallback connect onSuccess=" + userId);}@Overridepublic 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="/apk/res/android"xmlns:app="/apk/res-auto"xmlns:tools="/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"tools:context="com.zj.imrydemo.TalkDetailActivity"><fragmentandroid: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" /><dataandroid: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 {@Overrideprotected 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 '/'// name 'Google'// }maven {url '' }maven {url '/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: mainProcess: com.zj.imrydemo:ipc, PID: 8201java.lang.UnsatisfiedLinkError: dlopen failed: library "libsqlite.so" not found
去下载这个sqlite库放在IMkit的lib下边。
这里我提供了下载地址:/download/user11223344abc/10359170
Hole #4:No static method getFont(Landroid/content/Context;II)
FATAL EXCEPTION: mainProcess: com.zj.imrydemo, PID: 5752java.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' appearsin /data/app/com.zj.imrydemo-1/base.apk)
解决这个坑的方法为:统一各项版本号,一般情况下是统一app/build.gradle。
compile_sdk_version = 27build_tools_version = '27.0.2'target_sdk_version = 27support_version = '27.0.2'
Hole #5:app moudle 内的清单文件找不到 provider 标签。
解决方式:
gradle.properties
android.enableAapt2=false
信息备忘
本文Demo:/zj614android/RongYunIM_single_talk
融云开放平台账号:
账号:410那个Q
密码:123那个密码