Using Android websocket to realize instant messaging

Time:2021-5-13

Do this function recently, share it. There is no doubt that instant messaging is the most important, without obvious delay. It is not difficult to realize the function of IM. At present, there are many third parties, such as jmessage of Aurora, which are relatively easy to implement. But if the project has special requirements (such as not using the Internet), we have to do it ourselves, so we need to use websocket.

WebSocket

Websocket protocol is not detailed. Interested parties can refer to specific information. In short, it is a full duplex communication protocol that can establish a long connection, allowing the server to actively send information to the client.

Java websocket framework

For the use of websocket protocol, there are some mature frameworks on the Android side. After comparison, I chose Java websocket as the open source framework. GitHub address: https://github.com/TooTallNate/Java-WebSocket At present, there are more than 5000 stars, and they are still being updated and maintained, So this article will introduce how to use this open source library to achieve a stable instant messaging function.

design sketch

International practice, effect picture first

Key points of the article

1. Establishing long connection with websocket

2. Instant messaging with websocket

3. Communication and UI update between service and activity

4. Pop up message notification (including lock screen notification)

5. Heartbeat detection and reconnection (ensure websocket connection stability)

6. Keep alive with service

1、 Introducing Java websocket

1. Build. Gradle


implementation "org.java-websocket:Java-WebSocket:1.4.0" 

2. Join network request permission


<uses-permission android:name="android.permission.INTERNET" />

3. New client class

To create a new client class and inherit websocketclient, you need to implement its four abstract methods and constructors, as follows:


public class JWebSocketClient extends WebSocketClient {
 public JWebSocketClient(URI serverUri) {
  super(serverUri, new Draft_6455());
 }
 @Override
 public void onOpen(ServerHandshake handshakedata) {
  Log.e("JWebSocketClient", "onOpen()");
 }
 @Override
 public void onMessage(String message) {
  Log.e("JWebSocketClient", "onMessage()");
 }
 @Override
 public void onClose(int code, String reason, boolean remote) {
  Log.e("JWebSocketClient", "onClose()");
 }
 @Override
 public void onError(Exception ex) {
  Log.e("JWebSocketClient", "onError()");
 }
}

The onopen() method is called when the websocket connection is open, the onmessage() method is called when the message is received, the onclose() method is called when the connection is disconnected, and the onerror() method is called when the connection is in error. New draft in construction method_ 6455 () represents the version of the protocol to be used, which can be omitted or written like this.

4. Establishing websocket connection

To establish a connection, you only need to initialize the client and then call the connection method. It should be noted that the websocketclient object cannot be used repeatedly, so it cannot be initialized repeatedly. In other places, you can only call the current client.

URI uri = URI.create("ws://*******");
JWebSocketClient client = new JWebSocketClient(uri) {
 @Override
 public void onMessage(String message) {
  //Message is the received message
  Log.e("JWebSClientService", message);
 }
};

To facilitate the processing of the received message, you can override the onmessage () method here. When initializing the client, you need to pass in the websocket address (test address: WS: / / echo. Websocket. ORG). The websocket protocol address is roughly like this

Ws: / / IP address: port number

When connecting, you can use the connect() method or the connect blocking() method. It is recommended to use the connect blocking() method. There is one more waiting operation in connect blocking, which will connect first and then send.


try {
 client.connectBlocking();
} catch (InterruptedException e) {
 e.printStackTrace();
}

After running, you can see that the onopen () method of the client has been executed, indicating that a connection has been established with websocket

5. Send message

To send a message, just call the send () method, as follows

if (client != null && client.isOpen()) {
 Client.send ("hello");
}

6. Close socket connection

Close the connection and call the close () method. Finally, in order to avoid repeatedly instantiating the websocketclient object, the object must be empty when closing.

/**
 *Disconnect
 */
private void closeConnect() {
 try {
  if (null != client) {
   client.close();
  }
 } catch (Exception e) {
  e.printStackTrace();
 } finally {
  client = null;
 }
}

