0. gradle 文件配置

  1. android {
  2. defaultConfig {
  3. ndk {
  4. abiFilters "armeabi", "armeabi-v7a", "arm64-v8a"
  5. }
  6. manifestPlaceholders = [
  7. JPUSH_PKGNAME: applicationId,
  8. JPUSH_APPKEY : "5b28aec225261f4d6f8db68b",
  9. JPUSH_CHANNEL: "developer-default",
  10. ]
  11. }
  12. compileOptions {
  13. sourceCompatibility 1.8
  14. targetCompatibility 1.8
  15. }
  16. }
  17. dependencies {
  18. implementation 'androidx.appcompat:appcompat:1.1.0'
  19. implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
  20. implementation 'me.luzhuo.java.android:lib_common_ui:1.0.12-SNAPSHOT'
  21. implementation 'me.luzhuo.java.android:emoji:1.0.0-SNAPSHOT'
  22. implementation 'me.luzhuo.java.android:lib_core:1.0.115-SNAPSHOT'
  23. implementation 'me.luzhuo.java.android:lib_permission:1.0.1-SNAPSHOT'
  24. implementation 'me.luzhuo.java.android:lib_image_select:1.1.12-SNAPSHOT'
  25. implementation 'org.greenrobot:eventbus:3.2.0'
  26. implementation 'com.github.bumptech.glide:glide:4.10.0'
  27. implementation project(path: ':lib_im')
  28. implementation project(path: ':lib_map_gaode')
  29. }

1. Application 初始化

  1. // im
  2. IMManager.init(this)

2. 必须提前申请的权限

// 语音通话必要权限 Manifest.permission.RECORDAUDIO Manifest.permission.MODIFY_AUDIO_SETTINGS Manifest.permission._CAMERA // 提醒 Manifest.permission.VIBRATE

  1. Permission.request(this, object: PermissionCallback(){
  2. override fun onGranted() {
  3. IMRTCManager.reInit()
  4. }
  5. override fun onDenieds(denieds: MutableList<String>?) {
  6. toast("请授予权限, 否则语音通话功能将无法使用")
  7. }
  8. }, Manifest.permission.RECORD_AUDIO, Manifest.permission.MODIFY_AUDIO_SETTINGS, Manifest.permission.CAMERA, Manifest.permission.VIBRATE)

3. 登录与退出登录

  1. JunYueRetrofitManager.login(this, phone, password).subscribe(object : JunYueHttpObserver<LoginBean>() {
  2. override fun onSuccess(warning: String?, t: LoginBean?) {
  3. IMUser.login(true, phone, "123456", object : IMUser.IUserCallback {
  4. override fun onSuccess() {
  5. // 登录成功
  6. LoginBeanUtils.save(t)
  7. Main.startMain(this@LoginActivity)
  8. finish()
  9. }
  10. override fun onError(err: String?) {
  11. toast(err)
  12. }
  13. })
  14. }
  15. override fun onError(traceId: String?, errMessage: String?) {
  16. toast(errMessage)
  17. }
  18. })
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_xxxuser_ 开头视为其他应用(用户端应用)

/**
 * 根据 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() }