[Update]增加多屏互动Netty通信library
This commit is contained in:
@@ -55,6 +55,7 @@ dependencies {
|
||||
implementation rootProject.ext.dependencies.rxandroid
|
||||
// 从车机获取视频流
|
||||
implementation 'com.zhidao.carmanager:common:1.0.25@aar'
|
||||
implementation 'com.google.android.material:material:1.3.0'
|
||||
|
||||
|
||||
if (Boolean.valueOf(RELEASE)) {
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
|
||||
|
||||
<uses-feature
|
||||
android:glEsVersion="0x00020000"
|
||||
android:required="true" />
|
||||
@@ -21,14 +20,17 @@
|
||||
<uses-feature android:name="android.hardware.camera.autofocus" />
|
||||
|
||||
<application
|
||||
android:name="com.mogo.cloud.MoGoApplication"
|
||||
android:name=".MoGoApplication"
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:theme="@style/AppTheme">
|
||||
<activity
|
||||
android:name=".NSDNettyActivity"
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:launchMode="singleTask">
|
||||
@@ -38,7 +40,6 @@
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".ConfigInfoActivity"
|
||||
android:label="配置信息"
|
||||
@@ -54,7 +55,7 @@
|
||||
android:launchMode="singleTask"
|
||||
android:windowSoftInputMode="stateHidden" />
|
||||
<activity
|
||||
android:name="com.mogo.cloud.network.NetworkActivity"
|
||||
android:name=".network.NetworkActivity"
|
||||
android:label="网络测试"
|
||||
android:launchMode="singleTask" />
|
||||
<activity
|
||||
@@ -69,15 +70,12 @@
|
||||
android:name=".RoadConditionActivity"
|
||||
android:label="路况服务"
|
||||
android:launchMode="singleTask" />
|
||||
|
||||
<!-- <receiver android:name=".wifi.WifiBroadCastReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.net.wifi.RSSI_CHANGED" />
|
||||
<action android:name="android.net.wifi.STATE_CHANGE" />
|
||||
<action android:name="android.net.wifi.WIFI_STATE_CHANGED" />
|
||||
</intent-filter>
|
||||
|
||||
</receiver>-->
|
||||
<activity
|
||||
android:name=".netty.client.NettyClientActivity"
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:name=".netty.server.NettyServerActivity"
|
||||
android:exported="false" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -44,6 +44,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
private Button btnV2XFunctionTest;
|
||||
|
||||
private boolean v2xHasBeenInitialized = false;
|
||||
private Button btnNSDNetty;
|
||||
private SurfaceView surfacePreviewView;
|
||||
|
||||
private TextView tvSn;
|
||||
@@ -167,6 +168,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
});
|
||||
|
||||
btnRequestCarLive = findViewById(R.id.btnRequestCarLive);
|
||||
btnNSDNetty = findViewById(R.id.btnNSDNetty);
|
||||
surfacePreviewView = findViewById(R.id.surfacePreviewView);
|
||||
btnRequestCarLive.setOnClickListener(v -> {
|
||||
MoGoAiCloudTrafficLive.viewFrontVehicleLive(40.11547, 116.22544,
|
||||
@@ -203,6 +205,10 @@ public class MainActivity extends AppCompatActivity {
|
||||
}
|
||||
});
|
||||
});
|
||||
btnNSDNetty.setOnClickListener(v -> {
|
||||
Intent intent = new Intent(MainActivity.this, NSDNettyActivity.class);
|
||||
startActivity(intent);
|
||||
});
|
||||
|
||||
//V2X功能测试
|
||||
btnV2XFunctionTest = findViewById(R.id.btnV2XFunctionTest);
|
||||
|
||||
37
app/src/main/java/com/mogo/cloud/NSDNettyActivity.java
Normal file
37
app/src/main/java/com/mogo/cloud/NSDNettyActivity.java
Normal file
@@ -0,0 +1,37 @@
|
||||
package com.mogo.cloud;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
|
||||
import com.mogo.cloud.netty.client.NettyClientActivity;
|
||||
import com.mogo.cloud.netty.server.NettyServerActivity;
|
||||
|
||||
public class NSDNettyActivity extends AppCompatActivity {
|
||||
|
||||
private Button btnNettyServer;
|
||||
private Button btnNettyClient;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_nsdnetty);
|
||||
initView();
|
||||
}
|
||||
|
||||
private void initView() {
|
||||
btnNettyServer = findViewById(R.id.btnNettyServer);
|
||||
btnNettyServer.setOnClickListener(v -> {
|
||||
Intent intent = new Intent(NSDNettyActivity.this, NettyServerActivity.class);
|
||||
startActivity(intent);
|
||||
});
|
||||
btnNettyClient = findViewById(R.id.btnNettyClient);
|
||||
btnNettyClient.setOnClickListener(v -> {
|
||||
Intent intent = new Intent(NSDNettyActivity.this, NettyClientActivity.class);
|
||||
startActivity(intent);
|
||||
});
|
||||
}
|
||||
}
|
||||
14
app/src/main/java/com/mogo/cloud/netty/LogBean.java
Normal file
14
app/src/main/java/com/mogo/cloud/netty/LogBean.java
Normal file
@@ -0,0 +1,14 @@
|
||||
package com.mogo.cloud.netty;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
|
||||
public class LogBean {
|
||||
public String mTime;
|
||||
public String mLog;
|
||||
|
||||
public LogBean(long time, String log) {
|
||||
SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss");
|
||||
mTime = format.format(time);
|
||||
mLog = log;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.mogo.cloud.netty.client;
|
||||
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.mogo.cloud.R;
|
||||
import com.mogo.cloud.netty.LogBean;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
public class LogAdapter extends RecyclerView.Adapter<LogAdapter.ItemHolder> {
|
||||
|
||||
private List<LogBean> mDataList = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public ItemHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
return new ItemHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.log_item, parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(final ItemHolder holder, int position) {
|
||||
LogBean bean = mDataList.get(position);
|
||||
|
||||
holder.mTime.setText(bean.mTime);
|
||||
holder.mLog.setText(bean.mLog);
|
||||
|
||||
holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
|
||||
@Override
|
||||
public boolean onLongClick(View v) {
|
||||
ClipboardManager cmb = (ClipboardManager) v.getContext().getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
LogBean log = mDataList.get(holder.getAdapterPosition());
|
||||
String msg = log.mTime + " " + log.mLog;
|
||||
cmb.setPrimaryClip(ClipData.newPlainText(null, msg));
|
||||
Toast.makeText(v.getContext(), "已复制到剪贴板", Toast.LENGTH_LONG).show();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mDataList.size();
|
||||
}
|
||||
|
||||
public class ItemHolder extends RecyclerView.ViewHolder {
|
||||
public TextView mTime;
|
||||
public TextView mLog;
|
||||
|
||||
public ItemHolder(View itemView) {
|
||||
super(itemView);
|
||||
mTime = (TextView) itemView.findViewById(R.id.time);
|
||||
mLog = (TextView) itemView.findViewById(R.id.logtext);
|
||||
}
|
||||
}
|
||||
|
||||
public List<LogBean> getDataList() {
|
||||
return mDataList;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
package com.mogo.cloud.netty.client;
|
||||
|
||||
import android.net.nsd.NsdServiceInfo;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.mogo.cloud.R;
|
||||
import com.mogo.cloud.netty.LogBean;
|
||||
import com.mogo.telematic.client.NettyTcpClient;
|
||||
import com.mogo.telematic.client.NsdClient;
|
||||
import com.mogo.telematic.client.listener.MessageStateListener;
|
||||
import com.mogo.telematic.client.listener.NettyClientListener;
|
||||
import com.mogo.telematic.client.status.ConnectState;
|
||||
|
||||
public class NettyClientActivity extends AppCompatActivity implements View.OnClickListener, NettyClientListener<String> {
|
||||
|
||||
private static final String TAG = "NettyClientActivity";
|
||||
// client端用来过滤的
|
||||
public static final String SERVER_NAME = "NSD_SERVER";
|
||||
|
||||
private Button mClearLog;
|
||||
private Button mSendBtn;
|
||||
private Button mConnect;
|
||||
private EditText mSendET;
|
||||
private RecyclerView mSendList;
|
||||
private RecyclerView mReceList;
|
||||
private EditText mInputIp;
|
||||
|
||||
private LogAdapter mSendLogAdapter = new LogAdapter();
|
||||
private LogAdapter mReceLogAdapter = new LogAdapter();
|
||||
private NettyTcpClient mNettyTcpClient;
|
||||
private NsdClient nsdClient;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_netty_client);
|
||||
findViews();
|
||||
initView();
|
||||
searchNsdServer();
|
||||
}
|
||||
|
||||
private void initView() {
|
||||
LinearLayoutManager manager1 = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
|
||||
mSendList.setLayoutManager(manager1);
|
||||
mSendList.setAdapter(mSendLogAdapter);
|
||||
|
||||
LinearLayoutManager manager2 = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
|
||||
mReceList.setLayoutManager(manager2);
|
||||
mReceList.setAdapter(mReceLogAdapter);
|
||||
}
|
||||
|
||||
private void findViews() {
|
||||
mSendList = findViewById(R.id.send_list);
|
||||
mReceList = findViewById(R.id.rece_list);
|
||||
mSendET = findViewById(R.id.send_et);
|
||||
mConnect = findViewById(R.id.connect);
|
||||
mSendBtn = findViewById(R.id.send_btn);
|
||||
mClearLog = findViewById(R.id.clear_log);
|
||||
mInputIp = findViewById(R.id.et_ip);
|
||||
|
||||
mConnect.setOnClickListener(this);
|
||||
mSendBtn.setOnClickListener(this);
|
||||
mClearLog.setOnClickListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
switch (v.getId()) {
|
||||
case R.id.connect:
|
||||
// connectNettyServer(mInputIp.getText().toString().trim(), Const.TCP_PORT);
|
||||
break;
|
||||
|
||||
case R.id.send_btn:
|
||||
if (!mNettyTcpClient.getConnectStatus()) {
|
||||
Toast.makeText(getApplicationContext(), "未连接,请先连接", Toast.LENGTH_SHORT).show();
|
||||
} else {
|
||||
final String msg = mSendET.getText().toString();
|
||||
if (TextUtils.isEmpty(msg.trim())) {
|
||||
return;
|
||||
}
|
||||
mNettyTcpClient.sendMsgToServer(msg, new MessageStateListener() {
|
||||
@Override
|
||||
public void isSendSuccss(boolean isSuccess) {
|
||||
if (isSuccess) {
|
||||
Log.d(TAG, "Write auth successful");
|
||||
logSend(msg);
|
||||
} else {
|
||||
Log.d(TAG, "Write auth error");
|
||||
}
|
||||
}
|
||||
});
|
||||
mSendET.setText("");
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case R.id.clear_log:
|
||||
mReceLogAdapter.getDataList().clear();
|
||||
mSendLogAdapter.getDataList().clear();
|
||||
mReceLogAdapter.notifyDataSetChanged();
|
||||
mSendLogAdapter.notifyDataSetChanged();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过Nsd搜索注册过的服务端名称解析后拿到 IP 和端口,进行NettySocket的连接
|
||||
*/
|
||||
private void searchNsdServer() {
|
||||
|
||||
nsdClient = new NsdClient(NettyClientActivity.this, SERVER_NAME, new NsdClient.IServerFound() {
|
||||
@Override
|
||||
public void onServerFound(NsdServiceInfo info, int port) {
|
||||
if (info != null) {
|
||||
String hostAddress = info.getHost().getHostAddress();
|
||||
Log.d(TAG, "NSD查询到指定服务器信息ip为:" + hostAddress + ",port为:" + port);
|
||||
//获取到指定的地址,进行Netty的连接
|
||||
connectNettyServer(hostAddress, port);
|
||||
|
||||
if (info.getServiceName().equals(SERVER_NAME)) {
|
||||
//扫描到以后停止
|
||||
nsdClient.stopNSDServer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServerFail() {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
nsdClient.startNSDClient();
|
||||
}
|
||||
|
||||
private void connectNettyServer(String serverAddress, int port) {
|
||||
Log.d(TAG, "connectNettyServer");
|
||||
if (serverAddress == null || serverAddress.length() == 0) {
|
||||
Toast.makeText(this, "ip不能为空!", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
if (mNettyTcpClient == null) {
|
||||
mNettyTcpClient = new NettyTcpClient.Builder()
|
||||
.setHost(serverAddress) //设置服务端地址
|
||||
.setTcpPort(port) //设置服务端端口号
|
||||
.setMaxReconnectTimes(5) //设置最大重连次数
|
||||
.setReconnectIntervalTime(5) //设置重连间隔时间。单位:秒
|
||||
.setSendheartBeat(true) //设置是否发送心跳
|
||||
.setHeartBeatInterval(5) //设置心跳间隔时间。单位:秒
|
||||
.setHeartBeatData("I'm is HeartBeatData") //设置心跳数据,可以是String类型,也可以是byte[],以后设置的为准
|
||||
.setIndex(0) //设置客户端标识.(因为可能存在多个tcp连接)
|
||||
// .setPacketSeparator("#")//用特殊字符,作为分隔符,解决粘包问题,默认是用换行符作为分隔符
|
||||
// .setMaxPacketLong(1024)//设置一次发送数据的最大长度,默认是1024,最大值为Integer.MAX
|
||||
.build();
|
||||
mNettyTcpClient.setListener(NettyClientActivity.this); //设置TCP监听
|
||||
}
|
||||
|
||||
if (!mNettyTcpClient.getConnectStatus()) {
|
||||
mNettyTcpClient.connect();//连接服务器
|
||||
} else {
|
||||
mNettyTcpClient.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessageResponseClient(String msg, int index) {
|
||||
Log.e(TAG, "onMessageResponse:" + msg);
|
||||
logRece(index + ":" + msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClientStatusConnectChanged(final int statusCode, final int index) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (statusCode == ConnectState.STATUS_CONNECT_SUCCESS) {
|
||||
Log.e(TAG, "STATUS_CONNECT_SUCCESS:");
|
||||
mConnect.setText("DisConnect:" + index);
|
||||
} else {
|
||||
Log.e(TAG, "onServiceStatusConnectChanged:" + statusCode);
|
||||
mConnect.setText("Connect:" + index);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private void logSend(String log) {
|
||||
LogBean logBean = new LogBean(System.currentTimeMillis(), log);
|
||||
mSendLogAdapter.getDataList().add(0, logBean);
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mSendLogAdapter.notifyDataSetChanged();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private void logRece(String log) {
|
||||
LogBean logBean = new LogBean(System.currentTimeMillis(), log);
|
||||
mReceLogAdapter.getDataList().add(0, logBean);
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mReceLogAdapter.notifyDataSetChanged();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 方法三:
|
||||
* byte[] to hex string
|
||||
*
|
||||
* @param bytes
|
||||
* @return
|
||||
*/
|
||||
public static String bytesToHexFun3(byte[] bytes, int length) {
|
||||
StringBuilder buf = new StringBuilder(length * 2);
|
||||
for (int i = 0; i < length; i++) {// 使用String的format方法进行转换
|
||||
buf.append(String.format("%02x", new Integer(bytes[i] & 0xFF)));
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
public void disconnect(View view) {
|
||||
mNettyTcpClient.disconnect();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.mogo.cloud.netty.server;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.mogo.telematic.server.bean.ClientChanel;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* Created by IT小蔡 on 2018-11-10.
|
||||
*/
|
||||
|
||||
public class CustomSpinnerAdapter extends BaseAdapter {
|
||||
|
||||
private int layoutId;
|
||||
private Context mContext;
|
||||
private List<ClientChanel> mData;
|
||||
private final LayoutInflater mInflater;
|
||||
|
||||
public CustomSpinnerAdapter(@NonNull Context context, List<ClientChanel> list) {
|
||||
this.mContext = context;
|
||||
this.mData = list;
|
||||
mInflater = LayoutInflater.from(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return mData.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientChanel getItem(int position) {
|
||||
return mData.get(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
final View view;
|
||||
final TextView text;
|
||||
|
||||
if (convertView == null) {
|
||||
view = mInflater.inflate(android.R.layout.simple_spinner_item, parent, false);
|
||||
} else {
|
||||
view = convertView;
|
||||
}
|
||||
|
||||
text = (TextView) view;
|
||||
ClientChanel item = getItem(position);
|
||||
text.setText(item.getClientIp());
|
||||
|
||||
return view;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.mogo.cloud.netty.server;
|
||||
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.mogo.cloud.R;
|
||||
import com.mogo.cloud.netty.LogBean;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
public class LogAdapter extends RecyclerView.Adapter<LogAdapter.ItemHolder> {
|
||||
|
||||
private List<LogBean> mDataList = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public ItemHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
return new ItemHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.log_item, parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(final ItemHolder holder, int position) {
|
||||
LogBean bean = mDataList.get(position);
|
||||
|
||||
holder.mTime.setText(bean.mTime);
|
||||
holder.mLog.setText(bean.mLog);
|
||||
|
||||
holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
|
||||
@Override
|
||||
public boolean onLongClick(View v) {
|
||||
ClipboardManager cmb = (ClipboardManager) v.getContext().getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
LogBean log = mDataList.get(holder.getAdapterPosition());
|
||||
String msg = log.mTime + " " + log.mLog;
|
||||
cmb.setPrimaryClip(ClipData.newPlainText(null, msg));
|
||||
Toast.makeText(v.getContext(), "已复制到剪贴板", Toast.LENGTH_LONG).show();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mDataList.size();
|
||||
}
|
||||
|
||||
public class ItemHolder extends RecyclerView.ViewHolder {
|
||||
public TextView mTime;
|
||||
public TextView mLog;
|
||||
|
||||
public ItemHolder(View itemView) {
|
||||
super(itemView);
|
||||
mTime = (TextView) itemView.findViewById(R.id.time);
|
||||
mLog = (TextView) itemView.findViewById(R.id.logtext);
|
||||
}
|
||||
}
|
||||
|
||||
public List<LogBean> getDataList() {
|
||||
return mDataList;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,324 @@
|
||||
package com.mogo.cloud.netty.server;
|
||||
|
||||
import static android.widget.Toast.LENGTH_SHORT;
|
||||
|
||||
import android.net.nsd.NsdServiceInfo;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.mogo.cloud.R;
|
||||
import com.mogo.cloud.netty.LogBean;
|
||||
import com.mogo.telematic.NetworkUtils;
|
||||
import com.mogo.telematic.server.NSDServer;
|
||||
import com.mogo.telematic.server.bean.ClientChanel;
|
||||
import com.mogo.telematic.server.netty.NettyServerListener;
|
||||
import com.mogo.telematic.server.netty.NettyTcpServer;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
|
||||
public class NettyServerActivity extends AppCompatActivity implements View.OnClickListener, NettyServerListener<String> {
|
||||
|
||||
private static final String TAG = "NettyServerActivity";
|
||||
|
||||
private Button mClearLog;
|
||||
private Button mSendBtn;
|
||||
private Button startServer;
|
||||
private EditText mSendET;
|
||||
private RecyclerView mSendList;
|
||||
private RecyclerView mReceList;
|
||||
|
||||
private LogAdapter mSendLogAdapter = new LogAdapter();
|
||||
private LogAdapter mReceLogAdapter = new LogAdapter();
|
||||
private TextView receiveTv;
|
||||
|
||||
List<ClientChanel> clientChanelArray = new ArrayList<>(); //储存客户端通道信息
|
||||
private Spinner mSpinner;
|
||||
private CustomSpinnerAdapter spinnerAdapter;
|
||||
private TextView mIpView;
|
||||
|
||||
private NSDServer mNsdServer;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_netty_server);
|
||||
|
||||
findViews();
|
||||
initData();
|
||||
initListener();
|
||||
}
|
||||
|
||||
private void initListener() {
|
||||
|
||||
spinnerAdapter = new CustomSpinnerAdapter(this, clientChanelArray);
|
||||
|
||||
mSpinner.setAdapter(spinnerAdapter);
|
||||
mSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
ClientChanel clientChanel = spinnerAdapter.getItem(position);
|
||||
Toast.makeText(NettyServerActivity.this, "onItemSelected:" + clientChanel.getClientIp(), Toast.LENGTH_LONG).show();
|
||||
NettyTcpServer.getInstance().selectorChannel(clientChanel.getChannel());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {
|
||||
NettyTcpServer.getInstance().selectorChannel(null);
|
||||
Toast.makeText(NettyServerActivity.this, "onNothingSelected", Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void initData() {
|
||||
LinearLayoutManager manager1 = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
|
||||
mSendList.setLayoutManager(manager1);
|
||||
mSendList.setAdapter(mSendLogAdapter);
|
||||
|
||||
LinearLayoutManager manager2 = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
|
||||
mReceList.setLayoutManager(manager2);
|
||||
mReceList.setAdapter(mReceLogAdapter);
|
||||
|
||||
}
|
||||
|
||||
private void findViews() {
|
||||
mSendList = findViewById(R.id.send_list);
|
||||
mReceList = findViewById(R.id.rece_list);
|
||||
mSendET = findViewById(R.id.send_et);
|
||||
mSendBtn = findViewById(R.id.send_btn);
|
||||
mClearLog = findViewById(R.id.clear_log);
|
||||
startServer = findViewById(R.id.startServer);
|
||||
receiveTv = findViewById(R.id.receiveTv);
|
||||
mSpinner = findViewById(R.id.spinner);
|
||||
mIpView = findViewById(R.id.tv_ip);
|
||||
|
||||
startServer.setOnClickListener(this);
|
||||
mSendBtn.setOnClickListener(this);
|
||||
mClearLog.setOnClickListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWindowFocusChanged(boolean hasFocus) {
|
||||
super.onWindowFocusChanged(hasFocus);
|
||||
mIpView.setText(NetworkUtils.getIPAddress(this) + "(切换网络后不会刷新!)");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
switch (v.getId()) {
|
||||
case R.id.startServer:
|
||||
startServer();
|
||||
break;
|
||||
|
||||
case R.id.send_btn:
|
||||
if (!NettyTcpServer.getInstance().isServerStart()) {
|
||||
Toast.makeText(getApplicationContext(), "未连接,请先连接", LENGTH_SHORT).show();
|
||||
} else {
|
||||
final String msg = mSendET.getText().toString();
|
||||
if (TextUtils.isEmpty(msg.trim())) {
|
||||
return;
|
||||
}
|
||||
NettyTcpServer.getInstance().sendMsgToServer(msg, new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture channelFuture) throws Exception {
|
||||
if (channelFuture.isSuccess()) { //4
|
||||
Log.d(TAG, "Write auth successful");
|
||||
logSend(msg);
|
||||
} else {
|
||||
Log.d(TAG, "Write auth error");
|
||||
}
|
||||
}
|
||||
});
|
||||
mSendET.setText("");
|
||||
}
|
||||
break;
|
||||
|
||||
case R.id.clear_log:
|
||||
mReceLogAdapter.getDataList().clear();
|
||||
mSendLogAdapter.getDataList().clear();
|
||||
mReceLogAdapter.notifyDataSetChanged();
|
||||
mSendLogAdapter.notifyDataSetChanged();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void startServer() {
|
||||
NettyTcpServer nettyTcpServer = NettyTcpServer.getInstance();
|
||||
// nettyTcpServer.setPacketSeparator("#");
|
||||
if (!nettyTcpServer.isServerStart()) {
|
||||
nettyTcpServer.setListener(NettyServerActivity.this);
|
||||
|
||||
nettyTcpServer.start();
|
||||
} else {
|
||||
NettyTcpServer.getInstance().disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
private void registerNsdServer() {
|
||||
new Thread(nsdServerRunnable).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessageResponseServer(String msg, String uniqueId) {
|
||||
// Log.e(TAG,"onMessageResponseServer:ChannelId:"+uniqueId);
|
||||
logRece(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChannelConnect(final Channel channel) {
|
||||
|
||||
String socketStr = channel.remoteAddress().toString();
|
||||
final ClientChanel clientChanel = new ClientChanel(socketStr, channel, channel.id().asShortText());
|
||||
|
||||
synchronized (clientChanelArray) {
|
||||
clientChanelArray.add(clientChanel);
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Toast.makeText(NettyServerActivity.this, clientChanel.getClientIp() + " 建立连接", Toast.LENGTH_LONG).show();
|
||||
spinnerAdapter.notifyDataSetChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChannelDisConnect(Channel channel) {
|
||||
Log.e(TAG, "onChannelDisConnect:ChannelId" + channel.id().asShortText());
|
||||
|
||||
for (int i = 0; i < clientChanelArray.size(); i++) {
|
||||
final ClientChanel clientChanel = clientChanelArray.get(i);
|
||||
if (clientChanel.getShortId().equals(channel.id().asShortText())) {
|
||||
|
||||
/**
|
||||
* 当Spinner里第一个item被remove,不会触发onItemSelected,(因为 mSelectedPosition != mOldSelectedPosition)
|
||||
*/
|
||||
if (i == 0) {
|
||||
try {
|
||||
Field field = AdapterView.class.getDeclaredField("mOldSelectedPosition");
|
||||
field.setAccessible(true); //设置mOldSelectedPosition可访问
|
||||
field.setInt(mSpinner, AdapterView.INVALID_POSITION); //设置mOldSelectedPosition的值
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
synchronized (clientChanelArray) {
|
||||
clientChanelArray.remove(clientChanel);
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Log.e(TAG, "disconncect " + clientChanel.getClientIp());
|
||||
Toast.makeText(NettyServerActivity.this, clientChanel.getClientIp() + " 断开连接", Toast.LENGTH_LONG).show();
|
||||
spinnerAdapter.notifyDataSetChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartServer() {
|
||||
registerNsdServer();
|
||||
Log.e(TAG, "onStartServer");
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
startServer.setText("stopServer");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopServer() {
|
||||
Log.e(TAG, "onStopServer");
|
||||
if (mNsdServer != null) {
|
||||
mNsdServer.stopNSDServer();
|
||||
}
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
startServer.setText("startServer");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private void logSend(String log) {
|
||||
LogBean logBean = new LogBean(System.currentTimeMillis(), log);
|
||||
mSendLogAdapter.getDataList().add(0, logBean);
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mSendLogAdapter.notifyDataSetChanged();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private void logRece(String log) {
|
||||
LogBean logBean = new LogBean(System.currentTimeMillis(), log);
|
||||
mReceLogAdapter.getDataList().add(0, logBean);
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mReceLogAdapter.notifyDataSetChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 服务器端注册一个可供NSD探测到的网络 Ip 地址,便于给展示叫号机连接此socket
|
||||
*/
|
||||
Runnable nsdServerRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
mNsdServer = new NSDServer();
|
||||
mNsdServer.startNSDServer(NettyServerActivity.this, NettyTcpServer.SERVER_NAME, NettyTcpServer.SERVER_PORT);
|
||||
|
||||
mNsdServer.setRegisterState(new NSDServer.IRegisterState() {
|
||||
@Override
|
||||
public void onServiceRegistered(NsdServiceInfo serviceInfo) {
|
||||
Log.i(TAG, "已注册服务onServiceRegistered: " + serviceInfo.toString());
|
||||
//已经注册可停止该服务
|
||||
// nsdServer.stopNSDServer();
|
||||
}
|
||||
@Override
|
||||
public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
|
||||
|
||||
}
|
||||
@Override
|
||||
public void onServiceUnregistered(NsdServiceInfo serviceInfo) {
|
||||
|
||||
}
|
||||
@Override
|
||||
public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -112,6 +112,14 @@
|
||||
android:text="查看周边可直播车"
|
||||
android:visibility="visible" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnNSDNetty"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:text="测试NSDNetty通信"
|
||||
android:textAllCaps="false"
|
||||
android:visibility="visible" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnV2XFunctionTest"
|
||||
android:layout_width="match_parent"
|
||||
|
||||
113
app/src/main/res/layout/activity_netty_client.xml
Normal file
113
app/src/main/res/layout/activity_netty_client.xml
Normal file
@@ -0,0 +1,113 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/form"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="发送" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/send_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="150dp"
|
||||
android:background="#dfdddd" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_marginTop="10dp"
|
||||
android:text="接收" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rece_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="150dp"
|
||||
android:background="#dfdddd" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_ip"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="35dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:hint="请输入服务器ip"
|
||||
android:maxLines="20"
|
||||
android:padding="5dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<Button
|
||||
android:id="@+id/connect"
|
||||
style="?android:textAppearanceSmall"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Connect"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/clear_log"
|
||||
style="?android:textAppearanceSmall"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="CLEAR_LOG"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<Button
|
||||
style="?android:textAppearanceSmall"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="断开"
|
||||
android:onClick="disconnect"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/send_et"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="100dp"
|
||||
android:layout_weight="1"
|
||||
android:gravity="top"
|
||||
android:hint="输入要发送的文字"
|
||||
android:maxLines="100"
|
||||
android:padding="5dp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/send_btn"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="3"
|
||||
android:text="SEND" />
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</LinearLayout>
|
||||
111
app/src/main/res/layout/activity_netty_server.xml
Normal file
111
app/src/main/res/layout/activity_netty_server.xml
Normal file
@@ -0,0 +1,111 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/form"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="发送" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/send_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="150dp"
|
||||
android:background="#dfdddd" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/receiveTv"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_marginTop="10dp"
|
||||
android:text="接收" />
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinner"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rece_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="150dp"
|
||||
android:background="#dfdddd" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_ip"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAllCaps="false"
|
||||
android:layout_marginTop="5dp"
|
||||
tools:text="当前网络ip是192.168.0.1"
|
||||
android:textStyle="bold"
|
||||
/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<Button
|
||||
android:id="@+id/startServer"
|
||||
style="?android:textAppearanceSmall"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="startServer"
|
||||
android:textAllCaps="false"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/clear_log"
|
||||
style="?android:textAppearanceSmall"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="CLEAR_LOG"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/send_et"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="100dp"
|
||||
android:layout_weight="1"
|
||||
android:gravity="top"
|
||||
android:hint="输入要发送的文字"
|
||||
android:maxLines="100"
|
||||
android:padding="5dp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/send_btn"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="3"
|
||||
android:text="SEND" />
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</LinearLayout>
|
||||
28
app/src/main/res/layout/activity_nsdnetty.xml
Normal file
28
app/src/main/res/layout/activity_nsdnetty.xml
Normal file
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".NSDNettyActivity">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnNettyServer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:text="进入NettyServer"
|
||||
android:textAllCaps="false"
|
||||
android:visibility="visible"
|
||||
/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnNettyClient"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toBottomOf="@id/btnNettyServer"
|
||||
android:text="进入NettyClient"
|
||||
android:textAllCaps="false"
|
||||
android:visibility="visible" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
20
app/src/main/res/layout/log_item.xml
Normal file
20
app/src/main/res/layout/log_item.xml
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/time"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="--"
|
||||
android:textSize="13sp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/logtext"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="--"
|
||||
android:textSize="13sp"/>
|
||||
</LinearLayout>
|
||||
@@ -29,7 +29,7 @@ android {
|
||||
dependencies {
|
||||
implementation fileTree(dir: "libs", include: ["*.jar"])
|
||||
api rootProject.ext.dependencies.spi
|
||||
|
||||
api 'io.netty:netty-all:4.1.8.Final'
|
||||
|
||||
if (Boolean.valueOf(RELEASE)) {
|
||||
|
||||
|
||||
@@ -4,4 +4,5 @@
|
||||
android:name="android.permission.ACCESS_COARSE_LOCATION"/>
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
</manifest>
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.mogo.telematic;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.net.wifi.WifiInfo;
|
||||
import android.net.wifi.WifiManager;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.util.Enumeration;
|
||||
|
||||
public class NetworkUtils {
|
||||
public static String getIPAddress(Context context) {
|
||||
NetworkInfo info = ((ConnectivityManager)
|
||||
context.getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE)).getActiveNetworkInfo();
|
||||
if (info != null && info.isConnected()) {
|
||||
if (info.getType() == ConnectivityManager.TYPE_MOBILE) {//当前使用2G/3G/4G网络
|
||||
try {
|
||||
for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements(); ) {
|
||||
NetworkInterface intf = en.nextElement();
|
||||
for (Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements(); ) {
|
||||
InetAddress inetAddress = enumIpAddr.nextElement();
|
||||
if (!inetAddress.isLoopbackAddress() && inetAddress instanceof Inet4Address) {
|
||||
return "当前网络ip是" + inetAddress.getHostAddress();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (SocketException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} else if (info.getType() == ConnectivityManager.TYPE_WIFI) {//当前使用无线网络
|
||||
WifiManager wifiManager = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
|
||||
WifiInfo wifiInfo = wifiManager.getConnectionInfo();
|
||||
int ipAddress = wifiInfo.getIpAddress();
|
||||
if (ipAddress == 0) return "未连接wifi";
|
||||
return "当前网络ip是" + ((ipAddress & 0xff) + "." + (ipAddress >> 8 & 0xff) + "."
|
||||
+ (ipAddress >> 16 & 0xff) + "." + (ipAddress >> 24 & 0xff));
|
||||
}
|
||||
} else {
|
||||
//当前无网络连接,请在设置中打开网络
|
||||
return "当前无网络连接,请在设置中打开网络";
|
||||
}
|
||||
return "IP获取失败";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,433 @@
|
||||
package com.mogo.telematic.client;
|
||||
|
||||
import android.os.SystemClock;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import com.mogo.telematic.client.handler.NettyClientHandler;
|
||||
import com.mogo.telematic.client.listener.MessageStateListener;
|
||||
import com.mogo.telematic.client.listener.NettyClientListener;
|
||||
import com.mogo.telematic.client.status.ConnectState;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
|
||||
import io.netty.handler.codec.LineBasedFrameDecoder;
|
||||
import io.netty.handler.codec.string.StringDecoder;
|
||||
import io.netty.handler.codec.string.StringEncoder;
|
||||
import io.netty.handler.timeout.IdleStateHandler;
|
||||
import io.netty.util.CharsetUtil;
|
||||
|
||||
/**
|
||||
* TCP 客户端
|
||||
*/
|
||||
public class NettyTcpClient {
|
||||
private static final String TAG = "NettyTcpClient";
|
||||
|
||||
private EventLoopGroup group;
|
||||
|
||||
private NettyClientListener listener;
|
||||
|
||||
private Channel channel;
|
||||
|
||||
private boolean isConnect = false;
|
||||
|
||||
/**
|
||||
* 最大重连次数
|
||||
*/
|
||||
private int MAX_CONNECT_TIMES = Integer.MAX_VALUE;
|
||||
|
||||
private int reconnectNum = MAX_CONNECT_TIMES;
|
||||
|
||||
private boolean isNeedReconnect = true;
|
||||
private boolean isConnecting = false;
|
||||
|
||||
private long reconnectIntervalTime = 5000;
|
||||
private static final Integer CONNECT_TIMEOUT_MILLIS = 5000;
|
||||
|
||||
private String host;
|
||||
private int tcp_port;
|
||||
private int mIndex;
|
||||
/**
|
||||
* 心跳间隔时间
|
||||
*/
|
||||
private long heartBeatInterval = 5;//单位秒
|
||||
|
||||
/**
|
||||
* 是否发送心跳
|
||||
*/
|
||||
private boolean isSendheartBeat = false;
|
||||
|
||||
/**
|
||||
* 心跳数据,可以是String类型,也可以是byte[].
|
||||
*/
|
||||
private Object heartBeatData;
|
||||
|
||||
private String packetSeparator;
|
||||
private int maxPacketLong = 1024;
|
||||
|
||||
private void setPacketSeparator(String separator) {
|
||||
this.packetSeparator = separator;
|
||||
}
|
||||
|
||||
private void setMaxPacketLong(int maxPacketLong) {
|
||||
this.maxPacketLong = maxPacketLong;
|
||||
}
|
||||
|
||||
private NettyTcpClient(String host, int tcp_port, int index) {
|
||||
this.host = host;
|
||||
this.tcp_port = tcp_port;
|
||||
this.mIndex = index;
|
||||
}
|
||||
|
||||
public int getMaxConnectTimes() {
|
||||
return MAX_CONNECT_TIMES;
|
||||
}
|
||||
|
||||
public long getReconnectIntervalTime() {
|
||||
return reconnectIntervalTime;
|
||||
}
|
||||
|
||||
public String getHost() {
|
||||
return host;
|
||||
}
|
||||
|
||||
public int getTcp_port() {
|
||||
return tcp_port;
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return mIndex;
|
||||
}
|
||||
|
||||
public long getHeartBeatInterval() {
|
||||
return heartBeatInterval;
|
||||
}
|
||||
|
||||
public boolean isSendheartBeat() {
|
||||
return isSendheartBeat;
|
||||
}
|
||||
|
||||
public void connect() {
|
||||
if (isConnecting) {
|
||||
return;
|
||||
}
|
||||
Thread clientThread = new Thread("client-Netty") {
|
||||
@Override
|
||||
public void run() {
|
||||
super.run();
|
||||
isNeedReconnect = true;
|
||||
reconnectNum = MAX_CONNECT_TIMES;
|
||||
connectServer();
|
||||
}
|
||||
};
|
||||
clientThread.start();
|
||||
}
|
||||
|
||||
|
||||
private void connectServer() {
|
||||
synchronized (NettyTcpClient.this) {
|
||||
ChannelFuture channelFuture = null;
|
||||
if (!isConnect) {
|
||||
isConnecting = true;
|
||||
group = new NioEventLoopGroup();
|
||||
Bootstrap bootstrap = new Bootstrap().group(group)
|
||||
.option(ChannelOption.TCP_NODELAY, true)//屏蔽Nagle算法试图
|
||||
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
|
||||
.channel(NioSocketChannel.class)
|
||||
.handler(new ChannelInitializer<SocketChannel>() {
|
||||
@Override
|
||||
public void initChannel(SocketChannel ch) throws Exception {
|
||||
if (isSendheartBeat) {
|
||||
ch.pipeline().addLast("ping", new IdleStateHandler(0, heartBeatInterval, 0, TimeUnit.SECONDS));//5s未发送数据,回调userEventTriggered
|
||||
}
|
||||
|
||||
//黏包处理,需要客户端、服务端配合
|
||||
if (!TextUtils.isEmpty(packetSeparator)) {
|
||||
ByteBuf delimiter= Unpooled.buffer();
|
||||
delimiter.writeBytes(packetSeparator.getBytes());
|
||||
ch.pipeline().addLast(new DelimiterBasedFrameDecoder(maxPacketLong,delimiter));
|
||||
} else {
|
||||
ch.pipeline().addLast(new LineBasedFrameDecoder(maxPacketLong));
|
||||
}
|
||||
ch.pipeline().addLast(new StringEncoder(CharsetUtil.UTF_8));
|
||||
ch.pipeline().addLast(new StringDecoder(CharsetUtil.UTF_8));
|
||||
|
||||
|
||||
ch.pipeline().addLast(new NettyClientHandler(listener, mIndex, isSendheartBeat, heartBeatData,packetSeparator));
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
channelFuture = bootstrap.connect(host, tcp_port).addListener(new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture channelFuture) throws Exception {
|
||||
if (channelFuture.isSuccess()) {
|
||||
Log.e(TAG, "连接成功");
|
||||
reconnectNum = MAX_CONNECT_TIMES;
|
||||
isConnect = true;
|
||||
channel = channelFuture.channel();
|
||||
} else {
|
||||
Log.e(TAG, "连接失败");
|
||||
isConnect = false;
|
||||
}
|
||||
isConnecting = false;
|
||||
}
|
||||
}).sync();
|
||||
|
||||
// Wait until the connection is closed.
|
||||
channelFuture.channel().closeFuture().sync();
|
||||
Log.e(TAG, " 断开连接");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
isConnect = false;
|
||||
listener.onClientStatusConnectChanged(ConnectState.STATUS_CONNECT_CLOSED, mIndex);
|
||||
if (null != channelFuture) {
|
||||
if (channelFuture.channel() != null && channelFuture.channel().isOpen()) {
|
||||
channelFuture.channel().close();
|
||||
}
|
||||
}
|
||||
group.shutdownGracefully();
|
||||
reconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void disconnect() {
|
||||
Log.e(TAG, "disconnect");
|
||||
isNeedReconnect = false;
|
||||
group.shutdownGracefully();
|
||||
}
|
||||
|
||||
public void reconnect() {
|
||||
Log.e(TAG, "reconnect");
|
||||
if (isNeedReconnect && reconnectNum > 0 && !isConnect) {
|
||||
reconnectNum--;
|
||||
SystemClock.sleep(reconnectIntervalTime);
|
||||
if (isNeedReconnect && reconnectNum > 0 && !isConnect) {
|
||||
Log.e(TAG, "重新连接");
|
||||
connectServer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步发送
|
||||
*
|
||||
* @param data 要发送的数据
|
||||
* @param listener 发送结果回调
|
||||
* @return 方法执行结果
|
||||
*/
|
||||
public boolean sendMsgToServer(String data, final MessageStateListener listener) {
|
||||
boolean flag = channel != null && isConnect;
|
||||
if (flag) {
|
||||
String separator = TextUtils.isEmpty(packetSeparator) ? System.getProperty("line.separator") : packetSeparator;
|
||||
ChannelFuture channelFuture = channel.writeAndFlush(data + separator).addListener(new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture channelFuture) throws Exception {
|
||||
listener.isSendSuccss(channelFuture.isSuccess());
|
||||
}
|
||||
});
|
||||
}
|
||||
return flag;
|
||||
|
||||
// ByteBuf buffer = Unpooled.copiedBuffer(data, Charset.forName("UTF-8"));
|
||||
// if (flag) {
|
||||
// channel.writeAndFlush(buffer).addListener(listener);
|
||||
// }
|
||||
// return flag;
|
||||
// byte[] bytes = strToByteArray(data);
|
||||
//
|
||||
// return sendMsgToServer(bytes,listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步发送
|
||||
*
|
||||
* @param data 要发送的数据
|
||||
* @return 方法执行结果
|
||||
*/
|
||||
public boolean sendMsgToServer(String data) {
|
||||
boolean flag = channel != null && isConnect;
|
||||
if (flag) {
|
||||
String separator = TextUtils.isEmpty(packetSeparator) ? System.getProperty("line.separator") : packetSeparator;
|
||||
ChannelFuture channelFuture = channel.writeAndFlush(data + separator).awaitUninterruptibly();
|
||||
return channelFuture.isSuccess();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public boolean sendMsgToServer(byte[] data, final MessageStateListener listener) {
|
||||
boolean flag = channel != null && isConnect;
|
||||
if (flag) {
|
||||
ByteBuf buf = Unpooled.copiedBuffer(data);
|
||||
channel.writeAndFlush(buf).addListener(new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture channelFuture) throws Exception {
|
||||
listener.isSendSuccss(channelFuture.isSuccess());
|
||||
}
|
||||
});
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取TCP连接状态
|
||||
*
|
||||
* @return 获取TCP连接状态
|
||||
*/
|
||||
public boolean getConnectStatus() {
|
||||
return isConnect;
|
||||
}
|
||||
|
||||
public boolean isConnecting() {
|
||||
return isConnecting;
|
||||
}
|
||||
|
||||
public void setConnectStatus(boolean status) {
|
||||
this.isConnect = status;
|
||||
}
|
||||
|
||||
public void setListener(NettyClientListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public byte[] strToByteArray(String str) {
|
||||
if (str == null) {
|
||||
return null;
|
||||
}
|
||||
byte[] byteArray = str.getBytes();
|
||||
return byteArray;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建者,创建NettyTcpClient
|
||||
*/
|
||||
public static class Builder {
|
||||
|
||||
/**
|
||||
* 最大重连次数
|
||||
*/
|
||||
private int MAX_CONNECT_TIMES = Integer.MAX_VALUE;
|
||||
|
||||
/**
|
||||
* 重连间隔
|
||||
*/
|
||||
private long reconnectIntervalTime = 5000;
|
||||
/**
|
||||
* 服务器地址
|
||||
*/
|
||||
private String host;
|
||||
/**
|
||||
* 服务器端口
|
||||
*/
|
||||
private int tcp_port;
|
||||
/**
|
||||
* 客户端标识,(因为可能存在多个连接)
|
||||
*/
|
||||
private int mIndex;
|
||||
|
||||
/**
|
||||
* 是否发送心跳
|
||||
*/
|
||||
private boolean isSendheartBeat;
|
||||
/**
|
||||
* 心跳时间间隔
|
||||
*/
|
||||
private long heartBeatInterval = 5;
|
||||
|
||||
/**
|
||||
* 心跳数据,可以是String类型,也可以是byte[].
|
||||
*/
|
||||
private Object heartBeatData;
|
||||
|
||||
private String packetSeparator;
|
||||
private int maxPacketLong = 1024;
|
||||
|
||||
public Builder() {
|
||||
this.maxPacketLong = 1024;
|
||||
}
|
||||
|
||||
|
||||
public Builder setPacketSeparator(String packetSeparator) {
|
||||
this.packetSeparator = packetSeparator;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setMaxPacketLong(int maxPacketLong) {
|
||||
this.maxPacketLong = maxPacketLong;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setMaxReconnectTimes(int reConnectTimes) {
|
||||
this.MAX_CONNECT_TIMES = reConnectTimes;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Builder setReconnectIntervalTime(long reconnectIntervalTime) {
|
||||
this.reconnectIntervalTime = reconnectIntervalTime;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Builder setHost(String host) {
|
||||
this.host = host;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setTcpPort(int tcp_port) {
|
||||
this.tcp_port = tcp_port;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setIndex(int mIndex) {
|
||||
this.mIndex = mIndex;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setHeartBeatInterval(long intervalTime) {
|
||||
this.heartBeatInterval = intervalTime;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setSendheartBeat(boolean isSendheartBeat) {
|
||||
this.isSendheartBeat = isSendheartBeat;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setHeartBeatData(Object heartBeatData) {
|
||||
this.heartBeatData = heartBeatData;
|
||||
return this;
|
||||
}
|
||||
|
||||
public NettyTcpClient build() {
|
||||
NettyTcpClient nettyTcpClient = new NettyTcpClient(host, tcp_port, mIndex);
|
||||
nettyTcpClient.MAX_CONNECT_TIMES = this.MAX_CONNECT_TIMES;
|
||||
nettyTcpClient.reconnectIntervalTime = this.reconnectIntervalTime;
|
||||
nettyTcpClient.heartBeatInterval = this.heartBeatInterval;
|
||||
nettyTcpClient.isSendheartBeat = this.isSendheartBeat;
|
||||
nettyTcpClient.heartBeatData = this.heartBeatData;
|
||||
nettyTcpClient.packetSeparator = this.packetSeparator;
|
||||
nettyTcpClient.maxPacketLong = this.maxPacketLong;
|
||||
return nettyTcpClient;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
package com.mogo.telematic.client;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.nsd.NsdManager;
|
||||
import android.net.nsd.NsdServiceInfo;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.util.Log;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.ArrayList;
|
||||
|
||||
|
||||
public class NsdClient {
|
||||
|
||||
public static final String TAG = "NsdClient";
|
||||
|
||||
/**
|
||||
* NSD_SERVICE_NAME和NSD_SERVER_TYPE需要与服务器端完全一致
|
||||
*/
|
||||
private final String NSD_SERVER_TYPE = "_http._tcp.";
|
||||
|
||||
private NsdManager.DiscoveryListener mDiscoveryListener;
|
||||
private NsdManager.ResolveListener mResolverListener;
|
||||
private NsdManager mNsdManager;
|
||||
private Context mContext;
|
||||
private String mServiceName;
|
||||
private IServerFound mIServerFound;
|
||||
|
||||
/**
|
||||
* 用来存储解析后的网络对象列表,包含完整数据
|
||||
*/
|
||||
private ArrayList<NsdServiceInfo> mNsdServiceInfoList = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 未解析前搜索到的
|
||||
*/
|
||||
private ArrayList<NsdServiceInfo> mNsdServiceInfoListBefore = new ArrayList<>();
|
||||
|
||||
|
||||
private static final int MSG_RESOLVER = 1002;
|
||||
|
||||
private static final int MSG_NULL = 1003;
|
||||
|
||||
|
||||
int count;
|
||||
|
||||
/**
|
||||
* @param context this
|
||||
* @param serviceName 客户端扫描 指定的地址 暂时没用到
|
||||
* @param iServerFound 回调
|
||||
*/
|
||||
public NsdClient(Context context, String serviceName, IServerFound iServerFound) {
|
||||
mContext = context;
|
||||
mServiceName = serviceName;
|
||||
mIServerFound = iServerFound;
|
||||
}
|
||||
|
||||
public void startNSDClient() {
|
||||
mNsdManager = (NsdManager) mContext.getSystemService(Context.NSD_SERVICE);
|
||||
initializeDiscoveryListener();
|
||||
mNsdManager.discoverServices(NSD_SERVER_TYPE, NsdManager.PROTOCOL_DNS_SD, mDiscoveryListener);
|
||||
initializeResolveListener();
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫描未被解析前的 NsdServiceInfo
|
||||
* 用于服务发现的回调调用接口
|
||||
*/
|
||||
private void initializeDiscoveryListener() {
|
||||
mDiscoveryListener = new NsdManager.DiscoveryListener() {
|
||||
@Override
|
||||
public void onStartDiscoveryFailed(String serviceType, int errorCode) {
|
||||
mNsdManager.stopServiceDiscovery(this);
|
||||
Log.e(TAG, "onStartDiscoveryFailed():");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopDiscoveryFailed(String serviceType, int errorCode) {
|
||||
mNsdManager.stopServiceDiscovery(this);
|
||||
Log.e(TAG, "onStopDiscoveryFailed():");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDiscoveryStarted(String serviceType) {
|
||||
Log.e(TAG, "onDiscoveryStarted():");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDiscoveryStopped(String serviceType) {
|
||||
Log.e(TAG, "onDiscoveryStopped():");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param serviceInfo
|
||||
*/
|
||||
@Override
|
||||
public void onServiceFound(NsdServiceInfo serviceInfo) {
|
||||
//根据咱服务器的定义名称,指定解析该 NsdServiceInfo
|
||||
if (serviceInfo.getServiceName().equals(mServiceName)) {
|
||||
mNsdManager.resolveService(serviceInfo, mResolverListener);
|
||||
} else {
|
||||
mHandler.sendEmptyMessage(MSG_NULL);
|
||||
}
|
||||
|
||||
Log.e(TAG, "onServiceFound():");
|
||||
|
||||
mNsdServiceInfoListBefore.add(serviceInfo);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceLost(NsdServiceInfo serviceInfo) {
|
||||
Log.e(TAG, "onServiceLost(): serviceInfo=" + serviceInfo);
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private Handler mHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
super.handleMessage(msg);
|
||||
switch (msg.what) {
|
||||
case MSG_RESOLVER:
|
||||
|
||||
//回调到主线 进行解析結果的回調
|
||||
NsdServiceInfo serviceInfo = (NsdServiceInfo) msg.obj;
|
||||
if (mIServerFound != null) {
|
||||
mIServerFound.onServerFound(serviceInfo, serviceInfo.getPort());
|
||||
}
|
||||
Log.e(TAG, " 指定onServiceFound(" + mServiceName + "): Service Info: --> " + serviceInfo);
|
||||
|
||||
break;
|
||||
case MSG_NULL:
|
||||
if (mIServerFound != null) {
|
||||
mIServerFound.onServerFail();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 解析未 调用未被解析的 NsdServiceInfo
|
||||
*/
|
||||
private void initializeResolveListener() {
|
||||
mResolverListener = new NsdManager.ResolveListener() {
|
||||
@Override
|
||||
public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceResolved(NsdServiceInfo serviceInfo) {
|
||||
int port = serviceInfo.getPort();
|
||||
InetAddress host = serviceInfo.getHost();
|
||||
String serviceName = serviceInfo.getServiceName();
|
||||
String hostAddress = serviceInfo.getHost().getHostAddress();
|
||||
Log.i(TAG, "onServiceResolved 已解析:" + " host:" + hostAddress + ":" + port + " ----- serviceName: " + serviceName);
|
||||
|
||||
mNsdServiceInfoList.add(serviceInfo);
|
||||
|
||||
//解析的结果 通过Handler发送到主线程
|
||||
Message msg = Message.obtain();
|
||||
msg.what = MSG_RESOLVER;
|
||||
msg.obj = serviceInfo;
|
||||
mHandler.sendMessageDelayed(msg, 500);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public void stopNSDServer() {
|
||||
mNsdManager.stopServiceDiscovery(mDiscoveryListener);
|
||||
}
|
||||
|
||||
public interface IServerFound {
|
||||
|
||||
// void onServerFound(InetAddress host, int port);
|
||||
|
||||
/**
|
||||
* 回調 指定解析的结果
|
||||
*/
|
||||
void onServerFound(NsdServiceInfo serviceInfo, int port);
|
||||
|
||||
// void onServerFoundList(ArrayList<NsdServiceInfo> NsdServiceInfoList);
|
||||
|
||||
/**
|
||||
* 無合適 回調失敗
|
||||
*/
|
||||
void onServerFail();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package com.mogo.telematic.client.handler;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import com.mogo.telematic.client.listener.NettyClientListener;
|
||||
import com.mogo.telematic.client.status.ConnectState;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.handler.timeout.IdleState;
|
||||
import io.netty.handler.timeout.IdleStateEvent;
|
||||
|
||||
|
||||
public class NettyClientHandler extends SimpleChannelInboundHandler<String> {
|
||||
|
||||
private static final String TAG = "NettyClientHandler";
|
||||
private final boolean isSendheartBeat;
|
||||
private NettyClientListener listener;
|
||||
private int index;
|
||||
private Object heartBeatData;
|
||||
private String packetSeparator;
|
||||
|
||||
// private static final ByteBuf HEARTBEAT_SEQUENCE = Unpooled.unreleasableBuffer(Unpooled.copiedBuffer("Heartbeat"+System.getProperty("line.separator"),
|
||||
// CharsetUtil.UTF_8));
|
||||
byte[] requestBody = {(byte) 0xFE, (byte) 0xED, (byte) 0xFE, 5, 4, (byte) 0xFF, 0x0a};
|
||||
|
||||
|
||||
public NettyClientHandler(NettyClientListener listener, int index, boolean isSendheartBeat, Object heartBeatData) {
|
||||
this(listener,index,isSendheartBeat,heartBeatData,null);
|
||||
}
|
||||
|
||||
public NettyClientHandler(NettyClientListener listener, int index, boolean isSendheartBeat, Object heartBeatData,String separator) {
|
||||
this.listener = listener;
|
||||
this.index = index;
|
||||
this.isSendheartBeat = isSendheartBeat;
|
||||
this.heartBeatData = heartBeatData;
|
||||
this.packetSeparator = TextUtils.isEmpty(separator) ? System.getProperty("line.separator") : separator;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>设定IdleStateHandler心跳检测每x秒进行一次读检测,
|
||||
* 如果x秒内ChannelRead()方法未被调用则触发一次userEventTrigger()方法 </p>
|
||||
*
|
||||
* @param ctx ChannelHandlerContext
|
||||
* @param evt IdleStateEvent
|
||||
*/
|
||||
@Override
|
||||
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
|
||||
if (evt instanceof IdleStateEvent) {
|
||||
IdleStateEvent event = (IdleStateEvent) evt;
|
||||
if (event.state() == IdleState.WRITER_IDLE) { //发送心跳
|
||||
// ctx.channel().writeAndFlush("Heartbeat" + System.getProperty("line.separator"));
|
||||
if (isSendheartBeat) {
|
||||
if (heartBeatData == null) {
|
||||
ctx.channel().writeAndFlush("Heartbeat" + packetSeparator);
|
||||
} else {
|
||||
if (heartBeatData instanceof String) {
|
||||
// Log.d(TAG, "userEventTriggered: String");
|
||||
ctx.channel().writeAndFlush(heartBeatData + packetSeparator);
|
||||
} else if (heartBeatData instanceof byte[]) {
|
||||
// Log.d(TAG, "userEventTriggered: byte");
|
||||
ByteBuf buf = Unpooled.copiedBuffer((byte[]) heartBeatData);
|
||||
ctx.channel().writeAndFlush(buf);
|
||||
} else {
|
||||
Log.e(TAG, "userEventTriggered: heartBeatData type error");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.e(TAG, "不发送心跳");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>客户端上线</p>
|
||||
*
|
||||
* @param ctx ChannelHandlerContext
|
||||
*/
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) {
|
||||
Log.e(TAG, "channelActive");
|
||||
// NettyTcpClient.getInstance().setConnectStatus(true);
|
||||
listener.onClientStatusConnectChanged(ConnectState.STATUS_CONNECT_SUCCESS, index);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>客户端下线</p>
|
||||
*
|
||||
* @param ctx ChannelHandlerContext
|
||||
*/
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) {
|
||||
Log.e(TAG, "channelInactive");
|
||||
// NettyTcpClient.getInstance().setConnectStatus(false);
|
||||
// listener.onServiceStatusConnectChanged(NettyClientListener.STATUS_CONNECT_CLOSED);
|
||||
// NettyTcpClient.getInstance().reconnect();
|
||||
}
|
||||
|
||||
/**
|
||||
* 客户端收到消息
|
||||
*
|
||||
* @param channelHandlerContext ChannelHandlerContext
|
||||
* @param msg 消息
|
||||
*/
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext channelHandlerContext, String msg) {
|
||||
Log.e(TAG, "channelRead0:"+msg);
|
||||
listener.onMessageResponseClient(msg, index);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ctx ChannelHandlerContext
|
||||
* @param cause 异常
|
||||
*/
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
// Close the connection when an exception is raised.
|
||||
// NettyTcpClient.getInstance().setConnectStatus(false);
|
||||
Log.e(TAG, "exceptionCaught");
|
||||
listener.onClientStatusConnectChanged(ConnectState.STATUS_CONNECT_ERROR, index);
|
||||
cause.printStackTrace();
|
||||
ctx.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.mogo.telematic.client.listener;
|
||||
|
||||
|
||||
/**
|
||||
* 发送状态监听
|
||||
*/
|
||||
public interface MessageStateListener {
|
||||
void isSendSuccss(boolean isSuccess);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.mogo.telematic.client.listener;
|
||||
|
||||
|
||||
/**
|
||||
* TCP状态变化监听
|
||||
*/
|
||||
public interface NettyClientListener<T> {
|
||||
|
||||
/**
|
||||
* 当接收到系统消息
|
||||
* @param msg 消息
|
||||
* @param index tcp 客户端的标识,因为一个应用程序可能有很多个长链接
|
||||
*/
|
||||
void onMessageResponseClient(T msg, int index);
|
||||
|
||||
/**
|
||||
* 当服务状态发生变化时触发
|
||||
* @param statusCode 状态变化
|
||||
* @param index tcp 客户端的标识,因为一个应用程序可能有很多个长链接
|
||||
*/
|
||||
public void onClientStatusConnectChanged(int statusCode, int index);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.mogo.telematic.client.status;
|
||||
|
||||
/**
|
||||
* 连接状态
|
||||
*/
|
||||
public class ConnectState {
|
||||
public final static int STATUS_CONNECT_SUCCESS = 1;
|
||||
|
||||
public final static int STATUS_CONNECT_CLOSED = 0;
|
||||
|
||||
public final static int STATUS_CONNECT_ERROR = -1;
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package com.mogo.telematic.server;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.nsd.NsdManager;
|
||||
import android.net.nsd.NsdServiceInfo;
|
||||
import android.util.Log;
|
||||
|
||||
public class NSDServer {
|
||||
public static final String TAG = "NSDServer";
|
||||
private NsdManager mNsdManager;
|
||||
private NsdManager.RegistrationListener mRegistrationListener;
|
||||
private String mServerName;
|
||||
private Context mContext;
|
||||
private int mPort;
|
||||
private String mServiceName;
|
||||
private final String mServerType = "_http._tcp."; // 服务器type,要客户端扫描服务器的一致
|
||||
|
||||
public NSDServer() {
|
||||
}
|
||||
|
||||
public void startNSDServer(Context context, String serviceName, int port) {
|
||||
initializeRegistrationListener();
|
||||
registerService(context, serviceName, port);
|
||||
}
|
||||
|
||||
//实例化注册监听器
|
||||
private void initializeRegistrationListener() {
|
||||
mRegistrationListener = new NsdManager.RegistrationListener() {
|
||||
@Override
|
||||
public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
|
||||
Log.e(TAG, "NsdServiceInfo onRegistrationFailed");
|
||||
if (registerState != null) {
|
||||
registerState.onRegistrationFailed(serviceInfo, errorCode);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
|
||||
Log.i(TAG, "onUnregistrationFailed serviceInfo: " + serviceInfo + " ,errorCode:" + errorCode);
|
||||
if (registerState != null) {
|
||||
registerState.onUnregistrationFailed(serviceInfo, errorCode);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceRegistered(NsdServiceInfo serviceInfo) {
|
||||
mServerName = serviceInfo.getServiceName();
|
||||
Log.i(TAG, "onServiceRegistered: " + serviceInfo.toString());
|
||||
Log.i(TAG, "mServerName onServiceRegistered: " + mServerName);
|
||||
if (registerState != null) {
|
||||
registerState.onServiceRegistered(serviceInfo);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceUnregistered(NsdServiceInfo serviceInfo) {
|
||||
Log.i(TAG, "onServiceUnregistered serviceInfo: " + serviceInfo);
|
||||
if (registerState != null) {
|
||||
registerState.onServiceUnregistered(serviceInfo);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void registerService(Context context, String serviceName, int port) {
|
||||
mNsdManager = (NsdManager) context.getSystemService(Context.NSD_SERVICE);
|
||||
NsdServiceInfo serviceInfo = new NsdServiceInfo();
|
||||
serviceInfo.setServiceName(serviceName);
|
||||
serviceInfo.setPort(port);
|
||||
serviceInfo.setServiceType(mServerType);
|
||||
mNsdManager.registerService(serviceInfo, NsdManager.PROTOCOL_DNS_SD, mRegistrationListener);
|
||||
}
|
||||
|
||||
public void stopNSDServer() {
|
||||
mNsdManager.unregisterService(mRegistrationListener);
|
||||
}
|
||||
|
||||
|
||||
//NSD服务注册监听接口
|
||||
public interface IRegisterState {
|
||||
void onServiceRegistered(NsdServiceInfo serviceInfo); //注册NSD成功
|
||||
|
||||
void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode); //注册NSD失败
|
||||
|
||||
void onServiceUnregistered(NsdServiceInfo serviceInfo); //取消NSD注册成功
|
||||
|
||||
void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode); //取消NSD注册失败
|
||||
|
||||
}
|
||||
|
||||
//NSD服务接口对象
|
||||
private IRegisterState registerState;
|
||||
|
||||
|
||||
//设置NSD服务接口对象
|
||||
public void setRegisterState(IRegisterState registerState) {
|
||||
this.registerState = registerState;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.mogo.telematic.server.bean;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
|
||||
/**
|
||||
* 客户端信息
|
||||
*/
|
||||
public class ClientChanel {
|
||||
|
||||
private String clientIp; //客户端ip
|
||||
|
||||
private Channel channel; //与客户端建立的通道
|
||||
|
||||
private String shortId; //通道的唯一标示
|
||||
|
||||
|
||||
public ClientChanel(String clientIp, Channel channel, String shortId) {
|
||||
this.clientIp = clientIp;
|
||||
this.channel = channel;
|
||||
this.shortId = shortId;
|
||||
}
|
||||
|
||||
public String getClientIp() {
|
||||
return clientIp;
|
||||
}
|
||||
|
||||
public void setClientIp(String clientIp) {
|
||||
this.clientIp = clientIp;
|
||||
}
|
||||
|
||||
public Channel getChannel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
public void setChannel(Channel channel) {
|
||||
this.channel = channel;
|
||||
}
|
||||
|
||||
public String getShortId() {
|
||||
return shortId;
|
||||
}
|
||||
|
||||
public void setShortId(String shortId) {
|
||||
this.shortId = shortId;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user