2、 Background operation

Generally speaking, instant messaging functions want to keep running in the background like apps like QQ and wechat. Of course, the problem of keeping app alive is a false proposition. We can only keep it alive as much as possible, so the first thing is to build a service, put the logic of websocket into the service and keep it alive as much as possible, so that websocket can keep connected.

1. New service

Create a new service, instantiate websocketclient object and establish a connection when starting the service, and move the above code to the service.

2. Communication between service and activity

Because the message is received in the service and sent from the activity, the websocketclient object in the service needs to be obtained, so the communication between the service and the activity needs to be carried out, which requires the onbind() method in the service.

First, create a new binder class, let it inherit from binder, and provide corresponding methods internally. Then, return an instance of this class in onbind() method.

public class JWebSocketClientService extends Service {
 private URI uri;
 public JWebSocketClient client;
 private JWebSocketClientBinder mBinder = new JWebSocketClientBinder();

 //For communication between activity and service
 class JWebSocketClientBinder extends Binder {
  public JWebSocketClientService getService() {
   return JWebSocketClientService.this;
  }
 }
 @Override
 public IBinder onBind(Intent intent) {
  return mBinder;
 }
}

Next, you need to bind the corresponding activity to service and get the service. The code is as follows

public class MainActivity extends AppCompatActivity {
 private JWebSocketClient client;
 private JWebSocketClientService.JWebSocketClientBinder binder;
 private JWebSocketClientService jWebSClientService;
 private ServiceConnection serviceConnection = new ServiceConnection() {
  @Override
  public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
   //Service and activity bound successfully
   Log. E ("mainactivity", "service and activity bound successfully");
   binder = (JWebSocketClientService.JWebSocketClientBinder) iBinder;
   jWebSClientService = binder.getService();
   client = jWebSClientService.client;
  }
  @Override
  public void onServiceDisconnected(ComponentName componentName) {
   //Service and activity disconnection
   Log. E ("mainactivity", "service and activity disconnected successfully");
  }
 };
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  bindService();
 }
 /**
  *Binding service
  */
 private void bindService() {
  Intent bindIntent = new Intent(MainActivity.this, JWebSocketClientService.class);
  bindService(bindIntent, serviceConnection, BIND_AUTO_CREATE);
 }
}

Here, we first create a serviceconnection anonymous class, in which we override the onserviceconnected() and onservicedisconnected() methods, which will be called when the activity and service are successfully bound or disconnected. In onserviceconnected(), we first get an instance of jwebsocketclientbinder. With this instance, we can call any public method of the service. Here, we call getservice() method to get the service instance, get the service instance, get the websocketclient object, and then we can send messages in the activity.

3、 Update activity UI from service

When a message is received in the service, the interface in the activity needs to be updated. There are many methods. Here, we use broadcast to achieve this. We define the broadcast receiver in the corresponding activity, and the service can send a broadcast after receiving the message.

public class MainActivity extends AppCompatActivity {
 ...
 private class ChatMessageReceiver extends BroadcastReceiver{
  @Override
  public void onReceive(Context context, Intent intent) {
    String message=intent.getStringExtra("message");
  }
 }
 /**
  *Dynamic registration broadcast
  */
 private void doRegisterReceiver() {
  chatMessageReceiver = new ChatMessageReceiver();
  IntentFilter filter = new IntentFilter("com.xch.servicecallback.content");
  registerReceiver(chatMessageReceiver, filter);
 }
 ...
}

The above code is very simple. First, create an internal class and inherit it from broadcastreceiver, which is the broadcast receiver chatmessage receiver in the code, and then register the broadcast receiver dynamically. When a message is received in the service, it will send out a broadcast and receive the broadcast in the chatmessage receiver.

Send broadcast:


