This guide only provides a brief integration description. For more details on the usage, see API Documentation.
Android 3.0 (SDK API 11)and above is supported.
build.gradle
file of the project.compile 'com.tuisongbao.android:realtime-engine:2.3.0'
to dependencies
.libs
directory to the project.Declare the version of SDK:
<uses-sdk android:minSdkVersion="11" android:targetSdkVersion="xxx" />
Add permission declarations:
<!-- Network connection. -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<!-- When new messages are received, the application process needs to be woken up -->
<uses-permission android:name="android.permission.WAKE_LOCK" />
Usually you need to intialize an Engine
instance when enabling an application and keep global singleton references. Please see below:
public class DemoApplication extends Application {
public static Engine engine;
@Override
public void onCreate() {
super.onCreate();
// Initialize EngineOptions
// The appId is the ID assigned when you register the application on the TuiSongBao official website; authUrl is used for authentication
EngineOptions options = new EngineOptions(appId , authUrl);
// Initialize Engine
engine = new Engine(this, options);
}
}
The connection is automatically established after instantiation, and you can use the Engine
instance returned for later various operations.
Through engine.getConnection()
, you can get the Connection
instance, on which you can listen for Event to get the connection state. Please see below:
Emitter.Listener stateListener = new Emitter.Listener() {
@Override
public void call(Object... args) {
Connection.State state = (Connection.State)args[0];
Log.i(TAG, "Connection state changed to: " + state.toString());
}
};
connection.bind(Connection.State.Initialized, stateListener);
connection.bind(Connection.State.Connecting, stateListener);
connection.bind(Connection.State.Connected, stateListener);
connection.bind(Connection.State.Disconnected, stateListener);
// This is only the callback of state transition, and doesn't tell the reason. You need to listen for Connection.EVENT_ERROR Event to get the reason for failure
connection.bind(Connection.State.Failed, stateListener);
connection.bind(Connection.EVENT_CONNECT_IN, new Emitter.Listener() {
@Override
public void call(Object... args) {
Log.i(TAG, "Connect in " + args[0] + " seconds");
}
});
connection.bind(Connection.EVENT_STATE_CHANGED, new Emitter.Listener() {
@Override
public void call(Object... args) {
Log.i(TAG, "Connection state changed from " + args[0] + " to " + args[1]);
}
});
connection.bind(Connection.EVENT_ERROR, new Emitter.Listener() {
@Override
public void call(Object... args) {
Log.i(TAG, "Connection error: " + args[0]);
}
});
connection.bind(Connection.State.Connected, new Emitter.Listener() {
@Override
public void call(Object... args) {
String socketId = connection.getSocketId();
}
});
Call disconnect
on the connection
object:
connection.disconnect();
To restore the connection, call connect
:
connection.connect();
The Pub/Sub related functions are completed through ChannelManager
, which can be obtained through engine.getChannelManager()
.
Channel channel = channelManager.subscribe("demo", null);
Calling the above method will return the Channel
instance, which can be used for the bind
operation.
Unsubscribe through the unsubscribe
method:
channelManager.unsubscribe(channelName)
Emitter.Listener listener = new Emitter.Listener() {
@Override
public void call(Object... args) {
String data = args[0].toString();
Log.i(TAG, "Get " + data);
}
};
// Bind the callback function of the event named 'cool-event'
channel.bind("cool-event", listener);
// Unbind a certain callback function of the 'cool-event' event
channel.unbind("cool-event", listener);
// Unbind all the callback functions of the 'cool-event' event
channel.unbind("cool-event");
All the Channels
can handle the subscription result through listening for the EVENT_SUBSCRIPTION_SUCCESS
and EVENT_SUBSCRIPTION_ERROR
Event:
channel.bind(Channel.EVENT_SUBSCRIPTION_SUCCESS, new Emitter.Listener() {
@Override
public void call(Object... args) {
Log.i(TAG, "Subscribe succeeded");
}
});
channel.bind(Channel.EVENT_SUBSCRIPTION_ERROR, new Emitter.Listener() {
@Override
public void call(Object... args) {
String message = args[0].toString();
Log.i(TAG, "Subscribe failed with error: " + message);
}
});
As for Presence Channel and EVENT_SUBSCRIPTION_SUCCESS
, the callback function will get an additional parameter, i.e., the list of current online users:
presenceChannel.bind(Channel.EVENT_SUBSCRIPTION_SUCCESS, new Emitter.Listener() {
@Override
public void call(Object... args) {
List<OnlineUser> users = (List)args[0];
Log.i(TAG, "Subscribe succeeded");
}
});
Additionally, you can listen for EVENT_USER_ADDED
and EVENT_USER_REMOVED
Event on the Presence Channel object to handle notifications of users’ going online and offline:
presenceChannel.bind(PresenceChannel.EVENT_USER_ADDED, new Emitter.Listener() {
@Override
public void call(Object... args) {
User user = (User)args[0];
String id = user.id;
String info = user.info;
}
});
presenceChannel.bind(PresenceChannel.EVENT_USER_REMOVED, new Emitter.Listener() {
@Override
public void call(Object... args) {
User user = (User)args[0];
String id = user.id;
String info = user.info;
}
});
The Chat messages can only be received when the application is opened, otherwise, unable to be received normally. If necessary, the Push SDK can offer the offline notification function. Please refer to Android Push Guide for integration, after which the offline messages will automatically reach user’s mobiles through the Push* service. No additional configuration is required for the realtime engine.
<!--Audio Message-->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
The Chat relate functions are completed through ChatManager
, which can be obtained through engine.getChatManager()
.
chatManager.bind(ChatManager.EVENT_LOGIN_SUCCEEDED, new Emitter.Listener() {
@Override
public void call(Object... args) {
ChatUser user = (ChatUser)args[0];
Log.i(TAG, "Login succeeded");
}
});
chatManager.bind(ChatManager.EVENT_LOGIN_FAILED, new Emitter.Listener() {
@Override
public void call(Object... args) {
Log.i(TAG, "Login failed");
}
});
chatManager.bind(ChatManager.EVENT_PRESENCE_CHANGED, new Emitter.Listener() {
@Override
public void call(Object... args) {
ChatUserPresence data = (ChatUserPresence)args[0];
Log.i(TAG, data.getUserId() + " changed to " + data.getChangedTo());
}
});
chatManager.bind(ChatManager.EVENT_MESSAGE_NEW, new Emitter.Listener() {
@Override
public void call(Object... args) {
ChatMessage message = (ChatMessage)args[0];
}
});
ChatManager.EVENT_LOGIN_SUCCEEDED
Event is suggested so as to sync up the updates during network disconnection.
This operation is asynchronous. Please refer to the above example to listen for the related events to get the result.
chatManager.login(userData);
chatManager.logout(new EngineCallback<String>() {
@Override
public void onSuccess(String t) {
Log.i(TAG, "Successfully logout");
}
@Override
public void onError(ResponseError error) {
Log.i(TAG, "Failed to logout");
}
});
After signing out, SDK will unbind all the ChatManager
related Events.
// Enable cache
chatManager.enableCache()
// Disable cache
chatManager.disableCache()
Get ConversationManager
through chatManager.getConversationManager()
to perform conversation related operations.
Filter through session type and target:
conversationManager.getList(type, target, new EngineCallback<List<ChatConversation>>() {
@Override
public void onSuccess(final List<ChatConversation> t) {
Log.d(TAG, "Get " + t.size() + " conversations");
}
@Override
public void onError(ResponseError error) {
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getActivity(), "Failed to get conversation list, try later", Toast.LENGTH_LONG).show();
}
});
}
});
Developers can call the methods on the ChatConversation
instance to send messages, delete conversations, reset unread messages, and so forth. It can be obtained from the coversation list as mentioned in the above example, or you can create a specific conversation through the following method:
ChatConversation conversation = new ChatConversation(engine);
conversation.setTarget(targetId);
conversation.setType(ChatType.SingleChat);
conversation.sendMessage(message, callback);
conversation.getMessages(startMessageId, null, 20, new EngineCallback<List<ChatMessage>>() {
@Override
public void onSuccess(final List<ChatMessage> t) {
Log.d(TAG, "Get " + t.size() + " messages");
}
@Override
public void onError(ResponseError error) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(ChatConversationActivity.this,
"Failed to get messages, try later", Toast.LENGTH_LONG).show();
}
});
}
});
conversation.resetUnread(new EngineCallback<String>() {
@Override
public void onSuccess(String t) {
Log.i(TAG, "Successfully reset unread count");
}
@Override
public void onError(ResponseError error) {
Log.i(TAG, "Failed to reset unread count");
}
});
conversation.delete(new EngineCallback<String>() {
@Override
public void onSuccess(String t) {
Log.i(TAG, "Successfully delete conversation");
}
@Override
public void onError(ResponseError error) {
Log.i(TAG, "Failed to delete conversation");
}
});
Emitter.Listener messageNewListener = new Emitter.Listener() {
@Override
public void call(Object... args) {
ChatMessage message = (ChatMessage)args[0];
}
};
chatManager.bind(ChatManager.EVENT_MESSAGE_NEW, messageNewListener);
The sendings of callback of all message types are the same. Please see below:
private EngineCallback<ChatMessage> sendMessageCallback = new EngineCallback<ChatMessage>() {
@Override
public void onSuccess(final ChatMessage message) {
runOnUiThread(new Runnable() {
@Override
public void run() {
// Refresh message list
}
});
}
@Override
public void onError(final ResponseError error) {
runOnUiThread(new Runnable() {
@Override
public void run() {
// Notify the user of operation failure
}
});
}
};
Send messages by calling the sendMessage
method on the ChatConversation
object. This method will return ChatMessage
for developers to refresh UI.
Simple Text Message
ChatMessageContent content = new ChatMessageContent();
content.setText("Hello~");
ChatMessage sentMessage = conversation.sendMessage(content, sendMessageCallback);
Multimedia Message
Multimedia messages refer to image, audio and video messages. Developers need to call setFilePath
to specify the absolute path of the multimedia file.
// Use ChatMessageImageContent when sending image messages, and ChatMessageVoiceContent when sending audio messages
ChatMessageVideoContent content = new ChatMessageVideoContent();
content.setFilePath(videoPath);
ChatMessage sentMessage = conversation.sendMessage(content, sendMessageCallback);
SDK provides the downloadThumb
and download
methods for downloading thumbnail and original images respectively. The methods can be called repeatedly. SDK will automatically check whether there is a local cache, and will download from the server if no.
message.getContent().downloadThumb(new EngineCallback<String>() {
@Override
public void onSuccess(final String filePath) {
((ChatConversationActivity) context).runOnUiThread(new Runnable() {
@Override
public void run() {
Bitmap bmp = BitmapFactory.decodeFile(filePath);
imageView.setImageBitmap(bmp);
}
});
}
@Override
public void onError(ResponseError error) {
((ChatConversationActivity) context).runOnUiThread(new Runnable() {
@Override
public void run() {
textView.setText("Failed to load image");
}
});
}
}, new ProgressCallback() {
@Override
public void progress(final int percent) {
((ChatConversationActivity) context).runOnUiThread(new Runnable() {
@Override
public void run() {、
// Download progress prompt
textView.setText(percent + "%");
}
});
}
});
SDK offers ChatVoiceRecorder
for recording audios easily. Developers can also implement it themselves.
// Initializing ChatVoiceRecorder in onCreate is suggested
recorder = new ChatVoiceRecorder();
recorder.setMinDuration(2 * 1000, new Emitter.Listener() {
@Override
public void call(final Object... args) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(),
"duration is " + args[0] + ", less then min duration " + args[1], Toast.LENGTH_SHORT).show();
}
});
}
});
recorder.setMaxDuration(10 * 1000, new Emitter.Listener() {
@Override
public void call(final Object... args) {
Toast.makeText(getApplicationContext(),
"Reach max duration " + args[0] + ", send message automatically", Toast.LENGTH_SHORT).show();
// Call recorder.stop(), and send messages after getting the path of the audio file
onRecordFinished();
// Call recorder.start(), and continue to record audios
onRecordStart();
}
});
// Start recording
recorder.start();
// Stop recording, and return the path of the audio file for sending messages
String filePath = recorder.stop();
// Cancel recording
recorder.cancel();
// Calling this interface in onDestroy is suggested to release resources
recorder.release();
SDK offers ChatVoicePlayer
exclusively for playing the audio resources of TuiSongBao easily. Developers can also impelement it themselves. After calling the start
method, SDK will automatically download the audio file, and automatically play it when download completes, and will execute the onStop
callback when playing is stopped; will execute the onError
callback if an error occurs while downloading or playing the audio, and also tell the reason of the error
. This method can be called repeatedly:
ChatVoicePlayer player = ChatVoicePlayer.getInstance();
player.start(message, new ChatVoicePlayer.OnStopListener() {
@Override
public void onStop() {
Log.d(TAG, "onStop");
}
}, new ChatVoicePlayer.OnErrorListener() {
@Override
public void onError(String error) {
Log.d(TAG, "Failed to play this voice, because " + error);
}
}, new ProgressCallback() {
@Override
public void progress(final int percent) {
((ChatConversationActivity)context).runOnUiThread(new Runnable() {
@Override
public void run() {
// Download progress prompt
}
});
}
});
SurfaceView
, which has not been integrated well in SDK, developers need to record videos themselves, and then send messages through the absolute path of the file.message.getContent().downloadThumb()
to get it, and call message.getContent().download()
to download the video file.SDK contains the helper class to make it easy to get the current location:
// Set time-out with unit "sec"
int timeout = 5;
ChatLocationManager.getInstance().getCurrentLocation(new EngineCallback<Location>() {
@Override
public void onSuccess(Location location) {
Toast.makeText(getApplicationContext(), "Successfully located, and sent the current location", Toast.LENGTH_LONG).show();
// API Please refer to API for other constructor methods
ChatMessageLocationContent content = new ChatMessageLocationContent(location);
mConversation.sendMessage(content, sendMessageCallback, null);
}
@Override
public void onError(ResponseError error) {
Toast.makeText(getApplicationContext(), "Failed to locate", Toast.LENGTH_LONG).show();
}
}, timeout);
Get location content:
content = chatMessage.getContent();
if (content.getType() == TYPE.LOCATION) {
ChatMessageLocationContent location = (ChatMessageLocationContent)content;
String locationDescription = String.format("longitude and latitude:(%s, %s), point of interest: %s"
, location.getLongitude(), location.getLatitude(), location.getPointOfInterest());
}
Perform the group related operations through ChatGroupManager
, which can be obtained by calling the following method:
chatManager.getGroupManager()
groupManager.getList(null, new EngineCallback<List<ChatGroup>>() {
@Override
public void onSuccess(List<ChatGroup> t) {
Log.i(TAG, "Get " + t.size() + " groups");
}
@Override
public void onError(ResponseError error) {
Log.i(TAG, "Failed to get groups, because " + error.getMessage());
}
});
groupManager.create(members, true, false, new EngineCallback<ChatGroup>() {
@Override
public void onSuccess(ChatGroup t) {
Log.i(TAG, "Create group successfully, " + t);
}
@Override
public void onError(ResponseError error) {
Log.i(TAG, "Failed to create group, because " + error.getMessage());
}
});
group.getUsers(new EngineCallback<List<ChatUserPresence>>() {
@Override
public void onSuccess(List<ChatUserPresence> t) {
Log.i(TAG, "There are " + t.size() " members in this group");
}
@Override
public void onError(ResponseError error) {
Log.i(TAG, "Failed to get members, because " + error.getMessage());
}
});
Please see API Documentation 。
All the callbacks are executed in the background thread. To refresh UI, call back to the main thread.
groupManager.getList(null, new EngineCallback<List<ChatGroup>>() {
@Override
public void onSuccess(List<ChatGroup> t) {
runOnUiThread(new Runnable() {
@Override
public void run() {
// Refresh group list
}
});
}
@Override
public void onError(ResponseError error) {
runOnUiThread(new Runnable() {
@Override
public void run() {
// Notify the user of operation failure
}
});
}
});
After integrating with Push SDK, the onCreate
method of the Applciation will be called again when Remote Service
is enabled, so you need to prevent the Engine
SDK from being instantiated twice through the following method:
@Override
public void onCreate() {
super.onCreate();
PushManager.init(this);
// Return directly when the process already exists
String processName = getProcessName(android.os.Process.myPid());
if (processName == null || !processName.equalsIgnoreCase("com.tuisongbao.engine.demo")) {
return;
}
// Intialize EngineOptions
// appId The appId is the ID assigned when you register the application on the official website of TuiSongBao; authUrl is used for authentication
EngineOptions options = new EngineOptions(appId , authUrl);
// Initialize Engine
engine = new Engine(this, options);
}
private String getProcessName(int pid) {
String processName = null;
ActivityManager am = (ActivityManager) this.getSystemService(ACTIVITY_SERVICE);
List<ActivityManager.RunningAppProcessInfo> l = am.getRunningAppProcesses();
Iterator<ActivityManager.RunningAppProcessInfo> i = l.iterator();
while (i.hasNext()) {
ActivityManager.RunningAppProcessInfo info = (i.next());
try {
if (info.pid == pid) {
processName = info.processName;
return processName;
}
} catch (Exception e) {
}
}
return processName;
}
The
bindand
unbindoperations of
EventEmittermust be used in pairs. Maintaining event binding through
HashMap` is recommended, and this can be integrated into Android life cycle events easily:
private Map<String, Emitter.Listener> listenersMap = new HashMap<>();
private void initAllListeners() {
listenersMap.put(ChatManager.EVENT_LOGIN_SUCCEEDED, new Emitter.Listener() {
@Override
public void call(Object... args) {
showToaster("Auto login success");
}
});
listenersMap.put(ChatManager.EVENT_LOGIN_FAILED, new Emitter.Listener() {
@Override
public void call(Object... args) {
showToaster("Auto login failed");
}
});
listenersMap.put(ChatManager.EVENT_PRESENCE_CHANGED, new Emitter.Listener() {
@Override
public void call(Object... args) {
ChatUserPresence data = (ChatUserPresence) args[0];
showToaster(data.getUserId() + " changed to " + data.getChangedTo());
}
});
listenersMap.put(ChatManager.EVENT_MESSAGE_NEW, new Emitter.Listener() {
@Override
public void call(Object... args) {
if (currentPage != 0) {
conversationTextView.setTextColor(getResources().getColor(R.color.red));
}
}
});
}
private void manageListeners(boolean isBind) {
Iterator<String> events = listenersMap.keySet().iterator();
while (events.hasNext()) {
String event = events.next();
Emitter.Listener listener = listenersMap.get(event);
if (isBind) {
chatManager.bind(event, listener);
} else {
chatManager.unbind(event, listener);
}
}
}
Then call manageListeners()
in due time.
Both ChatMessage
and ChatGroup
can be serialized as String
by calling the serialize()
method to be the Extra
delivery of Intent
easily:
ChatConversation conversation = new ChatConversation(engine);
conversation.setTarget(friendsList.get(arg2).getUserId());
conversation.setType(ChatType.SingleChat);
intent.putExtra(ChatConversationActivity.EXTRA_CONVERSATION, conversation.serialize());
startActivity(intent);
You can restore by calling the deserialize()
method in ChatConversationActivity
. Because the reference to Engine
is not able to be passed, you have to specify:
String conversationString = getIntent().getStringExtra(EXTRA_CONVERSATION);
conversation = ChatConversation.deserialize(engine, conversationString);
If no special notes, just replacing the SDK would be enough.
Added multimedia messages, including image and audio messages. The following permission needs to be enabled for recording audio messsages:
<uses-permission android:name="android.permission.RECORD_AUDIO" />
Abstracted ChatGroup
and ChatConversation
for developers to directly call the related methods on these classes to develop easily.
name
and description
fields in the ChatGroup
structure, so the corresponding interface invocations need to be changed accordingly.