From eb64ce17df660e306fca135890c4b41c01ba54bb Mon Sep 17 00:00:00 2001 From: zhongchao Date: Fri, 22 Jan 2021 10:47:20 +0800 Subject: [PATCH] add socketModule --- .idea/gradle.xml | 2 + foudations/mogo-socket/.gitignore | 1 + foudations/mogo-socket/build.gradle | 38 ++++ foudations/mogo-socket/consumer-rules.pro | 0 foudations/mogo-socket/gradle.properties | 4 + foudations/mogo-socket/proguard-rules.pro | 21 ++ .../cloud/socket/ExampleInstrumentedTest.java | 26 +++ .../mogo-socket/src/main/AndroidManifest.xml | 5 + .../cloud/socket/IMogoCloudSocketManager.java | 42 ++++ .../IMogoCloudSocketMsgAckListener.java | 13 ++ .../IMogoCloudSocketOnMessageListener.java | 11 + .../java/com/mogo/cloud/socket/MsgBody.java | 58 +++++ .../com/mogo/cloud/socket/SocketManager.java | 209 ++++++++++++++++++ .../cloud/socket/SocketServicesConstants.java | 12 + .../mogo/cloud/socket/ExampleUnitTest.java | 17 ++ settings.gradle | 2 +- 16 files changed, 460 insertions(+), 1 deletion(-) create mode 100644 foudations/mogo-socket/.gitignore create mode 100644 foudations/mogo-socket/build.gradle create mode 100644 foudations/mogo-socket/consumer-rules.pro create mode 100644 foudations/mogo-socket/gradle.properties create mode 100644 foudations/mogo-socket/proguard-rules.pro create mode 100644 foudations/mogo-socket/src/androidTest/java/com/mogo/cloud/socket/ExampleInstrumentedTest.java create mode 100644 foudations/mogo-socket/src/main/AndroidManifest.xml create mode 100644 foudations/mogo-socket/src/main/java/com/mogo/cloud/socket/IMogoCloudSocketManager.java create mode 100644 foudations/mogo-socket/src/main/java/com/mogo/cloud/socket/IMogoCloudSocketMsgAckListener.java create mode 100644 foudations/mogo-socket/src/main/java/com/mogo/cloud/socket/IMogoCloudSocketOnMessageListener.java create mode 100644 foudations/mogo-socket/src/main/java/com/mogo/cloud/socket/MsgBody.java create mode 100644 foudations/mogo-socket/src/main/java/com/mogo/cloud/socket/SocketManager.java create mode 100644 foudations/mogo-socket/src/main/java/com/mogo/cloud/socket/SocketServicesConstants.java create mode 100644 foudations/mogo-socket/src/test/java/com/mogo/cloud/socket/ExampleUnitTest.java diff --git a/.idea/gradle.xml b/.idea/gradle.xml index 2c52dd3..ec8f062 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -16,6 +16,7 @@ diff --git a/foudations/mogo-socket/.gitignore b/foudations/mogo-socket/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/foudations/mogo-socket/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/foudations/mogo-socket/build.gradle b/foudations/mogo-socket/build.gradle new file mode 100644 index 0000000..70812dc --- /dev/null +++ b/foudations/mogo-socket/build.gradle @@ -0,0 +1,38 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion rootProject.ext.android.compileSdkVersion + + defaultConfig { + minSdkVersion rootProject.ext.android.minSdkVersion + targetSdkVersion rootProject.ext.android.targetSdkVersion + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles "consumer-rules.pro" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + implementation fileTree(dir: "libs", include: ["*.jar"]) + // 长链 + implementation 'com.zhidao.socket:built-in-socket:1.0.17' + // 上报位置 + implementation 'com.zhidao.locupload:loc-upload-sdk:1.1.7' + implementation project(path: ':foudations:mogo-passport') + api rootProject.ext.dependencies.mogoutils +} + +apply from: new File(rootProject.rootDir, "gradle/upload.gradle").toString() \ No newline at end of file diff --git a/foudations/mogo-socket/consumer-rules.pro b/foudations/mogo-socket/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/foudations/mogo-socket/gradle.properties b/foudations/mogo-socket/gradle.properties new file mode 100644 index 0000000..59ae673 --- /dev/null +++ b/foudations/mogo-socket/gradle.properties @@ -0,0 +1,4 @@ +GROUP=com.mogo.cloud +POM_ARTIFACT_ID=socket +VERSION_CODE=1 +VERSION_NAME=1.0.0 \ No newline at end of file diff --git a/foudations/mogo-socket/proguard-rules.pro b/foudations/mogo-socket/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/foudations/mogo-socket/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/foudations/mogo-socket/src/androidTest/java/com/mogo/cloud/socket/ExampleInstrumentedTest.java b/foudations/mogo-socket/src/androidTest/java/com/mogo/cloud/socket/ExampleInstrumentedTest.java new file mode 100644 index 0000000..8d55435 --- /dev/null +++ b/foudations/mogo-socket/src/androidTest/java/com/mogo/cloud/socket/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.mogo.cloud.socket; + +import android.content.Context; + +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + assertEquals("com.mogo.cloud.socket.test", appContext.getPackageName()); + } +} \ No newline at end of file diff --git a/foudations/mogo-socket/src/main/AndroidManifest.xml b/foudations/mogo-socket/src/main/AndroidManifest.xml new file mode 100644 index 0000000..04bb196 --- /dev/null +++ b/foudations/mogo-socket/src/main/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/foudations/mogo-socket/src/main/java/com/mogo/cloud/socket/IMogoCloudSocketManager.java b/foudations/mogo-socket/src/main/java/com/mogo/cloud/socket/IMogoCloudSocketManager.java new file mode 100644 index 0000000..d8b4c7f --- /dev/null +++ b/foudations/mogo-socket/src/main/java/com/mogo/cloud/socket/IMogoCloudSocketManager.java @@ -0,0 +1,42 @@ +package com.mogo.cloud.socket; + +import android.content.Context; + + +/** + * socket 长链 + */ +public interface IMogoCloudSocketManager { + + /** + * 初始化,各模块不用关心 + * + * @param context 上下文 + * @param appId 一般为包名,不参与通道的建立,一般用于发消息 + */ + void init(Context context, String appId); + + /** + * 注册消息监听 + * + * @param msgType 消息类型 + * @param listener 回调 + */ + void registerOnMessageListener(int msgType, IMogoCloudSocketOnMessageListener listener); + + /** + * 注销消息监听 + * + * @param msgType 消息类型 + * @param listener 回调 + */ + void unregisterOnMessageListener(int msgType, IMogoCloudSocketOnMessageListener listener); + + /** + * 发送消息 + * + * @param body 消息体 + * @param listener 回执监听 + */ + void sendMsg(MsgBody body, IMogoCloudSocketMsgAckListener listener); +} diff --git a/foudations/mogo-socket/src/main/java/com/mogo/cloud/socket/IMogoCloudSocketMsgAckListener.java b/foudations/mogo-socket/src/main/java/com/mogo/cloud/socket/IMogoCloudSocketMsgAckListener.java new file mode 100644 index 0000000..af0dfff --- /dev/null +++ b/foudations/mogo-socket/src/main/java/com/mogo/cloud/socket/IMogoCloudSocketMsgAckListener.java @@ -0,0 +1,13 @@ +package com.mogo.cloud.socket; + +/** + * 消息回执监听 + */ +public interface IMogoCloudSocketMsgAckListener { + + /** + * 长连接消息回执 + * msgId: 消息id + */ + void onAck(long msgId); +} diff --git a/foudations/mogo-socket/src/main/java/com/mogo/cloud/socket/IMogoCloudSocketOnMessageListener.java b/foudations/mogo-socket/src/main/java/com/mogo/cloud/socket/IMogoCloudSocketOnMessageListener.java new file mode 100644 index 0000000..720b18e --- /dev/null +++ b/foudations/mogo-socket/src/main/java/com/mogo/cloud/socket/IMogoCloudSocketOnMessageListener.java @@ -0,0 +1,11 @@ +package com.mogo.cloud.socket; + +/** + * 消息回调 + */ +public interface IMogoCloudSocketOnMessageListener { + + Class target(); + + void onMsgReceived(T obj); +} diff --git a/foudations/mogo-socket/src/main/java/com/mogo/cloud/socket/MsgBody.java b/foudations/mogo-socket/src/main/java/com/mogo/cloud/socket/MsgBody.java new file mode 100644 index 0000000..d23d032 --- /dev/null +++ b/foudations/mogo-socket/src/main/java/com/mogo/cloud/socket/MsgBody.java @@ -0,0 +1,58 @@ +package com.mogo.cloud.socket; + +/** + * 描述 + */ +public class MsgBody { + + /** + * 消息类型 + */ + private int mMsgType; + + /** + * 是否回执 + */ + private boolean mAck = false; + + /** + * 消息ID + */ + private final long mMsgId = System.currentTimeMillis(); + + /** + * 消息内容 + */ + private Object mContent; + + public MsgBody msgType(int msgType) { + this.mMsgType = msgType; + return this; + } + + public MsgBody ack(boolean ack) { + this.mAck = ack; + return this; + } + + public MsgBody content(Object object) { + this.mContent = object; + return this; + } + + public int getMsgType() { + return mMsgType; + } + + public boolean isAck() { + return mAck; + } + + public long getMsgId() { + return mMsgId; + } + + public Object getContent() { + return mContent; + } +} diff --git a/foudations/mogo-socket/src/main/java/com/mogo/cloud/socket/SocketManager.java b/foudations/mogo-socket/src/main/java/com/mogo/cloud/socket/SocketManager.java new file mode 100644 index 0000000..a854ce2 --- /dev/null +++ b/foudations/mogo-socket/src/main/java/com/mogo/cloud/socket/SocketManager.java @@ -0,0 +1,209 @@ +package com.mogo.cloud.socket; + + +import android.content.Context; + +import androidx.annotation.NonNull; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import com.mogo.cloud.passport.MoGoAiCloudClient; +import com.mogo.cloud.passport.MoGoAiCloudClientConfig; +import com.mogo.utils.logger.Logger; +import com.mogo.utils.network.utils.GsonUtil; +import com.zhidao.locupload.Platform; +import com.zhidao.ptech.connsvr.protocol.MogoConnsvr; +import com.zhidao.socket.Callback; +import com.zhidao.socket.CallbackManager; +import com.zhidao.socket.Environment; +import com.zhidao.socket.SocketClient; +import com.zhidao.socket.SocketConfig; +import com.zhidao.socket.utils.RequestUtil; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class SocketManager implements IMogoCloudSocketManager, Callback { + + private static final String TAG = "SocketManager"; + private static volatile SocketManager mInstance; + private MoGoAiCloudClientConfig cloudClientConfig; + private String mAppId; + + private SocketManager() { + CallbackManager.getInstance().register(this); + cloudClientConfig = MoGoAiCloudClient.getInstance().getAiCloudClientConfig(); + } + + public static SocketManager getInstance() { + if (mInstance == null) { + synchronized (SocketManager.class) { + if (mInstance == null) { + mInstance = new SocketManager(); + } + } + } + return mInstance; + } + + /** + * 管理消息分发 + *

+ * key - msgType + */ + private Map> mListeners = new ConcurrentHashMap<>(); + + /** + * 管理消息回执 + *

+ * key - msgId + */ + private Map mAckListeners = new ConcurrentHashMap<>(); + + + public static final int MAX_CAP = 64; //保证充足的容量应对非常延时的推送 + private final ArrayList mReceivedMsgId = new ArrayList<>(MAX_CAP); + private int mCurrentIndex = 0; + + @Override + public void init(Context context, String appId) { + mAppId = appId; + SocketConfig.instance() + .setAppContext(context.getApplicationContext()) + .setEnvironment(getEnvironment()) + .setClient(Platform.getClient(Platform.car)) + .setChannelId(SocketServicesConstants.SOCKET_CHANNEL_ID) + .setOpenAnalytics(true) + .setSn(cloudClientConfig.getSn()) + .setDebug(cloudClientConfig.isShowDebugLog()); + SocketClient.getInstance().start(context); + } + + @Override + public void registerOnMessageListener(int msgType, IMogoCloudSocketOnMessageListener listener) { + if (mListeners.containsKey(msgType)) { + Logger.w(TAG, "msgType %d is exist.", msgType); + } + if (!mListeners.containsKey(msgType)) { + mListeners.put(msgType, new ArrayList<>()); + } + mListeners.get(msgType).add(listener); + } + + @Override + public void unregisterOnMessageListener(int msgType, IMogoCloudSocketOnMessageListener listener) { + if (listener == null) { + return; + } + if (!mListeners.containsKey(msgType)) { + return; + } + List listeners = mListeners.get(msgType); + if (listeners != null) { + listeners.remove(listener); + } + } + + @Override + public void sendMsg(MsgBody body, IMogoCloudSocketMsgAckListener listener) { + Logger.d(TAG, "sendMsg."); + final byte[] pb = convertToPBBytes(body.getMsgType(), objectToBytes(body.getContent())); + RequestUtil.sendPayloadData(mAppId, 2, pb, 1, true, System.currentTimeMillis()); + } + + @Override + public void update(@NonNull CallbackManager manager, @NonNull byte[] message, String appId, long msgId) { + try { + MogoConnsvr.Payload payload = MogoConnsvr.Payload.parseFrom(message); + int msgType = payload.getMsgType(); + Logger.d(TAG, "received msg type = %d", msgType); + List listeners = mListeners.get(msgType); + if (listeners != null && !listeners.isEmpty()) { + Iterator iterator = listeners.iterator(); + if (msgId != 0) { //兼容老版本 + if (mReceivedMsgId.contains(msgId)) { // 避免消息重发 + return; + } + cacheLastReceivedMsgId(msgId); + } + Object object = null; + while (iterator.hasNext()) { + IMogoCloudSocketOnMessageListener listener = iterator.next(); + if (object == null) { + object = GsonUtil.objectFromJson(payload.getPayload().toStringUtf8(), listener.target()); + } + if (listener != null) { + Logger.d(TAG, "received msgId = %s, content = %s", msgId, payload.getPayload().toStringUtf8()); + listener.onMsgReceived(object); + } + } + } + } catch (InvalidProtocolBufferException e) { + Logger.e(TAG, e, "parse msg error."); + } + } + + private void cacheLastReceivedMsgId(long msgId) { + if (msgId == 0) { + return; + } + synchronized (this) { + mReceivedMsgId.add(mCurrentIndex % MAX_CAP, msgId); + mCurrentIndex++; + } + } + + @Override + public void onAck(@NonNull CallbackManager manager, @NonNull byte[] headerBytes, byte[] content) { + try { + MogoConnsvr.Header header = MogoConnsvr.Header.parseFrom(headerBytes); + int msgType = header.getMsgType(); + String appId = header.getAppId(); + int productLine = header.getProductLine(); + long msgId = header.getMsgId(); + IMogoCloudSocketMsgAckListener listener = mAckListeners.remove(msgId); + if (listener != null) { + listener.onAck(msgId); + } + Logger.d(TAG, "send message success: msgType = %d, appId = %s, productLine = %d", msgType, appId, productLine); + } catch (InvalidProtocolBufferException e) { + e.printStackTrace(); + } + } + + private Environment getEnvironment() { + switch (cloudClientConfig.getNetMode()) { + case 1: + return Environment.dev; + case 2: + case 4: + return Environment.qa; + case 3: + default: + return Environment.release; + } + } + + public byte[] objectToBytes(Object obj) { + String jsonStr = GsonUtil.jsonFromObject(obj); + return jsonStr.getBytes(); + } + + private byte[] convertToPBBytes(int msgType, byte[] payloadBytes) { + MogoConnsvr.Payload payloadData = MogoConnsvr.Payload.newBuilder() + .setMsgType(msgType) + .setPayload(ByteString.copyFrom(payloadBytes)).build(); + return payloadData.toByteArray(); + } + + public synchronized void release() { + mListeners.clear(); + mListeners = null; + cloudClientConfig = null; + mInstance = null; + } + +} diff --git a/foudations/mogo-socket/src/main/java/com/mogo/cloud/socket/SocketServicesConstants.java b/foudations/mogo-socket/src/main/java/com/mogo/cloud/socket/SocketServicesConstants.java new file mode 100644 index 0000000..cfb4a91 --- /dev/null +++ b/foudations/mogo-socket/src/main/java/com/mogo/cloud/socket/SocketServicesConstants.java @@ -0,0 +1,12 @@ +package com.mogo.cloud.socket; + +import androidx.annotation.Keep; + +public class SocketServicesConstants { + + /** + * 建立长链的通道ID + */ + @Keep + public static final String SOCKET_CHANNEL_ID = "dataCrawler"; +} diff --git a/foudations/mogo-socket/src/test/java/com/mogo/cloud/socket/ExampleUnitTest.java b/foudations/mogo-socket/src/test/java/com/mogo/cloud/socket/ExampleUnitTest.java new file mode 100644 index 0000000..e889224 --- /dev/null +++ b/foudations/mogo-socket/src/test/java/com/mogo/cloud/socket/ExampleUnitTest.java @@ -0,0 +1,17 @@ +package com.mogo.cloud.socket; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index d4cc957..5156a21 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,4 +1,4 @@ -include ':modules:mogo-realtime' +include ':foudations:mogo-socket' include ':modules:mogo-realtime' include ':modules:realtime' include ':modules:mogo-tanlu'