client = new JWebSocketClient(uri) {
  @Override
  public void onMessage(String message) {
   Intent intent = new Intent();
   intent.setAction("com.xch.servicecallback.content");
   intent.putExtra("message", message);
   sendBroadcast(intent);
  }
};

After getting the message from the broadcast, you can update the UI. The specific layout is relatively simple. Just look at my source code. I will put the demo address at the end of the article.

4、 Message notification

The message notification directly uses notification, but when the screen is locked, it needs to light up the screen first. The code is as follows

/**
 *Check the lock screen status, if the lock screen lights up first
 *
 * @param content
 */
 private void checkLockAndShowNotification(String content) {
  //A service for managing lock screen
  KeyguardManager km = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
  If (km. Inkeyguardrestrictedinputmode()) {// lock screen
   //Gets the power manager object
   PowerManager pm = (PowerManager) this.getSystemService(Context.POWER_SERVICE);
   if (!pm.isScreenOn()) {
    @SuppressLint("InvalidWakeLockTag") PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.ACQUIRE_CAUSES_WAKEUP |
      PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "bright");
    wl.acquire(); // Light up the screen
    wl.release(); // Release after task
   }
   sendNotification(content);
  } else {
   sendNotification(content);
  }
 }
 /**
 *Send notification
 *
 * @param content
 */
 private void sendNotification(String content) {
  Intent intent = new Intent();
  intent.setClass(this, MainActivity.class);
  PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
  NotificationManager notifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
  Notification notification = new NotificationCompat.Builder(this)
    .setAutoCancel(true)
    //Set the notification priority
    .setPriority(Notification.PRIORITY_MAX)
    .setSmallIcon(R.mipmap.ic_launcher)
    . setcontenttitle ("nickname")
    .setContentText(content)
    .setVisibility(VISIBILITY_PUBLIC)
    .setWhen(System.currentTimeMillis())
    //Add sound, flashing, and vibration effects to notifications
    .setDefaults(Notification.DEFAULT_VIBRATE | Notification.DEFAULT_ALL | Notification.DEFAULT_SOUND)
    .setContentIntent(pendingIntent)
    .build();
  notifyManager.notify(1, notification);// The ID must be unique
 }

If the notification is not received, it may be that the notification is not turned on in the settings. Just enter the settings to turn it on. If the notification cannot pop up when the screen is locked, it may be that the lock screen notification authority is not turned on, and you also need to enter the settings to turn it on. To be on the safe side, we can judge whether the notification is on or not. If it is not, the user will be guided to open it. The code is as follows:

Finally add

