0. gradle 文件配置
android {
defaultConfig {
ndk {
abiFilters "armeabi", "armeabi-v7a", "arm64-v8a"
}
manifestPlaceholders = [
JPUSH_PKGNAME: applicationId,
JPUSH_APPKEY : "5b28aec225261f4d6f8db68b",
JPUSH_CHANNEL: "developer-default",
]
}
compileOptions {
sourceCompatibility 1.8
targetCompatibility 1.8
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'me.luzhuo.java.android:lib_common_ui:1.0.12-SNAPSHOT'
implementation 'me.luzhuo.java.android:emoji:1.0.0-SNAPSHOT'
implementation 'me.luzhuo.java.android:lib_core:1.0.115-SNAPSHOT'
implementation 'me.luzhuo.java.android:lib_permission:1.0.1-SNAPSHOT'
implementation 'me.luzhuo.java.android:lib_image_select:1.1.12-SNAPSHOT'
implementation 'org.greenrobot:eventbus:3.2.0'
implementation 'com.github.bumptech.glide:glide:4.10.0'
implementation project(path: ':lib_im')
implementation project(path: ':lib_map_gaode')
}
1. Application 初始化
// im
IMManager.init(this)
2. 必须提前申请的权限
// 语音通话必要权限 Manifest.permission.RECORDAUDIO Manifest.permission.MODIFY_AUDIO_SETTINGS Manifest.permission._CAMERA // 提醒 Manifest.permission.VIBRATE
Permission.request(this, object: PermissionCallback(){
override fun onGranted() {
IMRTCManager.reInit()
}
override fun onDenieds(denieds: MutableList<String>?) {
toast("请授予权限, 否则语音通话功能将无法使用")
}
}, Manifest.permission.RECORD_AUDIO, Manifest.permission.MODIFY_AUDIO_SETTINGS, Manifest.permission.CAMERA, Manifest.permission.VIBRATE)
3. 登录与退出登录
JunYueRetrofitManager.login(this, phone, password).subscribe(object : JunYueHttpObserver<LoginBean>() {
override fun onSuccess(warning: String?, t: LoginBean?) {
IMUser.login(true, phone, "123456", object : IMUser.IUserCallback {
override fun onSuccess() {
// 登录成功
LoginBeanUtils.save(t)
Main.startMain(this@LoginActivity)
finish()
}
override fun onError(err: String?) {
toast(err)
}
})
}
override fun onError(traceId: String?, errMessage: String?) {
toast(errMessage)
}
})
app_exit.setOnClickListener {
AppManager.loginOut()
JunYueUserInfo.clear()
IMUserManager.logout()
LoginActivity.start(JunyueApplication.instance.applicationContext)
}
4. 发送信息**
IMSendMessager.sendText(ConversationType.Single, "1024"/*userid*/, "bilibala"/*内容*/, null, object : IMSendMessager.SendMessageCallback{
override fun onOK() {
toast("onOK")
}
override fun onErr(code: Int, errMessage: String?) {
toast(errMessage)
}
})
1. 给其他应用发消息
给其他应用发消息, 仅需把 AppKey 带上即可. 实际应用中可以根据userid来区分应用, 比如
user_xxx
为user_
开头视为其他应用(用户端应用)
/**
* 根据 UserId 获取 AppKey
*/
private static String getAppkey(String userid) {
if (TextUtils.isEmpty(userid)) {
return cn.jmessage.b.a.a.a();
} else {
if (userid.startsWith("user_")) {
return "e90aa010aeee4167585d32a0";
} else {
return cn.jmessage.b.a.a.a();
}
}
}
2. 打开详情页后的发送消息
// 方式一: 不关闭详情页, 直接发送
JunYueIMSendMessager.sendXXXMessage(username, ... , object : IMSendMessager.IMessageCallback{
override fun onMessage(message: Message?) {
EventBus.getDefault().post(ReceivedMessageEvent(ConversationType.Single, username, message))
requireActivity().finish()
}
}, null)
// 方式二: 关闭详情页, 然后再次打开
JunYueIMSendMessager.sendXXXMessage(username, ... , null, object: IMSendMessager.SendMessageCallback{
override fun onErr(code: Int, errMessage: String?) {
toast("发送失败, 请重试!")
}
override fun onOK() {
toast("发送成功")
IMStartUtils.startSingleDetail(requireContext(), username)
requireActivity().finish()
}
})
5. 界面
1. 单聊列表 (Fragment)
SingleConversationList()
2. 群聊列表 (Fragment)
GroupConversationList()
3. 详情页
<activity android:name="com.saltedfish.pethome.module.me.message.ConversitionDetailActivity2"
android:screenOrientation="portrait"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data
android:host="im"
android:path="/single/${applicationId}"
android:scheme="luzhuo" />
</intent-filter>
</activity>
<activity android:name="com.saltedfish.pethome.module.me.message.GroupDetailActivity2"
android:screenOrientation="portrait"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data
android:host="im"
android:path="/group/${applicationId}"
android:scheme="luzhuo" />
</intent-filter>
</activity>
1. 单聊详情页
public class ConversitionDetailActivity2 extends BaseActivity {
@Override
public int setContentId() {
return R.layout.activity_conversation_detail;
}
@Override
public void onCreated() {
final IMStartUtils imstart = new IMStartUtils(this);
String targetId = imstart.parseSingleDetail(getIntent().getData());
if (targetId == null) return;
((TextView) findViewById(R.id.wansuo_common_title)).setText(IMConversationManager.getConversationTitle(ConversationType.Single, targetId, null));
final FragmentManager fragmentManager = new FragmentManager(this, R.id.frame);
fragmentManager.replaceFragment(SingleDetailConversation.instance(targetId));
}
@Override
public void initEvent() {
}
}
2. 群聊详情页
class GroupDetailActivity2 : BaseActivity() {
override fun setContentId(): Int = R.layout.activity_conversation_detail
override fun onCreated() {
val imStart = IMStartUtils(this)
val groupId = imStart.parseGroupDetail(intent.data)
if (groupId == 0L) return
wansuo_common_title.text = IMConversationManager.getConversationTitle(ConversationType.Group, groupId.toString(), null)
val fragmentManager = FragmentManager(this, R.id.frame)
fragmentManager.replaceFragment(GroupDetailConversation.instance(groupId))
}
override fun initEvent() {
}
}
4. 地图
<activity android:name=".main.activity.IMMapShowActivity"
android:screenOrientation="portrait"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data
android:host="im"
android:path="/map/show/${applicationId}"
android:scheme="luzhuo" />
</intent-filter>
</activity>
<activity android:name=".main.activity.IMMapActivity"
android:screenOrientation="portrait"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data
android:host="im"
android:path="/map/select/${applicationId}"
android:scheme="luzhuo" />
</intent-filter>
</activity>
1. 地图展示
public class IMMapShowActivity extends BaseActivity {
@Override
public int setContentId() {
return R.layout.activity_conversation_detail;
}
@Override
public void onCreated() {
final IMMapStartUtils startUtils = new IMMapStartUtils(this);
MapSelectEvent selectEvent = startUtils.parseShowMap(getIntent().getData());
if (selectEvent == null) return;
LocationBean location = new LocationBean(selectEvent.lat, selectEvent.lon, selectEvent.title, selectEvent.address);
final FragmentManager fragmentManager = new FragmentManager(this, R.id.frame);
fragmentManager.replaceFragment(IMMapShowFragment.instance(location));
}
@Override
public void initEvent() {
}
}
2. 地图选择
public class IMMapActivity extends BaseActivity {
@Override
public int setContentId() {
return R.layout.activity_conversation_detail;
}
@Override
public void onCreated() {
final IMMapStartUtils startUtils = new IMMapStartUtils(this);
IMMapStartUtils.IMSelectMapData imSelectMapData = startUtils.parseSelectMap(getIntent().getData());
if (imSelectMapData == null) return;
final FileManager fileManager = new FileManager();
final IMMapPickerFragement fragment = new IMMapPickerFragement();
final FragmentManager fragmentManager = new FragmentManager(this, R.id.frame);
fragmentManager.replaceFragment(fragment);
findViewById(R.id.wansuo_common_return).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
fragment.screentShot(new OnScreentShotCallback() {
@Override
public void onScreentShotCallback(Bitmap bitmap) {
try {
String filepath = fileManager.getFileDirectory(IMMapActivity.this) + File.separator + UUID.randomUUID().toString().replace("-", "") + ".png";
fileManager.Bitmap2PNGFile(bitmap, filepath);
final LocationBean picker = fragment.getCurrentPicker();
final int mapType = imSelectMapData.type;
final String name = imSelectMapData.targetId;
if (mapType == ConversationType.Single.ordinal()) EventBus.getDefault().post(new MapSelectEvent(ConversationType.Single.ordinal(), name, filepath, "", picker.latitude, picker.longitude, picker.city, picker.title, picker.address));
else if (mapType == ConversationType.Group.ordinal()) EventBus.getDefault().post(new MapSelectEvent(ConversationType.Group.ordinal(), name, filepath, "", picker.latitude, picker.longitude, picker.city, picker.title, picker.address));
else if (mapType == ConversationType.ChatRoom.ordinal()) EventBus.getDefault().post(new MapSelectEvent(ConversationType.ChatRoom.ordinal(), name, filepath, "", picker.latitude, picker.longitude, picker.city, picker.title, picker.address));
finish();
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(IMMapActivity.this, "返回截图失败", Toast.LENGTH_SHORT).show();
}
}
});
}
});
}
@Override
public void initEvent() {
}
}
5. 需要处理的事件
@Subscribe(threadMode = ThreadMode.MAIN)
fun XXX(event: MainEvent) {
if (event.type == TypeLogin) { // 账户在其他地方登录
if (!TextUtils.isEmpty(event.username) && event.username == ("emp_" + JunYueUserInfo.userId())) {
JunYueUserInfo.clear()
AppManager.loginOut()
IMUserManager.logout()
LoginActivity.start(JunyueApplication.instance.applicationContext)
toast("你的账号在其他地方登录")
}
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun XXX(event: MainEvent) {
if (event.type == TypeUnreader) { // 未读消息
viewModel.unreadNUm.value = IMConversationManager.getAllUnread()
}
}
viewModel.unreadNUm.observe(this, Observer<Int> { t -> unreadTabIndicatorView?.setTabUnreadCount(t?:0) })
val unreadNUm = MutableLiveData<Int>().apply { value = IMConversationManager.getAllUnread() }