├── .gitignore ├── .idea ├── compiler.xml ├── gradle.xml ├── misc.xml ├── modules.xml └── runConfigurations.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── libs │ └── netty-all-4.1.23.Final.jar ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── ifreecomm │ │ └── nettydemo │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── ifreecomm │ │ │ └── nettydemo │ │ │ ├── Const.java │ │ │ ├── MainActivity.java │ │ │ ├── NettyClient.java │ │ │ ├── NettyClientHandler.java │ │ │ ├── NettyListener.java │ │ │ ├── adapter │ │ │ └── LogAdapter.java │ │ │ └── bean │ │ │ └── LogBean.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ └── log_item.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── ifreecomm │ └── nettydemo │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── nettyserver ├── .gitignore ├── build.gradle ├── libs │ └── netty-all-4.1.23.Final.jar ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── ifreecomm │ │ └── nettyserver │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── ifreecomm │ │ │ └── nettyserver │ │ │ ├── EchoServer.java │ │ │ ├── EchoServerHandler.java │ │ │ ├── MainActivity.java │ │ │ ├── NettyListener.java │ │ │ ├── adapter │ │ │ └── LogAdapter.java │ │ │ └── bean │ │ │ └── LogBean.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ └── log_item.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── ifreecomm │ └── nettyserver │ └── ExampleUnitTest.java ├── screenshot ├── clent.gif └── server.gif └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 26 | 27 | 28 | 29 | 30 | 31 | 33 | 34 | 35 | 36 | 37 | 1.8 38 | 39 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NettyDemo 2 | 3 | Netty是基于Java NIO client-server的网络应用框架,使用Netty可以快速开发网络应用 4 | 更多用法请请跳转到https://github.com/netty/netty 5 | 6 | 本项目基于Netty在Android平台所建项目,供大家参考学习 7 | 8 | ![image](https://github.com/cai784921129/NettyDemo/blob/master/screenshot/clent.gif?raw=true) 9 | ![image](https://github.com/cai784921129/NettyDemo/blob/master/screenshot/server.gif?raw=true) 10 | 11 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 26 5 | defaultConfig { 6 | applicationId "ifreecomm.nettydemo" 7 | minSdkVersion 18 8 | targetSdkVersion 26 9 | versionCode 1 10 | versionName "1.0" 11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | implementation fileTree(include: ['*.jar'], dir: 'libs') 23 | implementation 'com.android.support:appcompat-v7:26.1.0' 24 | compile 'com.android.support:design:26.1.0' 25 | implementation 'com.android.support.constraint:constraint-layout:1.0.2' 26 | testImplementation 'junit:junit:4.12' 27 | androidTestImplementation 'com.android.support.test:runner:1.0.1' 28 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' 29 | implementation files('libs/netty-all-4.1.23.Final.jar') 30 | } 31 | -------------------------------------------------------------------------------- /app/libs/netty-all-4.1.23.Final.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thisfeng/NettyDemo/b03a6216bca923d6c13ab157aa8833abf58f279c/app/libs/netty-all-4.1.23.Final.jar -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/ifreecomm/nettydemo/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package ifreecomm.nettydemo; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("ifreecomm.nettydemo", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 16 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/java/ifreecomm/nettydemo/Const.java: -------------------------------------------------------------------------------- 1 | package ifreecomm.nettydemo; 2 | 3 | 4 | 5 | public class Const { 6 | 7 | public static final String HOST = "172.16.8.233"; 8 | public static final int TCP_PORT = 1088; 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/java/ifreecomm/nettydemo/MainActivity.java: -------------------------------------------------------------------------------- 1 | package ifreecomm.nettydemo; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | import android.support.v7.widget.LinearLayoutManager; 6 | import android.support.v7.widget.RecyclerView; 7 | import android.text.TextUtils; 8 | import android.util.Log; 9 | import android.view.View; 10 | import android.widget.Button; 11 | import android.widget.EditText; 12 | import android.widget.Toast; 13 | 14 | import ifreecomm.nettydemo.adapter.LogAdapter; 15 | import ifreecomm.nettydemo.bean.LogBean; 16 | import io.netty.channel.ChannelFuture; 17 | import io.netty.channel.ChannelFutureListener; 18 | 19 | import static android.widget.Toast.LENGTH_SHORT; 20 | 21 | public class MainActivity extends AppCompatActivity implements View.OnClickListener, NettyListener { 22 | 23 | private static final String TAG = "MainActivity"; 24 | private Button mClearLog; 25 | private Button mSendBtn; 26 | private Button mConnect; 27 | private EditText mSendET; 28 | private RecyclerView mSendList; 29 | private RecyclerView mReceList; 30 | 31 | private LogAdapter mSendLogAdapter = new LogAdapter(); 32 | private LogAdapter mReceLogAdapter = new LogAdapter(); 33 | private NettyClient mNettyClient; 34 | 35 | @Override 36 | protected void onCreate(Bundle savedInstanceState) { 37 | super.onCreate(savedInstanceState); 38 | setContentView(R.layout.activity_main); 39 | findViews(); 40 | initData(); 41 | initlistener(); 42 | mNettyClient = new NettyClient(Const.HOST, Const.TCP_PORT); 43 | } 44 | 45 | private void initlistener() { 46 | 47 | 48 | } 49 | 50 | private void initData() { 51 | LinearLayoutManager manager1 = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false); 52 | mSendList.setLayoutManager(manager1); 53 | mSendList.setAdapter(mSendLogAdapter); 54 | 55 | LinearLayoutManager manager2 = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false); 56 | mReceList.setLayoutManager(manager2); 57 | mReceList.setAdapter(mReceLogAdapter); 58 | 59 | } 60 | 61 | private void findViews() { 62 | mSendList = findViewById(R.id.send_list); 63 | mReceList = findViewById(R.id.rece_list); 64 | mSendET = findViewById(R.id.send_et); 65 | mConnect = findViewById(R.id.connect); 66 | mSendBtn = findViewById(R.id.send_btn); 67 | mClearLog = findViewById(R.id.clear_log); 68 | 69 | mConnect.setOnClickListener(this); 70 | mSendBtn.setOnClickListener(this); 71 | mClearLog.setOnClickListener(this); 72 | } 73 | 74 | @Override 75 | public void onClick(View v) { 76 | switch (v.getId()) { 77 | 78 | case R.id.connect: 79 | connect(); 80 | break; 81 | 82 | case R.id.send_btn: 83 | if (!mNettyClient.getConnectStatus()) { 84 | Toast.makeText(getApplicationContext(), "未连接,请先连接", LENGTH_SHORT).show(); 85 | } else { 86 | final String msg = mSendET.getText().toString(); 87 | if (TextUtils.isEmpty(msg.trim())) { 88 | return; 89 | } 90 | mNettyClient.sendMsgToServer(msg, new ChannelFutureListener() { 91 | @Override 92 | public void operationComplete(ChannelFuture channelFuture) throws Exception { 93 | if (channelFuture.isSuccess()) { //4 94 | Log.d(TAG, "Write auth successful"); 95 | logSend(msg); 96 | } else { 97 | Log.d(TAG, "Write auth error"); 98 | } 99 | } 100 | }); 101 | mSendET.setText(""); 102 | } 103 | 104 | break; 105 | 106 | case R.id.clear_log: 107 | mReceLogAdapter.getDataList().clear(); 108 | mSendLogAdapter.getDataList().clear(); 109 | mReceLogAdapter.notifyDataSetChanged(); 110 | mSendLogAdapter.notifyDataSetChanged(); 111 | break; 112 | } 113 | } 114 | 115 | private void connect() { 116 | Log.d(TAG, "connect"); 117 | if (!mNettyClient.getConnectStatus()) { 118 | mNettyClient.setListener(MainActivity.this); 119 | mNettyClient.connect();//连接服务器 120 | } else { 121 | mNettyClient.disconnect(); 122 | } 123 | } 124 | 125 | @Override 126 | public void onMessageResponse(Object msg) { 127 | Log.e(TAG, "onMessageResponse:" + msg); 128 | logRece((String) msg); 129 | // byte[] bytes = byteBuf.array(); 130 | // 131 | // String hexFun3 = bytesToHexFun3(bytes, byteBuf.writerIndex()); 132 | // logRece(hexFun3); 133 | } 134 | 135 | @Override 136 | public void onServiceStatusConnectChanged(final int statusCode) { 137 | runOnUiThread(new Runnable() { 138 | @Override 139 | public void run() { 140 | if (statusCode == NettyListener.STATUS_CONNECT_SUCCESS) { 141 | Log.e(TAG, "STATUS_CONNECT_SUCCESS:"); 142 | mConnect.setText("DisConnect"); 143 | } else { 144 | Log.e(TAG, "onServiceStatusConnectChanged:" + statusCode); 145 | mConnect.setText("Connect"); 146 | } 147 | } 148 | }); 149 | 150 | } 151 | 152 | private void logSend(String log) { 153 | LogBean logBean = new LogBean(System.currentTimeMillis(), log); 154 | mSendLogAdapter.getDataList().add(0, logBean); 155 | runOnUiThread(new Runnable() { 156 | @Override 157 | public void run() { 158 | mSendLogAdapter.notifyDataSetChanged(); 159 | } 160 | }); 161 | 162 | } 163 | 164 | private void logRece(String log) { 165 | LogBean logBean = new LogBean(System.currentTimeMillis(), log); 166 | mReceLogAdapter.getDataList().add(0, logBean); 167 | runOnUiThread(new Runnable() { 168 | @Override 169 | public void run() { 170 | mReceLogAdapter.notifyDataSetChanged(); 171 | } 172 | }); 173 | 174 | } 175 | 176 | /** 177 | * 方法三: 178 | * byte[] to hex string 179 | * 180 | * @param bytes 181 | * @return 182 | */ 183 | public static String bytesToHexFun3(byte[] bytes, int length) { 184 | StringBuilder buf = new StringBuilder(length * 2); 185 | for (int i = 0; i < length; i++) {// 使用String的format方法进行转换 186 | 187 | buf.append(String.format("%02x", new Integer(bytes[i] & 0xFF))); 188 | } 189 | return buf.toString(); 190 | } 191 | 192 | public void disconnect(View view) { 193 | mNettyClient.disconnect(); 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /app/src/main/java/ifreecomm/nettydemo/NettyClient.java: -------------------------------------------------------------------------------- 1 | package ifreecomm.nettydemo; 2 | 3 | import android.os.SystemClock; 4 | import android.util.Log; 5 | 6 | import java.util.concurrent.TimeUnit; 7 | 8 | import io.netty.bootstrap.Bootstrap; 9 | import io.netty.buffer.ByteBuf; 10 | import io.netty.buffer.Unpooled; 11 | import io.netty.channel.Channel; 12 | import io.netty.channel.ChannelFuture; 13 | import io.netty.channel.ChannelFutureListener; 14 | import io.netty.channel.ChannelInitializer; 15 | import io.netty.channel.ChannelOption; 16 | import io.netty.channel.EventLoopGroup; 17 | import io.netty.channel.nio.NioEventLoopGroup; 18 | import io.netty.channel.socket.SocketChannel; 19 | import io.netty.channel.socket.nio.NioSocketChannel; 20 | import io.netty.handler.codec.LineBasedFrameDecoder; 21 | import io.netty.handler.codec.string.StringDecoder; 22 | import io.netty.handler.codec.string.StringEncoder; 23 | import io.netty.handler.timeout.IdleStateHandler; 24 | import io.netty.util.CharsetUtil; 25 | 26 | 27 | public class NettyClient { 28 | private static final String TAG = "NettyClient"; 29 | 30 | private EventLoopGroup group; 31 | 32 | private NettyListener listener; 33 | 34 | private Channel channel; 35 | 36 | private boolean isConnect = false; 37 | 38 | private static int reconnectNum = Integer.MAX_VALUE; 39 | 40 | private boolean isNeedReconnect = true; 41 | private boolean isConnecting = false; 42 | 43 | private long reconnectIntervalTime = 5000; 44 | private static final Integer CONNECT_TIMEOUT_MILLIS = 5000; 45 | 46 | public String host; 47 | public int tcp_port; 48 | 49 | // private ScheduledExecutorService mScheduledExecutorService; 50 | 51 | public NettyClient(String host, int tcp_port) { 52 | this.host = host; 53 | this.tcp_port = tcp_port; 54 | } 55 | 56 | public void connect() { 57 | 58 | if (isConnecting) { 59 | return; 60 | } 61 | Thread clientThread = new Thread("client-Netty") { 62 | @Override 63 | public void run() { 64 | super.run(); 65 | isNeedReconnect = true; 66 | reconnectNum = Integer.MAX_VALUE; 67 | connectServer(); 68 | } 69 | }; 70 | clientThread.start(); 71 | } 72 | 73 | 74 | private void connectServer() { 75 | synchronized (NettyClient.this) { 76 | ChannelFuture channelFuture = null; 77 | if (!isConnect) { 78 | isConnecting = true; 79 | group = new NioEventLoopGroup(); 80 | Bootstrap bootstrap = new Bootstrap().group(group) 81 | .option(ChannelOption.TCP_NODELAY, true)//屏蔽Nagle算法试图 82 | .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000) 83 | .channel(NioSocketChannel.class) 84 | .handler(new ChannelInitializer() { // 5 85 | @Override 86 | public void initChannel(SocketChannel ch) throws Exception { 87 | ch.pipeline().addLast("ping", new IdleStateHandler(0, 5, 0, TimeUnit.SECONDS));//5s未发送数据,回调userEventTriggered 88 | ch.pipeline().addLast(new StringEncoder(CharsetUtil.UTF_8)); 89 | ch.pipeline().addLast(new LineBasedFrameDecoder(1024));//黏包处理 90 | ch.pipeline().addLast(new StringDecoder(CharsetUtil.UTF_8)); 91 | ch.pipeline().addLast(new NettyClientHandler(listener)); 92 | } 93 | }); 94 | 95 | try { 96 | channelFuture = bootstrap.connect(host, tcp_port).addListener(new ChannelFutureListener() { 97 | @Override 98 | public void operationComplete(ChannelFuture channelFuture) throws Exception { 99 | if (channelFuture.isSuccess()) { 100 | Log.e(TAG,"连接成功"); 101 | isConnect = true; 102 | channel = channelFuture.channel(); 103 | } else { 104 | Log.e(TAG,"连接失败"); 105 | isConnect = false; 106 | } 107 | isConnecting = false; 108 | } 109 | }).sync(); 110 | 111 | // Wait until the connection is closed. 112 | channelFuture.channel().closeFuture().sync(); 113 | Log.e(TAG, " 断开连接"); 114 | } catch (Exception e) { 115 | e.printStackTrace(); 116 | } finally { 117 | isConnect = false; 118 | listener.onServiceStatusConnectChanged(NettyListener.STATUS_CONNECT_CLOSED); 119 | if (null != channelFuture) { 120 | if (channelFuture.channel() != null && channelFuture.channel().isOpen()) { 121 | channelFuture.channel().close(); 122 | } 123 | } 124 | group.shutdownGracefully(); 125 | reconnect(); 126 | } 127 | } 128 | } 129 | } 130 | 131 | 132 | public void disconnect() { 133 | Log.e(TAG, "disconnect"); 134 | isNeedReconnect = false; 135 | group.shutdownGracefully(); 136 | } 137 | 138 | public void reconnect() { 139 | Log.e(TAG, "reconnect"); 140 | if (isNeedReconnect && reconnectNum > 0 && !isConnect) { 141 | reconnectNum--; 142 | SystemClock.sleep(reconnectIntervalTime); 143 | if (isNeedReconnect && reconnectNum > 0 && !isConnect) { 144 | Log.e(TAG, "重新连接"); 145 | connectServer(); 146 | } 147 | } 148 | } 149 | 150 | public boolean sendMsgToServer(String data, ChannelFutureListener listener) { 151 | boolean flag = channel != null && isConnect; 152 | if (flag) { 153 | // ByteBuf buf = Unpooled.copiedBuffer(data); 154 | // ByteBuf byteBuf = Unpooled.copiedBuffer(data + System.getProperty("line.separator"), //2 155 | // CharsetUtil.UTF_8); 156 | channel.writeAndFlush(data + System.getProperty("line.separator")).addListener(listener); 157 | } 158 | return flag; 159 | } 160 | 161 | public boolean sendMsgToServer(byte[] data, ChannelFutureListener listener) { 162 | boolean flag = channel != null && isConnect; 163 | if (flag) { 164 | ByteBuf buf = Unpooled.copiedBuffer(data); 165 | channel.writeAndFlush(buf).addListener(listener); 166 | } 167 | return flag; 168 | } 169 | 170 | public void setReconnectNum(int reconnectNum) { 171 | this.reconnectNum = reconnectNum; 172 | } 173 | 174 | public void setReconnectIntervalTime(long reconnectIntervalTime) { 175 | this.reconnectIntervalTime = reconnectIntervalTime; 176 | } 177 | 178 | public boolean getConnectStatus() { 179 | return isConnect; 180 | } 181 | 182 | public boolean isConnecting() { 183 | return isConnecting; 184 | } 185 | 186 | public void setConnectStatus(boolean status) { 187 | this.isConnect = status; 188 | } 189 | 190 | public void setListener(NettyListener listener) { 191 | this.listener = listener; 192 | } 193 | 194 | } 195 | -------------------------------------------------------------------------------- /app/src/main/java/ifreecomm/nettydemo/NettyClientHandler.java: -------------------------------------------------------------------------------- 1 | package ifreecomm.nettydemo; 2 | 3 | import android.util.Log; 4 | 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.channel.SimpleChannelInboundHandler; 7 | import io.netty.handler.timeout.IdleState; 8 | import io.netty.handler.timeout.IdleStateEvent; 9 | 10 | 11 | public class NettyClientHandler extends SimpleChannelInboundHandler { 12 | 13 | private static final String TAG = "NettyClientHandler"; 14 | private NettyListener listener; 15 | 16 | // private static final ByteBuf HEARTBEAT_SEQUENCE = Unpooled.unreleasableBuffer(Unpooled.copiedBuffer("Heartbeat"+System.getProperty("line.separator"), 17 | // CharsetUtil.UTF_8)); 18 | // byte[] requestBody = {(byte) 0xFE, (byte) 0xED, (byte) 0xFE, 5,4, (byte) 0xFF,0x0a}; 19 | 20 | 21 | public NettyClientHandler(NettyListener listener) { 22 | this.listener = listener; 23 | } 24 | 25 | 26 | @Override 27 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { 28 | if (evt instanceof IdleStateEvent) { 29 | IdleStateEvent event = (IdleStateEvent) evt; 30 | if (event.state() == IdleState.WRITER_IDLE) { 31 | ctx.channel().writeAndFlush("Heartbeat"+System.getProperty("line.separator")); 32 | } 33 | } 34 | } 35 | 36 | /** 37 | * 连接成功 38 | * 39 | * @param ctx 40 | * @throws Exception 41 | */ 42 | @Override 43 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 44 | Log.e(TAG, "channelActive"); 45 | // NettyClient.getInstance().setConnectStatus(true); 46 | listener.onServiceStatusConnectChanged(NettyListener.STATUS_CONNECT_SUCCESS); 47 | } 48 | 49 | @Override 50 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 51 | Log.e(TAG, "channelInactive"); 52 | // NettyClient.getInstance().setConnectStatus(false); 53 | // listener.onServiceStatusConnectChanged(NettyListener.STATUS_CONNECT_CLOSED); 54 | // NettyClient.getInstance().reconnect(); 55 | } 56 | 57 | /** 58 | * 客户端收到消息 59 | * 60 | * @param channelHandlerContext 61 | * @param byteBuf 62 | * @throws Exception 63 | */ 64 | @Override 65 | protected void channelRead0(ChannelHandlerContext channelHandlerContext, String byteBuf) throws Exception { 66 | Log.e(TAG, "channelRead0"); 67 | listener.onMessageResponse(byteBuf); 68 | } 69 | 70 | @Override 71 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { 72 | // Close the connection when an exception is raised. 73 | // NettyClient.getInstance().setConnectStatus(false); 74 | Log.e(TAG, "exceptionCaught"); 75 | listener.onServiceStatusConnectChanged(NettyListener.STATUS_CONNECT_ERROR); 76 | cause.printStackTrace(); 77 | ctx.close(); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /app/src/main/java/ifreecomm/nettydemo/NettyListener.java: -------------------------------------------------------------------------------- 1 | package ifreecomm.nettydemo; 2 | 3 | 4 | 5 | public interface NettyListener { 6 | 7 | public final static byte STATUS_CONNECT_SUCCESS = 1; 8 | 9 | public final static byte STATUS_CONNECT_CLOSED = 0; 10 | 11 | public final static byte STATUS_CONNECT_ERROR = 0; 12 | 13 | 14 | /** 15 | * 当接收到系统消息 16 | */ 17 | void onMessageResponse(Object msg); 18 | 19 | /** 20 | * 当服务状态发生变化时触发 21 | */ 22 | public void onServiceStatusConnectChanged(int statusCode); 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/ifreecomm/nettydemo/adapter/LogAdapter.java: -------------------------------------------------------------------------------- 1 | package ifreecomm.nettydemo.adapter; 2 | 3 | import android.content.ClipData; 4 | import android.content.ClipboardManager; 5 | import android.content.Context; 6 | import android.support.v7.widget.RecyclerView; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.TextView; 11 | import android.widget.Toast; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | import ifreecomm.nettydemo.R; 17 | import ifreecomm.nettydemo.bean.LogBean; 18 | 19 | public class LogAdapter extends RecyclerView.Adapter { 20 | 21 | private List mDataList = new ArrayList<>(); 22 | 23 | @Override 24 | public ItemHolder onCreateViewHolder(ViewGroup parent, int viewType) { 25 | return new ItemHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.log_item, parent, false)); 26 | } 27 | 28 | @Override 29 | public void onBindViewHolder(final ItemHolder holder, int position) { 30 | LogBean bean = mDataList.get(position); 31 | 32 | holder.mTime.setText(bean.mTime); 33 | holder.mLog.setText(bean.mLog); 34 | 35 | holder.itemView.setOnLongClickListener(new View.OnLongClickListener() { 36 | @Override 37 | public boolean onLongClick(View v) { 38 | ClipboardManager cmb = (ClipboardManager) v.getContext().getSystemService(Context.CLIPBOARD_SERVICE); 39 | LogBean log = mDataList.get(holder.getAdapterPosition()); 40 | String msg = log.mTime + " " + log.mLog; 41 | cmb.setPrimaryClip(ClipData.newPlainText(null, msg)); 42 | Toast.makeText(v.getContext(), "已复制到剪贴板", Toast.LENGTH_LONG).show(); 43 | return true; 44 | } 45 | }); 46 | } 47 | 48 | @Override 49 | public int getItemCount() { 50 | return mDataList.size(); 51 | } 52 | 53 | public class ItemHolder extends RecyclerView.ViewHolder { 54 | public TextView mTime; 55 | public TextView mLog; 56 | 57 | public ItemHolder(View itemView) { 58 | super(itemView); 59 | mTime = (TextView) itemView.findViewById(R.id.time); 60 | mLog = (TextView) itemView.findViewById(R.id.logtext); 61 | } 62 | } 63 | 64 | public List getDataList() { 65 | return mDataList; 66 | } 67 | } -------------------------------------------------------------------------------- /app/src/main/java/ifreecomm/nettydemo/bean/LogBean.java: -------------------------------------------------------------------------------- 1 | package ifreecomm.nettydemo.bean; 2 | 3 | import java.text.SimpleDateFormat; 4 | 5 | public class LogBean { 6 | public String mTime; 7 | public String mLog; 8 | 9 | public LogBean(long time, String log) { 10 | SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss"); 11 | mTime = format.format(time); 12 | mLog = log; 13 | } 14 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 12 | 13 | 17 | 18 | 19 | 23 | 24 | 29 | 30 | 36 | 37 | 42 | 43 | 47 | 48 |