/**
 *Detect whether to turn on notification
 *
 * @param context
 */
 private void checkNotification(final Context context) {
  if (!isNotificationEnabled(context)) {
   New alertdialog.builder (context). Settitle ("warm tips")
     . setMessage ("you haven't turned on the system notification, which will affect the message reception. Do you want to turn it on?")
     . setpositivebutton ("OK", new dialoginterface. Onclicklistener (){
      @Override
      public void onClick(DialogInterface dialog, int which) {
       setNotification(context);
      }
     }). Setnegativebutton ("Cancel", new dialoginterface. Onclicklistener (){
    @Override
    public void onClick(DialogInterface dialog, int which) {
    }
   }).show();
  }
 }
 /**
 *If the notification is not turned on, jump to the setting interface
 *
 * @param context
 */
 private void setNotification(Context context) {
  Intent localIntent = new Intent();
  //Jump directly to the application notification setting code:
  if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
   localIntent.setAction("android.settings.APP_NOTIFICATION_SETTINGS");
   localIntent.putExtra("app_package", context.getPackageName());
   localIntent.putExtra("app_uid", context.getApplicationInfo().uid);
  } else if (android.os.Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) {
   localIntent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
   localIntent.addCategory(Intent.CATEGORY_DEFAULT);
   localIntent.setData(Uri.parse("package:" + context.getPackageName()));
  } else {
   //4.4 the following actions that do not jump from the app to the application notification settings page can be considered to jump to the application details page
   localIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
   if (Build.VERSION.SDK_INT >= 9) {
    localIntent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS");
    localIntent.setData(Uri.fromParts("package", context.getPackageName(), null));
   } else if (Build.VERSION.SDK_INT <= 8) {
    localIntent.setAction(Intent.ACTION_VIEW);
    localIntent.setClassName("com.android.settings", "com.android.setting.InstalledAppDetails");
    localIntent.putExtra("com.android.settings.ApplicationPkgName", context.getPackageName());
   }
  }
  context.startActivity(localIntent);
 }
 /**
 *Get notification permission to detect whether system notification is enabled
 *
 * @param context
 */
 @TargetApi(Build.VERSION_CODES.KITKAT)
 private boolean isNotificationEnabled(Context context) {
  String CHECK_OP_NO_THROW = "checkOpNoThrow";
  String OP_POST_NOTIFICATION = "OP_POST_NOTIFICATION";
  AppOpsManager mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
  ApplicationInfo appInfo = context.getApplicationInfo();
  String pkg = context.getApplicationContext().getPackageName();
  int uid = appInfo.uid;
  Class appOpsClass = null;
  try {
   appOpsClass = Class.forName(AppOpsManager.class.getName());
   Method checkOpNoThrowMethod = appOpsClass.getMethod(CHECK_OP_NO_THROW, Integer.TYPE, Integer.TYPE,
     String.class);
   Field opPostNotificationValue = appOpsClass.getDeclaredField(OP_POST_NOTIFICATION);
   int value = (Integer) opPostNotificationValue.get(Integer.class);
   return ((Integer) checkOpNoThrowMethod.invoke(mAppOps, value, uid, pkg) == AppOpsManager.MODE_ALLOWED);
  } catch (Exception e) {
   e.printStackTrace();
  }
  return false;
 }

Enter the relevant permissions

<!--  Permissions required to unlock the screen -- >
 <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
 <!--  Permission required to apply for power lock -- >
 <uses-permission android:name="android.permission.WAKE_LOCK" />
 <!-- Vibration permission -- >
 <uses-permission android:name="android.permission.VIBRATE" />

5、 Heartbeat detection and reconnection

Because many uncertain factors will lead to the disconnection of websocket, such as network disconnection, it is necessary to ensure the stability of websocket connection, which requires the addition of heartbeat detection and reconnection.

Heartbeat detection is actually a timer. It is detected once a period of time. If the connection is broken, it will be reconnected. In the latest version of Java websocket framework, there are two reconnection methods, reconnect() and reconnectblocking(), which are also used here.

private static final long HEART_ BEAT_ RATE = 10 * 1000;// Heartbeat detection for long connections is performed every 10 seconds
 private Handler mHandler = new Handler();
 private Runnable heartBeatRunnable = new Runnable() {
  @Override
  public void run() {
   if (client != null) {
    if (client.isClosed()) {
     reconnectWs();
    }
   } else {
    //If the client is empty, reinitialize websocket
    initSocketClient();
   }
   //Heartbeat detection of long connection at regular time
   mHandler.postDelayed(this, HEART_BEAT_RATE);
  }
 };
 /**
 *Open reconnection
 */
 private void reconnectWs() {
  mHandler.removeCallbacks(heartBeatRunnable);
  new Thread() {
   @Override
   public void run() {
    try {
     //Reconnection
     client.reconnectBlocking();
    } catch (InterruptedException e) {
     e.printStackTrace();
    }
   }
  }.start();
 }

Then turn on heartbeat detection when the service starts

mHandler.postDelayed(heartBeatRunnable, HEART_ BEAT_ RATE);// Turn on heartbeat detection

Let’s print the log, as shown in the figure

6、 Keep alive with service

If some business scenarios need to keep the app alive, such as using the websocket to push, then our app background service will not be killed. Of course, if there is no cooperation with mobile phone manufacturers, it may be a headache for all Android developers to ensure that the service will not be killed all the time. Here, we can only ensure the survival of the service as far as possible.

1. Improve service priority (front desk service)

The priority of the front desk service is relatively high. It will display the effect similar to the notification in the status bar. It can try to avoid being recycled by the system when the memory is insufficient. The front desk service is relatively simple, so I will not elaborate on it. Sometimes we want to use the front desk service, but we don’t want it to be displayed in the status bar. Then we can use the method of gray keeping alive, as follows

private final static int GRAY_SERVICE_ID = 1001;
 //Grey means of keeping alive
 public static class GrayInnerService extends Service {
  @Override
  public int onStartCommand(Intent intent, int flags, int startId) {
   startForeground(GRAY_SERVICE_ID, new Notification());
   stopForeground(true);
   stopSelf();
   return super.onStartCommand(intent, flags, startId);
  }
  @Override
  public IBinder onBind(Intent intent) {
   return null;
  }
 }
 //Set the service as the front desk service to improve the priority
 if (Build.VERSION.SDK_INT < 18) {
  //Under Android 4.3, hide the icon on notification
  startForeground(GRAY_SERVICE_ID, new Notification());
 } else if(Build.VERSION.SDK_INT>18 && Build.VERSION.SDK_INT<25){
  //Android 4.3 - Android 7.0, hide the icon on notification
  Intent innerIntent = new Intent(this, GrayInnerService.class);
  startService(innerIntent);
  startForeground(GRAY_SERVICE_ID, new Notification());
 }else{
  //There is no solution
  startForeground(GRAY_SERVICE_ID, new Notification());
 }

This service is registered in Android manifest. XML


 <service android:name=".im.JWebSocketClientService$GrayInnerService"
  android:enabled="true"
  android:exported="false"
  android:process=":gray"/>

In fact, this is to turn on the foreground service and hide the notification, that is, to start a service and share a notification bar, and then stop the service to make the notification bar disappear. However, the version above 7.0 will display the “running” notice in the status bar, and there is no good solution at present.

2. Modify the return value of onstartcommand method of service


@Override
 public int onStartCommand(Intent intent, int flags, int startId) {
  ...
  return START_STICKY;
 }

Onstartcommand() returns an integer value to describe whether the system will continue to start the service after killing the service_ Sticky indicates that if the service process is killed, the system will try to re create the service.

3. Lock screen wake up

PowerManager.WakeLock wakeLock;// Lock screen wake up
 private void acquireWakeLock()
 {
  if (null == wakeLock)
  {
   PowerManager pm = (PowerManager)this.getSystemService(Context.POWER_SERVICE);
   wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK|PowerManager.ON_AFTER_RELEASE, "PostLocationService");
   if (null != wakeLock)
   {
    wakeLock.acquire();
   }
  }
 }

Get the power lock to keep the service running while still getting the CPU when the screen is off.

4. Other ways to keep alive

There are many other ways to keep the service alive, such as process pull, one pixel keeping alive, applying for self starting permission, guiding users to set white list, etc. in fact, after Android 7.0, there is no real sense of keeping alive, but it is better to do some processing than not. This article focuses on instant messaging. If you need to keep the service alive, you can check more information by yourself. I won’t elaborate here.

Finally, the source code address of this article is attached https://github.com/yangxch/WebSocketClient If you have any help, please order a star.

summary

The above is the Android websocket instant messaging function introduced by Xiaobian. I hope it can help you. If you have any questions, please leave me a message and Xiaobian will reply you in time. Thank you very much for your support to developer!
If you think this article is helpful to you, please reprint, please indicate the source, thank you!

Recommended Today

Looking for frustration 1.0

I believe you have a basic understanding of trust in yesterday’s article. Today we will give a complete introduction to trust. Why choose rust It’s a language that gives everyone the ability to build reliable and efficient software. You can’t write unsafe code here (unsafe block is not in the scope of discussion). Most of […]