├── .gitignore ├── .idea ├── caches │ └── build_file_checksums.ser ├── codeStyles │ └── Project.xml ├── gradle.xml ├── misc.xml └── runConfigurations.xml ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── freddy │ │ └── chat │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── freddy │ │ │ └── chat │ │ │ ├── MainActivity.java │ │ │ ├── NettyChatApp.java │ │ │ ├── bean │ │ │ ├── AppMessage.java │ │ │ ├── BaseMessage.java │ │ │ ├── ContentMessage.java │ │ │ ├── Head.java │ │ │ └── SingleMessage.java │ │ │ ├── event │ │ │ ├── CEvenObjPool.java │ │ │ ├── CEvent.java │ │ │ ├── CEventCenter.java │ │ │ ├── Events.java │ │ │ ├── I_CEventListener.java │ │ │ ├── ObjectPool.java │ │ │ └── PoolableObject.java │ │ │ ├── im │ │ │ ├── IMSClientBootstrap.java │ │ │ ├── IMSConnectStatusListener.java │ │ │ ├── IMSEventListener.java │ │ │ ├── IMessageProcessor.java │ │ │ ├── MessageBuilder.java │ │ │ ├── MessageProcessor.java │ │ │ ├── MessageType.java │ │ │ └── handler │ │ │ │ ├── AbstractMessageHandler.java │ │ │ │ ├── GroupChatMessageHandler.java │ │ │ │ ├── IMessageHandler.java │ │ │ │ ├── MessageHandlerFactory.java │ │ │ │ ├── ServerReportMessageHandler.java │ │ │ │ └── SingleChatMessageHandler.java │ │ │ └── utils │ │ │ ├── CThreadPoolExecutor.java │ │ │ └── StringUtil.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ └── activity_main.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 │ └── com │ └── freddy │ └── chat │ └── ExampleUnitTest.java ├── build.gradle ├── config.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── im_lib ├── .gitignore ├── build.gradle ├── libs │ └── netty-tcp-4.1.33-1.0.jar ├── proguard-rules.pro ├── src │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── freddy │ │ │ └── im │ │ │ └── ExampleInstrumentedTest.java │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── freddy │ │ │ │ └── im │ │ │ │ ├── ExecutorServiceFactory.java │ │ │ │ ├── HeartbeatHandler.java │ │ │ │ ├── HeartbeatRespHandler.java │ │ │ │ ├── IMSClientFactory.java │ │ │ │ ├── IMSConfig.java │ │ │ │ ├── LoginAuthRespHandler.java │ │ │ │ ├── MsgDispatcher.java │ │ │ │ ├── MsgTimeoutTimer.java │ │ │ │ ├── MsgTimeoutTimerManager.java │ │ │ │ ├── NettyServerDemo.java │ │ │ │ ├── interf │ │ │ │ └── IMSClientInterface.java │ │ │ │ ├── listener │ │ │ │ ├── IMSConnectStatusCallback.java │ │ │ │ └── OnEventListener.java │ │ │ │ ├── netty │ │ │ │ ├── NettyTcpClient.java │ │ │ │ ├── TCPChannelInitializerHandler.java │ │ │ │ └── TCPReadHandler.java │ │ │ │ └── protobuf │ │ │ │ ├── MessageProtobuf.java │ │ │ │ └── msg.proto │ │ └── res │ │ │ └── values │ │ │ └── strings.xml │ └── test │ │ └── java │ │ └── com │ │ └── freddy │ │ └── im │ │ └── ExampleUnitTest.java └── 消息结构v1.0_FreddyChen.xmind └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/libraries 5 | /.idea/modules.xml 6 | /.idea/workspace.xml 7 | .DS_Store 8 | /build 9 | /captures 10 | .externalNativeBuild 11 | -------------------------------------------------------------------------------- /.idea/caches/build_file_checksums.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/52im/NettyChat/d246ae24c4c895c5fe0b535980910527089a03e5/.idea/caches/build_file_checksums.ser -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 36 | 37 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 97 | 107 | 108 | 109 | 110 | 111 | 112 | 114 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### 使用方式 2 | ``` 3 | 1. 依赖im_lib库,implementation project(':im_lib') 4 | 2. 自定义IMSEventListener,实现OnEventListener,重写对应的方法配置参数 5 | 3. 自定义IMSConnectStatusListener,实现IMSConnectStatusCallback,重现对应的方法监听ims连接状态 6 | 4. 调用IMSClientInterface.init(Vector serverUrlList, OnEventListener listener, IMSConnectStatusCallback callback)方法,把服务器地址列表、IMSEventListener、IMSConnectStatusListener三个参数传入即可。 7 | 5. 发送消息:调用IMSClientInterface.sendMsg(MessageProtobuf.Msg msg)即可发送 8 | 6. 接收消息:收到消息会回调IMSEventListener.dispatchMsg(MessageProtobuf.Msg msg)方法。 9 | ``` 10 | 11 | 注:由于jcenter账号一直申请不了,所以目前可以先通过下载源码方式进行依赖,后续会发布到jcenter上,以gradle方式进行依赖。 12 | 13 | ## 项目博客地址: 14 | [掘金](https://juejin.im/post/5c97ae12e51d45580b681b0b) 15 | [简书](https://www.jianshu.com/p/00ba0ac2fc96) 16 | [CSDN](https://blog.csdn.net/FreddyChen/article/details/89201785) 17 | 18 | ## 使用过程中,如果有任何疑问,请联系我。 19 | ## 如果该项目对你有用,麻烦star一下哈。。。 20 | ## QQ交流群:1015178804,目前是Android IM技术交流群,后续写的文章,也会用此群进行交流。 21 | ## 目前准备写的文章如下: 22 | ``` 23 | 1.《开源一个自用的Android IM库,基于Netty+TCP+Protobuf实现》 24 | 2.《开源一个自用的Android IM库,基于Netty+WebSocket+Protobuf实现》 25 | 3.《开源一个自用的Android IM库,基于Netty+UDP+Protobuf实现》 26 | 4.《开源一个自用的Android网络请求库,基于Rxjava+Retrofit实现》 27 | 5.《开源一个自用的Android线程池,基于ThreadPoolExecutor实现》 28 | 6.《开源一个自用的Android IM UI界面,包含文本、图片、语音、表情、红包等实现》 29 | 7.《开源一个自用的Android图片加载库,基于Glide实现》 30 | 8.《开源一个自用的Android视频压缩库,基于MediaCodec实现》 31 | 9.《开源一个自用的Android视频压缩库,基于ffmpeg实现》 32 | 10.《开源一个自用的Android事件分发中心库,基于对象池实现》 33 | ``` 34 | 以上文章没有先后顺序,想到哪就写到哪吧。 35 | 36 | # 再次感谢鸿洋大神和郭霖大神。虽然文章太长,无法投稿公众号,但这两天star量还是增加了不少,非常感谢鸿洋大神在wanandroid上的推荐。 37 | 38 | # License 39 | 40 | 41 | Copyright 2019, chenshichao 42 | 43 | Licensed under the Apache License, Version 2.0 (the "License"); 44 | you may not use this file except in compliance with the License. 45 | You may obtain a copy of the License at 46 | 47 | http://www.apache.org/licenses/LICENSE-2.0 48 | 49 | Unless required by applicable law or agreed to in writing, software 50 | distributed under the License is distributed on an "AS IS" BASIS, 51 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 52 | See the License for the specific language governing permissions and 53 | limitations under the License. 54 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 28 5 | defaultConfig { 6 | applicationId "com.freddy.chat" 7 | minSdkVersion 15 8 | targetSdkVersion 28 9 | versionCode 1 10 | versionName "1.0" 11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 12 | } 13 | 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | implementation fileTree(dir: 'libs', include: ['*.jar']) 24 | implementation 'com.android.support:appcompat-v7:28.0.0' 25 | implementation 'com.android.support.constraint:constraint-layout:1.1.3' 26 | testImplementation 'junit:junit:4.12' 27 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 28 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 29 | implementation rootProject.ext.dependencies.multidex 30 | implementation rootProject.ext.dependencies.fastjson 31 | 32 | implementation project(':im_lib') 33 | } 34 | -------------------------------------------------------------------------------- /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/com/freddy/chat/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.freddy.chat; 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() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.freddy.chat", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/freddy/chat/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.freddy.chat; 2 | 3 | import android.support.v7.app.AppCompatActivity; 4 | import android.os.Bundle; 5 | import android.view.View; 6 | import android.widget.Button; 7 | import android.widget.EditText; 8 | import android.widget.TextView; 9 | 10 | import com.freddy.chat.bean.SingleMessage; 11 | import com.freddy.chat.event.CEvent; 12 | import com.freddy.chat.event.CEventCenter; 13 | import com.freddy.chat.event.Events; 14 | import com.freddy.chat.event.I_CEventListener; 15 | import com.freddy.chat.im.IMSClientBootstrap; 16 | import com.freddy.chat.im.MessageProcessor; 17 | import com.freddy.chat.im.MessageType; 18 | import com.freddy.chat.utils.CThreadPoolExecutor; 19 | 20 | import java.util.UUID; 21 | 22 | public class MainActivity extends AppCompatActivity implements I_CEventListener { 23 | 24 | private EditText mEditText; 25 | private TextView mTextView; 26 | 27 | String userId = "100002"; 28 | String token = "token_" + userId; 29 | String hosts = "[{\"host\":\"192.168.0.102\", \"port\":8855}]"; 30 | 31 | private static final String[] EVENTS = { 32 | Events.CHAT_SINGLE_MESSAGE 33 | }; 34 | 35 | @Override 36 | protected void onCreate(Bundle savedInstanceState) { 37 | super.onCreate(savedInstanceState); 38 | setContentView(R.layout.activity_main); 39 | 40 | mEditText = findViewById(R.id.et_content); 41 | mTextView = findViewById(R.id.tv_msg); 42 | 43 | IMSClientBootstrap.getInstance().init(userId, token, hosts, 1); 44 | 45 | CEventCenter.registerEventListener(this, EVENTS); 46 | } 47 | 48 | public void sendMsg(View view) { 49 | SingleMessage message = new SingleMessage(); 50 | message.setMsgId(UUID.randomUUID().toString()); 51 | message.setMsgType(MessageType.SINGLE_CHAT.getMsgType()); 52 | message.setMsgContentType(MessageType.MessageContentType.TEXT.getMsgContentType()); 53 | message.setFromId(userId); 54 | message.setToId("100001"); 55 | message.setTimestamp(System.currentTimeMillis()); 56 | message.setContent(mEditText.getText().toString()); 57 | 58 | MessageProcessor.getInstance().sendMsg(message); 59 | 60 | } 61 | 62 | @Override 63 | protected void onDestroy() { 64 | super.onDestroy(); 65 | CEventCenter.unregisterEventListener(this, EVENTS); 66 | } 67 | 68 | @Override 69 | public void onCEvent(String topic, int msgCode, int resultCode, Object obj) { 70 | switch (topic) { 71 | case Events.CHAT_SINGLE_MESSAGE: { 72 | final SingleMessage message = (SingleMessage) obj; 73 | CThreadPoolExecutor.runOnMainThread(new Runnable() { 74 | 75 | @Override 76 | public void run() { 77 | mTextView.setText(message.getContent()); 78 | } 79 | }); 80 | break; 81 | } 82 | 83 | default: 84 | break; 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /app/src/main/java/com/freddy/chat/NettyChatApp.java: -------------------------------------------------------------------------------- 1 | package com.freddy.chat; 2 | 3 | import android.support.multidex.MultiDexApplication; 4 | 5 | /** 6 | *

@ProjectName: NettyChat

7 | *

@ClassName: NettyChatApp.java

8 | *

@PackageName: com.freddy.chat

9 | * 10 | *

@Description: 类描述

11 | *
12 | *

@author: FreddyChen

13 | *

@date: 2019/04/07 23:58

14 | *

@email: chenshichao@outlook.com

15 | */ 16 | public class NettyChatApp extends MultiDexApplication { 17 | 18 | private static NettyChatApp instance; 19 | 20 | public static NettyChatApp sharedInstance() { 21 | if (instance == null) { 22 | throw new IllegalStateException("app not init..."); 23 | } 24 | return instance; 25 | } 26 | 27 | @Override 28 | public void onCreate() { 29 | super.onCreate(); 30 | instance = this; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/com/freddy/chat/bean/AppMessage.java: -------------------------------------------------------------------------------- 1 | package com.freddy.chat.bean; 2 | 3 | /** 4 | *

@ProjectName: NettyChat

5 | *

@ClassName: AppMessage.java

6 | *

@PackageName: com.freddy.chat.bean

7 | * 8 | *

@Description: App消息,用于把protobuf消息转换成app可用的消息类型

9 | *
10 | *

@author: FreddyChen

11 | *

@date: 2019/04/10 00:01

12 | *

@email: chenshichao@outlook.com

13 | */ 14 | public class AppMessage { 15 | 16 | private Head head; // 消息头 17 | private String body;// 消息体 18 | 19 | public Head getHead() { 20 | return head; 21 | } 22 | 23 | public void setHead(Head head) { 24 | this.head = head; 25 | } 26 | 27 | public String getBody() { 28 | return body; 29 | } 30 | 31 | public void setBody(String body) { 32 | this.body = body; 33 | } 34 | 35 | @Override 36 | public String toString() { 37 | return "AppMessage{" + 38 | "head=" + head + 39 | ", body='" + body + '\'' + 40 | '}'; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/com/freddy/chat/bean/BaseMessage.java: -------------------------------------------------------------------------------- 1 | package com.freddy.chat.bean; 2 | 3 | import com.freddy.chat.utils.StringUtil; 4 | 5 | /** 6 | *

@ProjectName: NettyChat

7 | *

@ClassName: BaseMessage.java

8 | *

@PackageName: com.freddy.chat.bean

9 | * 10 | *

@Description: 消息基类

11 | *
12 | *

@author: FreddyChen

13 | *

@date: 2019/04/10 00:02

14 | *

@email: chenshichao@outlook.com

15 | */ 16 | public class BaseMessage { 17 | 18 | protected String msgId; // 消息id 19 | protected int msgType; // 消息类型 20 | protected int msgContentType; // 消息内容乐行 21 | protected String fromId; // 发送者id 22 | protected String toId; // 接收者id 23 | protected long timestamp; // 消息时间戳 24 | protected int statusReport; // 消息状态报告 25 | protected String extend; // 扩展字段,以key/value形式存放json 26 | protected String content; // 消息内容 27 | 28 | public String getMsgId() { 29 | return msgId; 30 | } 31 | 32 | public void setMsgId(String msgId) { 33 | this.msgId = msgId; 34 | } 35 | 36 | public int getMsgType() { 37 | return msgType; 38 | } 39 | 40 | public void setMsgType(int msgType) { 41 | this.msgType = msgType; 42 | } 43 | 44 | public int getMsgContentType() { 45 | return msgContentType; 46 | } 47 | 48 | public void setMsgContentType(int msgContentType) { 49 | this.msgContentType = msgContentType; 50 | } 51 | 52 | public String getFromId() { 53 | return fromId; 54 | } 55 | 56 | public void setFromId(String fromId) { 57 | this.fromId = fromId; 58 | } 59 | 60 | public String getToId() { 61 | return toId; 62 | } 63 | 64 | public void setToId(String toId) { 65 | this.toId = toId; 66 | } 67 | 68 | public long getTimestamp() { 69 | return timestamp; 70 | } 71 | 72 | public void setTimestamp(long timestamp) { 73 | this.timestamp = timestamp; 74 | } 75 | 76 | public int getStatusReport() { 77 | return statusReport; 78 | } 79 | 80 | public void setStatusReport(int statusReport) { 81 | this.statusReport = statusReport; 82 | } 83 | 84 | public String getExtend() { 85 | return extend; 86 | } 87 | 88 | public void setExtend(String extend) { 89 | this.extend = extend; 90 | } 91 | 92 | public String getContent() { 93 | return content; 94 | } 95 | 96 | public void setContent(String content) { 97 | this.content = content; 98 | } 99 | 100 | @Override 101 | public int hashCode() { 102 | try { 103 | return this.msgId.hashCode(); 104 | }catch (NullPointerException e) { 105 | e.printStackTrace(); 106 | } 107 | 108 | return 1; 109 | } 110 | 111 | @Override 112 | public boolean equals(Object obj) { 113 | if(obj == null || !(obj instanceof BaseMessage)) { 114 | return false; 115 | } 116 | 117 | return StringUtil.equals(this.msgId, ((BaseMessage) obj).getMsgId()); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /app/src/main/java/com/freddy/chat/bean/ContentMessage.java: -------------------------------------------------------------------------------- 1 | package com.freddy.chat.bean; 2 | 3 | import com.freddy.chat.utils.StringUtil; 4 | 5 | /** 6 | *

@ProjectName: NettyChat

7 | *

@ClassName: ContentMessage.java

8 | *

@PackageName: com.freddy.chat.bean

9 | * 10 | *

@Description: 内容消息,包含单聊消息及群聊消息

11 | *
12 | *

@author: FreddyChen

13 | *

@date: 2019/04/10 00:06

14 | *

@email: chenshichao@outlook.com

15 | */ 16 | public class ContentMessage extends BaseMessage { 17 | 18 | protected boolean isRead; 19 | protected boolean isPlaying; 20 | protected boolean isLoading; 21 | 22 | public ContentMessage() { 23 | } 24 | 25 | public ContentMessage(String msgId, int msgType, int msgContentType, String fromId, String toId, 26 | long timestamp, int statusReport, String extend, String content) { 27 | this.msgId = msgId; 28 | this.msgType = msgType; 29 | this.msgContentType = msgContentType; 30 | this.fromId = fromId; 31 | this.toId = toId; 32 | this.timestamp = timestamp; 33 | this.statusReport = statusReport; 34 | this.extend = extend; 35 | this.content = content; 36 | } 37 | 38 | public boolean isRead() { 39 | return isRead; 40 | } 41 | 42 | public void setRead(boolean read) { 43 | isRead = read; 44 | } 45 | 46 | public boolean isPlaying() { 47 | return isPlaying; 48 | } 49 | 50 | public void setPlaying(boolean playing) { 51 | isPlaying = playing; 52 | } 53 | 54 | public boolean isLoading() { 55 | return isLoading; 56 | } 57 | 58 | public void setLoading(boolean loading) { 59 | isLoading = loading; 60 | } 61 | 62 | @Override 63 | public boolean equals(Object obj) { 64 | if (obj == null || !(obj instanceof ContentMessage)) { 65 | return false; 66 | } 67 | 68 | return StringUtil.equals(this.msgId, ((ContentMessage) obj).getMsgId()); 69 | } 70 | 71 | @Override 72 | public int hashCode() { 73 | try { 74 | return this.msgId.hashCode(); 75 | }catch (NullPointerException e) { 76 | e.printStackTrace(); 77 | } 78 | 79 | return 1; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /app/src/main/java/com/freddy/chat/bean/Head.java: -------------------------------------------------------------------------------- 1 | package com.freddy.chat.bean; 2 | 3 | /** 4 | *

@ProjectName: NettyChat

5 | *

@ClassName: Head.java

6 | *

@PackageName: com.freddy.chat.bean

7 | * 8 | *

@Description: 消息头

9 | *
10 | *

@author: FreddyChen

11 | *

@date: 2019/04/10 00:00

12 | *

@email: chenshichao@outlook.com

13 | */ 14 | public class Head { 15 | 16 | private String msgId; 17 | private int msgType; 18 | private int msgContentType; 19 | private String fromId; 20 | private String toId; 21 | private long timestamp; 22 | private int statusReport; 23 | private String extend; 24 | 25 | public String getMsgId() { 26 | return msgId; 27 | } 28 | 29 | public void setMsgId(String msgId) { 30 | this.msgId = msgId; 31 | } 32 | 33 | public int getMsgType() { 34 | return msgType; 35 | } 36 | 37 | public void setMsgType(int msgType) { 38 | this.msgType = msgType; 39 | } 40 | 41 | public int getMsgContentType() { 42 | return msgContentType; 43 | } 44 | 45 | public void setMsgContentType(int msgContentType) { 46 | this.msgContentType = msgContentType; 47 | } 48 | 49 | public String getFromId() { 50 | return fromId; 51 | } 52 | 53 | public void setFromId(String fromId) { 54 | this.fromId = fromId; 55 | } 56 | 57 | public String getToId() { 58 | return toId; 59 | } 60 | 61 | public void setToId(String toId) { 62 | this.toId = toId; 63 | } 64 | 65 | public long getTimestamp() { 66 | return timestamp; 67 | } 68 | 69 | public void setTimestamp(long timestamp) { 70 | this.timestamp = timestamp; 71 | } 72 | 73 | public int getStatusReport() { 74 | return statusReport; 75 | } 76 | 77 | public void setStatusReport(int statusReport) { 78 | this.statusReport = statusReport; 79 | } 80 | 81 | public String getExtend() { 82 | return extend; 83 | } 84 | 85 | public void setExtend(String extend) { 86 | this.extend = extend; 87 | } 88 | 89 | @Override 90 | public String toString() { 91 | return "Head{" + 92 | "msgId='" + msgId + '\'' + 93 | ", msgType=" + msgType + 94 | ", msgContentType=" + msgContentType + 95 | ", fromId='" + fromId + '\'' + 96 | ", toId='" + toId + '\'' + 97 | ", timestamp=" + timestamp + 98 | ", statusReport=" + statusReport + 99 | ", extend='" + extend + '\'' + 100 | '}'; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /app/src/main/java/com/freddy/chat/bean/SingleMessage.java: -------------------------------------------------------------------------------- 1 | package com.freddy.chat.bean; 2 | 3 | import com.freddy.chat.utils.StringUtil; 4 | 5 | /** 6 | *

@ProjectName: NettyChat

7 | *

@ClassName: SingleMessage.java

8 | *

@PackageName: com.freddy.chat.bean

9 | * 10 | *

@Description: 单聊消息

11 | *
12 | *

@author: FreddyChen

13 | *

@date: 2019/04/10 03:24

14 | *

@email: chenshichao@outlook.com

15 | */ 16 | public class SingleMessage extends ContentMessage implements Cloneable { 17 | 18 | @Override 19 | public int hashCode() { 20 | try { 21 | return this.msgId.hashCode(); 22 | } catch (Exception ex) { 23 | ex.printStackTrace(); 24 | } 25 | 26 | return 1; 27 | } 28 | 29 | @Override 30 | public boolean equals(Object obj) { 31 | if (obj == null) { 32 | return false; 33 | } 34 | 35 | if (!(obj instanceof SingleMessage)) { 36 | return false; 37 | } 38 | 39 | return StringUtil.equals(this.msgId, ((SingleMessage) obj).getMsgId()); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/com/freddy/chat/event/CEvenObjPool.java: -------------------------------------------------------------------------------- 1 | package com.freddy.chat.event; 2 | 3 | /** 4 | * 事件对象池 5 | * 6 | * Created by Freddy on 2015/11/3. 7 | * chenshichao@outlook.com 8 | */ 9 | public class CEvenObjPool extends ObjectPool { 10 | 11 | public CEvenObjPool(int capacity) { 12 | super(capacity); 13 | } 14 | 15 | @Override 16 | protected CEvent[] createObjPool(int capacity) { 17 | return new CEvent[capacity]; 18 | } 19 | 20 | @Override 21 | protected CEvent createNewObj() { 22 | return new CEvent(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/freddy/chat/event/CEvent.java: -------------------------------------------------------------------------------- 1 | package com.freddy.chat.event; 2 | 3 | /** 4 | * 事件模型 5 | * 6 | * Created by Freddy on 2015/11/3. 7 | * chenshichao@outlook.com 8 | */ 9 | public class CEvent implements PoolableObject { 10 | 11 | /** 12 | * 主题 13 | */ 14 | public String topic; 15 | 16 | /** 17 | * 消息类型 18 | */ 19 | public int msgCode; 20 | 21 | /** 22 | * 预留参数 23 | */ 24 | public int resultCode; 25 | 26 | /** 27 | * 回调返回数据 28 | */ 29 | public Object obj; 30 | 31 | public CEvent() {} 32 | 33 | public CEvent(String topic, int msgCode, int resultCode, Object obj) { 34 | this.topic = topic; 35 | this.msgCode = msgCode; 36 | this.resultCode = resultCode; 37 | this.obj = obj; 38 | } 39 | 40 | @Override 41 | public void reset() { 42 | topic = null; 43 | msgCode = 0; 44 | resultCode = 0; 45 | obj = null; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/com/freddy/chat/event/CEventCenter.java: -------------------------------------------------------------------------------- 1 | package com.freddy.chat.event; 2 | 3 | import android.text.TextUtils; 4 | import android.util.Log; 5 | 6 | import java.util.HashMap; 7 | import java.util.LinkedList; 8 | import java.util.List; 9 | 10 | /** 11 | * 事件中心服务(实现类似系统广播的功能) 12 | * 13 | * Created by Freddy on 2015/11/3. 14 | * chenshichao@outlook.com 15 | */ 16 | public class CEventCenter { 17 | 18 | /** 19 | * 事件中心服务名称 20 | */ 21 | public static final String SYMBOLIC_NAME = "EventCenter"; 22 | 23 | /** 24 | * 监听器列表,支持一对多存储 25 | */ 26 | private static final HashMap mListenerMap = new HashMap(); 27 | 28 | /** 29 | * 监听器列表锁 30 | */ 31 | private static final Object mListenerLock = new Object(); 32 | 33 | /** 34 | * 事件对象池 35 | */ 36 | private static final CEvenObjPool mPool = new CEvenObjPool(5); 37 | 38 | /** 39 | * 注册/注销监听器 40 | * 41 | * @param toBind 42 | * @param listener 监听器 43 | * @param topics 主题(一个服务可以发布多个主题事件) 44 | */ 45 | public static void onBindEvent(boolean toBind, I_CEventListener listener, String[] topics) { 46 | if(toBind) { 47 | registerEventListener(listener, topics); 48 | }else { 49 | unregisterEventListener(listener, topics); 50 | } 51 | } 52 | 53 | /** 54 | * 注册监听器 55 | * @param listener 56 | * 监听器 57 | * @param topic 58 | * 主题 59 | */ 60 | public static void registerEventListener(I_CEventListener listener, String topic) { 61 | registerEventListener(listener, new String[]{ topic }); 62 | } 63 | 64 | /** 65 | * 注册监听器 66 | * @param listener 67 | * 监听器 68 | * @param topics 69 | * 主题(一个服务可以发布多个主题事件) 70 | */ 71 | public static void registerEventListener(I_CEventListener listener, String[] topics) { 72 | if(null == listener || null == topics) { 73 | return; 74 | } 75 | 76 | synchronized (mListenerLock) { 77 | for(String topic : topics) { 78 | if(TextUtils.isEmpty(topic)) { 79 | continue; 80 | } 81 | Object obj = mListenerMap.get(topic); 82 | if(null == obj) { 83 | // 还没有监听器,直接放到Map集合 84 | mListenerMap.put(topic, listener); 85 | }else if(obj instanceof I_CEventListener) { 86 | // 有一个监听器 87 | I_CEventListener oldListener = (I_CEventListener) obj; 88 | if(listener == oldListener) { 89 | // 去重 90 | continue; 91 | } 92 | LinkedList list = new LinkedList(); 93 | list.add(oldListener); 94 | list.add(listener); 95 | mListenerMap.put(topic, list); 96 | }else if(obj instanceof List) { 97 | // 有多个监听器 98 | LinkedList listenerList = (LinkedList) obj; 99 | if(listenerList.indexOf(listener) >= 0) { 100 | // 去重 101 | continue; 102 | } 103 | listenerList.add(listener); 104 | } 105 | } 106 | } 107 | } 108 | 109 | /** 110 | * 注销监听器 111 | * @param listener 112 | * 监听器 113 | * @param topic 114 | * 注销对该主题的监听 115 | */ 116 | public static void unregisterEventListener(I_CEventListener listener, String topic) { 117 | unregisterEventListener(listener, new String[]{topic}); 118 | } 119 | 120 | /** 121 | * 注销监听器 122 | * @param listener 123 | * 监听器 124 | * @param topics 125 | * 注销对该主题(一个服务可以发布多个主题事件)的监听 126 | */ 127 | public static void unregisterEventListener(I_CEventListener listener, String[] topics) { 128 | if(null == listener || null == topics) { 129 | return; 130 | } 131 | synchronized (mListenerLock) { 132 | for(String topic : topics) { 133 | if(TextUtils.isEmpty(topic)) { 134 | continue; 135 | } 136 | Object obj = mListenerMap.get(topic); 137 | if(null == obj) { 138 | continue; 139 | }else if(obj instanceof I_CEventListener) { 140 | // 有一个监听器 141 | if(obj == listener) { 142 | mListenerMap.remove(topic); 143 | } 144 | }else if(obj instanceof List) { 145 | // 有多个监听器 146 | LinkedList list = (LinkedList) obj; 147 | list.remove(listener); 148 | } 149 | } 150 | } 151 | } 152 | 153 | /** 154 | * 同步分发事件 155 | * 156 | * @param topic 157 | * 主题 158 | * @param msgCode 159 | * 消息类型 160 | * @param resultCode 161 | * 预留参数 162 | * @param obj 163 | * 回调返回数据 164 | * 165 | */ 166 | public static void dispatchEvent(String topic, int msgCode, int resultCode, Object obj) { 167 | if(!TextUtils.isEmpty(topic)) { 168 | CEvent event = mPool.get(); 169 | event.topic = topic; 170 | event.msgCode = msgCode; 171 | event.resultCode = resultCode; 172 | event.obj = obj; 173 | dispatchEvent(event); 174 | } 175 | } 176 | 177 | public static void dispatchEvent(CEvent event) { 178 | if(mListenerMap.size() == 0) {// 没有监听器,直接跳出代码,无需执行以下代码 179 | return; 180 | } 181 | 182 | if(null != event && !TextUtils.isEmpty(event.topic)) { 183 | String topic = event.topic; 184 | // 通知事件监听器处理事件 185 | I_CEventListener listener = null; 186 | LinkedList listenerList = null; 187 | 188 | synchronized (mListenerLock) { 189 | Log.d(SYMBOLIC_NAME, "dispatchEvent | topic = " + topic + " msgCode = " + event.msgCode); 190 | Object obj = mListenerMap.get(topic); 191 | if(null != obj) { 192 | if(obj instanceof I_CEventListener) { 193 | listener = (I_CEventListener) obj; 194 | }else if(obj instanceof List) { 195 | listenerList = (LinkedList) ((LinkedList) obj).clone(); 196 | } 197 | } 198 | } 199 | 200 | if(null != listener) { 201 | listener.onCEvent(topic, event.msgCode, event.resultCode, event.obj); 202 | }else if(null != listenerList) { 203 | for(I_CEventListener l : listenerList) { 204 | l.onCEvent(topic, event.msgCode, event.resultCode, event.obj); 205 | } 206 | } 207 | 208 | mPool.returnObj(event); 209 | } 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /app/src/main/java/com/freddy/chat/event/Events.java: -------------------------------------------------------------------------------- 1 | package com.freddy.chat.event; 2 | 3 | /** 4 | * Created by Freddy on 2015/11/4. 5 | * chenshichao@outlook.com 6 | */ 7 | public class Events { 8 | 9 | public static final String CHAT_SINGLE_MESSAGE = "chat_single_message"; 10 | public static final String CHAT_GROUP_MESSAGE = "chat_group_message"; 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/freddy/chat/event/I_CEventListener.java: -------------------------------------------------------------------------------- 1 | package com.freddy.chat.event; 2 | 3 | /** 4 | * 事件监听器 5 | * 6 | * Created by Freddy on 2015/11/3. 7 | * chenshichao@outlook.com 8 | */ 9 | public interface I_CEventListener { 10 | 11 | /** 12 | * 事件回调函数
13 | * 注意:
14 | * 如果 obj 使用了对象池(如 socket 事件的对象),
15 | * 那么事件完成后,obj 即自动回收到对象池,请不要在其它线程继续使用,否则可能会导致数据不正常 16 | * 17 | * @param topic 18 | * 事件名称 19 | * @param msgCode 20 | * 消息类型 21 | * @param resultCode 22 | * 预留参数 23 | * @param obj 24 | * 数据对象 25 | */ 26 | void onCEvent(String topic, int msgCode, int resultCode, Object obj); 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/freddy/chat/event/ObjectPool.java: -------------------------------------------------------------------------------- 1 | package com.freddy.chat.event; 2 | 3 | /** 4 | * 对象池 5 | * 6 | * Created by Freddy on 2015/11/3. 7 | * chenshichao@outlook.com 8 | */ 9 | public abstract class ObjectPool { 10 | /** 11 | * 对象容器 12 | */ 13 | private T[] mContainer; 14 | 15 | private final Object mLock = new Object(); 16 | 17 | /** 18 | * 每次返回对象都放到数据末端,mLength表示前面可用对象数 19 | */ 20 | private int mLength; 21 | 22 | public ObjectPool(int capacity) { 23 | mContainer = createObjPool(capacity); 24 | } 25 | 26 | /** 27 | * 创建对象池 28 | * @param capacity 29 | * 最大限度容量 30 | * @return 31 | * 对象池 32 | */ 33 | protected abstract T[] createObjPool(int capacity); 34 | 35 | /** 36 | * 创建一个新的对象 37 | * @return 38 | * 创建成功的对象 39 | */ 40 | protected abstract T createNewObj(); 41 | 42 | /** 43 | * 从对象池中捞出一个对象,如果池已满,会重新创建一个对象 44 | * @return 45 | * 捞出或重新创建的对象 46 | */ 47 | public final T get() { 48 | T obj = findFreeObject(); 49 | if(null == obj) { 50 | obj = createNewObj(); 51 | }else { 52 | // 清除对象状态 53 | obj.reset(); 54 | } 55 | 56 | return obj; 57 | } 58 | 59 | /** 60 | * 把对象放回池里面 61 | * @param obj 62 | * 需要放回对象池的对象 63 | */ 64 | public final void returnObj(T obj) { 65 | synchronized (mLock) { 66 | int size = mContainer.length; 67 | if(mLength < size) { 68 | mContainer[mLength] = obj; 69 | mLength++; 70 | } 71 | } 72 | } 73 | 74 | /** 75 | * 从池中找到空闲的对象 76 | * @return 77 | * 空闲的对象 78 | */ 79 | private T findFreeObject() { 80 | T obj = null; 81 | synchronized (mLock) { 82 | if(mLength > 0) { 83 | --mLength; 84 | obj = mContainer[mLength]; 85 | // 赋值完成后,释放资源 86 | mContainer[mLength] = null; 87 | } 88 | } 89 | return obj; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /app/src/main/java/com/freddy/chat/event/PoolableObject.java: -------------------------------------------------------------------------------- 1 | package com.freddy.chat.event; 2 | 3 | /** 4 | * 对象池中的对象要求实现PoolableObject接口 5 | * 6 | * Created by Freddy on 2015/11/3. 7 | * chenshichao@outlook.com 8 | */ 9 | public interface PoolableObject { 10 | 11 | /** 12 | * 恢复到默认状态 13 | */ 14 | void reset(); 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/freddy/chat/im/IMSClientBootstrap.java: -------------------------------------------------------------------------------- 1 | package com.freddy.chat.im; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.alibaba.fastjson.JSONArray; 5 | import com.alibaba.fastjson.JSONObject; 6 | import com.freddy.im.IMSClientFactory; 7 | import com.freddy.im.interf.IMSClientInterface; 8 | import com.freddy.im.protobuf.MessageProtobuf; 9 | 10 | import java.util.Vector; 11 | 12 | /** 13 | *

@ProjectName: NettyChat

14 | *

@ClassName: IMSClientBootstrap.java

15 | *

@PackageName: com.freddy.chat.im

16 | * 17 | *

@Description: 应用层的imsClient启动器

18 | *
19 | *

@author: FreddyChen

20 | *

@date: 2019/04/08 00:25

21 | *

@email: chenshichao@outlook.com

22 | */ 23 | public class IMSClientBootstrap { 24 | 25 | private static final IMSClientBootstrap INSTANCE = new IMSClientBootstrap(); 26 | private IMSClientInterface imsClient; 27 | private boolean isActive; 28 | 29 | private IMSClientBootstrap() { 30 | 31 | } 32 | 33 | public static IMSClientBootstrap getInstance() { 34 | return INSTANCE; 35 | } 36 | 37 | public static void main(String[] args) { 38 | String userId = "100001"; 39 | String token = "token_" + userId; 40 | IMSClientBootstrap bootstrap = IMSClientBootstrap.getInstance(); 41 | String hosts = "[{\"host\":\"127.0.0.1\", \"port\":8866}]"; 42 | bootstrap.init(userId, token, hosts, 0); 43 | } 44 | 45 | public synchronized void init(String userId, String token, String hosts, int appStatus) { 46 | if (!isActive()) { 47 | Vector serverUrlList = convertHosts(hosts); 48 | if (serverUrlList == null || serverUrlList.size() == 0) { 49 | System.out.println("init IMLibClientBootstrap error,ims hosts is null"); 50 | return; 51 | } 52 | 53 | isActive = true; 54 | System.out.println("init IMLibClientBootstrap, servers=" + hosts); 55 | if (null != imsClient) { 56 | imsClient.close(); 57 | } 58 | imsClient = IMSClientFactory.getIMSClient(); 59 | updateAppStatus(appStatus); 60 | imsClient.init(serverUrlList, new IMSEventListener(userId, token), new IMSConnectStatusListener()); 61 | } 62 | } 63 | 64 | public boolean isActive() { 65 | return isActive; 66 | } 67 | 68 | /** 69 | * 发送消息 70 | * 71 | * @param msg 72 | */ 73 | public void sendMessage(MessageProtobuf.Msg msg) { 74 | if (isActive) { 75 | imsClient.sendMsg(msg); 76 | } 77 | } 78 | 79 | private Vector convertHosts(String hosts) { 80 | if (hosts != null && hosts.length() > 0) { 81 | JSONArray hostArray = JSONArray.parseArray(hosts); 82 | if (null != hostArray && hostArray.size() > 0) { 83 | Vector serverUrlList = new Vector(); 84 | JSONObject host; 85 | for (int i = 0; i < hostArray.size(); i++) { 86 | host = JSON.parseObject(hostArray.get(i).toString()); 87 | serverUrlList.add(host.getString("host") + " " 88 | + host.getInteger("port")); 89 | } 90 | return serverUrlList; 91 | } 92 | } 93 | return null; 94 | } 95 | 96 | public void updateAppStatus(int appStatus) { 97 | if (imsClient == null) { 98 | return; 99 | } 100 | 101 | imsClient.setAppStatus(appStatus); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /app/src/main/java/com/freddy/chat/im/IMSConnectStatusListener.java: -------------------------------------------------------------------------------- 1 | package com.freddy.chat.im; 2 | 3 | import com.freddy.im.listener.IMSConnectStatusCallback; 4 | 5 | /** 6 | *

@ProjectName: NettyChat

7 | *

@ClassName: IMSConnectStatusListener.java

8 | *

@PackageName: com.freddy.chat.im

9 | * 10 | *

@Description: 类描述

11 | *
12 | *

@author: FreddyChen

13 | *

@date: 2019/04/08 00:31

14 | *

@email: chenshichao@outlook.com

15 | */ 16 | public class IMSConnectStatusListener implements IMSConnectStatusCallback { 17 | 18 | @Override 19 | public void onConnecting() { 20 | } 21 | 22 | @Override 23 | public void onConnected() { 24 | } 25 | 26 | @Override 27 | public void onConnectFailed() { 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/freddy/chat/im/IMSEventListener.java: -------------------------------------------------------------------------------- 1 | package com.freddy.chat.im; 2 | 3 | import android.content.Context; 4 | import android.net.ConnectivityManager; 5 | import android.net.NetworkInfo; 6 | 7 | import com.alibaba.fastjson.JSONObject; 8 | import com.freddy.chat.NettyChatApp; 9 | import com.freddy.im.listener.OnEventListener; 10 | import com.freddy.im.protobuf.MessageProtobuf; 11 | 12 | import java.util.UUID; 13 | 14 | /** 15 | *

@ProjectName: NettyChat

16 | *

@ClassName: IMSEventListener.java

17 | *

@PackageName: com.freddy.chat.im

18 | * 19 | *

@Description: 与ims交互的listener

20 | *
21 | *

@author: FreddyChen

22 | *

@date: 2019/04/07 23:55

23 | *

@email: chenshichao@outlook.com

24 | */ 25 | public class IMSEventListener implements OnEventListener { 26 | 27 | private String userId; 28 | private String token; 29 | 30 | public IMSEventListener(String userId, String token) { 31 | this.userId = userId; 32 | this.token = token; 33 | } 34 | 35 | /** 36 | * 接收ims转发过来的消息 37 | * 38 | * @param msg 39 | */ 40 | @Override 41 | public void dispatchMsg(MessageProtobuf.Msg msg) { 42 | MessageProcessor.getInstance().receiveMsg(MessageBuilder.getMessageByProtobuf(msg)); 43 | } 44 | 45 | /** 46 | * 网络是否可用 47 | * 48 | * @return 49 | */ 50 | @Override 51 | public boolean isNetworkAvailable() { 52 | ConnectivityManager cm = (ConnectivityManager) NettyChatApp.sharedInstance().getSystemService(Context.CONNECTIVITY_SERVICE); 53 | NetworkInfo info = cm.getActiveNetworkInfo(); 54 | return info != null && info.isConnected(); 55 | } 56 | 57 | /** 58 | * 设置ims重连间隔时长,0表示默认使用ims的值 59 | * 60 | * @return 61 | */ 62 | @Override 63 | public int getReconnectInterval() { 64 | return 0; 65 | } 66 | 67 | /** 68 | * 设置ims连接超时时长,0表示默认使用ims的值 69 | * 70 | * @return 71 | */ 72 | @Override 73 | public int getConnectTimeout() { 74 | return 0; 75 | } 76 | 77 | /** 78 | * 设置应用在前台时ims心跳间隔时长,0表示默认使用ims的值 79 | * 80 | * @return 81 | */ 82 | @Override 83 | public int getForegroundHeartbeatInterval() { 84 | return 0; 85 | } 86 | 87 | /** 88 | * 设置应用在后台时ims心跳间隔时长,0表示默认使用ims的值 89 | * 90 | * @return 91 | */ 92 | @Override 93 | public int getBackgroundHeartbeatInterval() { 94 | return 0; 95 | } 96 | 97 | /** 98 | * 构建握手消息 99 | * 100 | * @return 101 | */ 102 | @Override 103 | public MessageProtobuf.Msg getHandshakeMsg() { 104 | MessageProtobuf.Msg.Builder builder = MessageProtobuf.Msg.newBuilder(); 105 | MessageProtobuf.Head.Builder headBuilder = MessageProtobuf.Head.newBuilder(); 106 | headBuilder.setMsgId(UUID.randomUUID().toString()); 107 | headBuilder.setMsgType(MessageType.HANDSHAKE.getMsgType()); 108 | headBuilder.setFromId(userId); 109 | headBuilder.setTimestamp(System.currentTimeMillis()); 110 | 111 | JSONObject jsonObj = new JSONObject(); 112 | jsonObj.put("token", token); 113 | headBuilder.setExtend(jsonObj.toString()); 114 | builder.setHead(headBuilder.build()); 115 | 116 | return builder.build(); 117 | } 118 | 119 | /** 120 | * 构建心跳消息 121 | * 122 | * @return 123 | */ 124 | @Override 125 | public MessageProtobuf.Msg getHeartbeatMsg() { 126 | MessageProtobuf.Msg.Builder builder = MessageProtobuf.Msg.newBuilder(); 127 | MessageProtobuf.Head.Builder headBuilder = MessageProtobuf.Head.newBuilder(); 128 | headBuilder.setMsgId(UUID.randomUUID().toString()); 129 | headBuilder.setMsgType(MessageType.HEARTBEAT.getMsgType()); 130 | headBuilder.setFromId(userId); 131 | headBuilder.setTimestamp(System.currentTimeMillis()); 132 | builder.setHead(headBuilder.build()); 133 | 134 | return builder.build(); 135 | } 136 | 137 | /** 138 | * 服务端返回的消息发送状态报告消息类型 139 | * 140 | * @return 141 | */ 142 | @Override 143 | public int getServerSentReportMsgType() { 144 | return MessageType.SERVER_MSG_SENT_STATUS_REPORT.getMsgType(); 145 | } 146 | 147 | /** 148 | * 客户端提交的消息接收状态报告消息类型 149 | * 150 | * @return 151 | */ 152 | @Override 153 | public int getClientReceivedReportMsgType() { 154 | return MessageType.CLIENT_MSG_RECEIVED_STATUS_REPORT.getMsgType(); 155 | } 156 | 157 | /** 158 | * 设置ims消息发送超时重发次数,0表示默认使用ims的值 159 | * 160 | * @return 161 | */ 162 | @Override 163 | public int getResendCount() { 164 | return 0; 165 | } 166 | 167 | /** 168 | * 设置ims消息发送超时重发间隔时长,0表示默认使用ims的值 169 | * 170 | * @return 171 | */ 172 | @Override 173 | public int getResendInterval() { 174 | return 0; 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /app/src/main/java/com/freddy/chat/im/IMessageProcessor.java: -------------------------------------------------------------------------------- 1 | package com.freddy.chat.im; 2 | 3 | import com.freddy.chat.bean.AppMessage; 4 | import com.freddy.chat.bean.BaseMessage; 5 | import com.freddy.chat.bean.ContentMessage; 6 | 7 | /** 8 | *

@ProjectName: NettyChat

9 | *

@ClassName: IMessageProcessor.java

10 | *

@PackageName: com.freddy.chat.im

11 | * 12 | *

@Description: 消息处理器接口

13 | *
14 | *

@author: FreddyChen

15 | *

@date: 2019/04/10 00:11

16 | *

@email: chenshichao@outlook.com

17 | */ 18 | public interface IMessageProcessor { 19 | 20 | void receiveMsg(AppMessage message); 21 | void sendMsg(AppMessage message); 22 | void sendMsg(ContentMessage message); 23 | void sendMsg(BaseMessage message); 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/freddy/chat/im/MessageBuilder.java: -------------------------------------------------------------------------------- 1 | package com.freddy.chat.im; 2 | 3 | import com.freddy.chat.bean.AppMessage; 4 | import com.freddy.chat.bean.BaseMessage; 5 | import com.freddy.chat.bean.ContentMessage; 6 | import com.freddy.chat.bean.Head; 7 | import com.freddy.chat.utils.StringUtil; 8 | import com.freddy.im.protobuf.MessageProtobuf; 9 | 10 | /** 11 | *

@ProjectName: BoChat

12 | *

@ClassName: MessageBuilder.java

13 | *

@PackageName: com.bochat.app.message

14 | * 15 | *

@Description: 消息转换

16 | *
17 | *

@author: FreddyChen

18 | *

@date: 2019/02/07 17:26

19 | *

@email: chenshichao@outlook.com

20 | */ 21 | public class MessageBuilder { 22 | 23 | /** 24 | * 根据聊天消息,生成一条可以能够传输通讯的消息 25 | * 26 | * @param msgId 27 | * @param type 28 | * @param subType 29 | * @param fromId 30 | * @param toId 31 | * @param extend 32 | * @param content 33 | * @return 34 | */ 35 | public static AppMessage buildAppMessage(String msgId, int type, int subType, String fromId, 36 | String toId, String extend, String content) { 37 | AppMessage message = new AppMessage(); 38 | Head head = new Head(); 39 | head.setMsgId(msgId); 40 | head.setMsgType(type); 41 | head.setMsgContentType(subType); 42 | head.setFromId(fromId); 43 | head.setToId(toId); 44 | head.setExtend(extend); 45 | message.setHead(head); 46 | message.setBody(content); 47 | 48 | return message; 49 | } 50 | 51 | /** 52 | * 根据聊天消息,生成一条可以能够传输通讯的消息 53 | * 54 | * @param msg 55 | * @return 56 | */ 57 | public static AppMessage buildAppMessage(ContentMessage msg) { 58 | AppMessage message = new AppMessage(); 59 | Head head = new Head(); 60 | head.setMsgId(msg.getMsgId()); 61 | head.setMsgType(msg.getMsgType()); 62 | head.setMsgContentType(msg.getMsgContentType()); 63 | head.setFromId(msg.getFromId()); 64 | head.setToId(msg.getToId()); 65 | head.setTimestamp(msg.getTimestamp()); 66 | head.setExtend(msg.getExtend()); 67 | message.setHead(head); 68 | message.setBody(msg.getContent()); 69 | 70 | return message; 71 | } 72 | 73 | /** 74 | * 根据聊天消息,生成一条可以能够传输通讯的消息 75 | * 76 | * @param msg 77 | * @return 78 | */ 79 | public static AppMessage buildAppMessage(BaseMessage msg) { 80 | AppMessage message = new AppMessage(); 81 | Head head = new Head(); 82 | head.setMsgId(msg.getMsgId()); 83 | head.setMsgType(msg.getMsgType()); 84 | head.setMsgContentType(msg.getMsgContentType()); 85 | head.setFromId(msg.getFromId()); 86 | head.setToId(msg.getToId()); 87 | head.setExtend(msg.getExtend()); 88 | head.setTimestamp(msg.getTimestamp()); 89 | message.setHead(head); 90 | message.setBody(msg.getContent()); 91 | 92 | return message; 93 | } 94 | 95 | /** 96 | * 根据业务消息对象获取protoBuf消息对应的builder 97 | * 98 | * @param message 99 | * @return 100 | */ 101 | public static MessageProtobuf.Msg.Builder getProtoBufMessageBuilderByAppMessage(AppMessage message) { 102 | MessageProtobuf.Msg.Builder builder = MessageProtobuf.Msg.newBuilder(); 103 | MessageProtobuf.Head.Builder headBuilder = MessageProtobuf.Head.newBuilder(); 104 | headBuilder.setMsgType(message.getHead().getMsgType()); 105 | headBuilder.setStatusReport(message.getHead().getStatusReport()); 106 | headBuilder.setMsgContentType(message.getHead().getMsgContentType()); 107 | if (!StringUtil.isEmpty(message.getHead().getMsgId())) 108 | headBuilder.setMsgId(message.getHead().getMsgId()); 109 | if (!StringUtil.isEmpty(message.getHead().getFromId())) 110 | headBuilder.setFromId(message.getHead().getFromId()); 111 | if (!StringUtil.isEmpty(message.getHead().getToId())) 112 | headBuilder.setToId(message.getHead().getToId()); 113 | if (message.getHead().getTimestamp() != 0) 114 | headBuilder.setTimestamp(message.getHead().getTimestamp()); 115 | if (!StringUtil.isEmpty(message.getHead().getExtend())) 116 | headBuilder.setExtend(message.getHead().getExtend()); 117 | if (!StringUtil.isEmpty(message.getBody())) 118 | builder.setBody(message.getBody()); 119 | builder.setHead(headBuilder); 120 | return builder; 121 | } 122 | 123 | /** 124 | * 通过protobuf消息对象获取业务消息对象 125 | * 126 | * @param protobufMessage 127 | * @return 128 | */ 129 | public static AppMessage getMessageByProtobuf( 130 | MessageProtobuf.Msg protobufMessage) { 131 | AppMessage message = new AppMessage(); 132 | Head head = new Head(); 133 | MessageProtobuf.Head protoHead = protobufMessage.getHead(); 134 | head.setMsgType(protoHead.getMsgType()); 135 | head.setStatusReport(protoHead.getStatusReport()); 136 | head.setMsgContentType(protoHead.getMsgContentType()); 137 | head.setMsgId(protoHead.getMsgId()); 138 | head.setFromId(protoHead.getFromId()); 139 | head.setToId(protoHead.getToId()); 140 | head.setTimestamp(protoHead.getTimestamp()); 141 | head.setExtend(protoHead.getExtend()); 142 | message.setHead(head); 143 | message.setBody(protobufMessage.getBody()); 144 | return message; 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /app/src/main/java/com/freddy/chat/im/MessageProcessor.java: -------------------------------------------------------------------------------- 1 | package com.freddy.chat.im; 2 | 3 | import android.util.Log; 4 | 5 | import com.freddy.chat.bean.AppMessage; 6 | import com.freddy.chat.bean.BaseMessage; 7 | import com.freddy.chat.bean.ContentMessage; 8 | import com.freddy.chat.im.handler.IMessageHandler; 9 | import com.freddy.chat.im.handler.MessageHandlerFactory; 10 | import com.freddy.chat.utils.CThreadPoolExecutor; 11 | 12 | /** 13 | *

@ProjectName: NettyChat

14 | *

@ClassName: MessageProcessor.java

15 | *

@PackageName: com.freddy.chat.im

16 | * 17 | *

@Description: 消息处理器

18 | *
19 | *

@author: FreddyChen

20 | *

@date: 2019/04/10 03:27

21 | *

@email: chenshichao@outlook.com

22 | */ 23 | public class MessageProcessor implements IMessageProcessor { 24 | 25 | private static final String TAG = MessageProcessor.class.getSimpleName(); 26 | 27 | private MessageProcessor() { 28 | 29 | } 30 | 31 | private static class MessageProcessorInstance { 32 | private static final IMessageProcessor INSTANCE = new MessageProcessor(); 33 | } 34 | 35 | public static IMessageProcessor getInstance() { 36 | return MessageProcessorInstance.INSTANCE; 37 | } 38 | 39 | /** 40 | * 接收消息 41 | * @param message 42 | */ 43 | @Override 44 | public void receiveMsg(final AppMessage message) { 45 | CThreadPoolExecutor.runInBackground(new Runnable() { 46 | 47 | @Override 48 | public void run() { 49 | try { 50 | IMessageHandler messageHandler = MessageHandlerFactory.getHandlerByMsgType(message.getHead().getMsgType()); 51 | if (messageHandler != null) { 52 | messageHandler.execute(message); 53 | } else { 54 | Log.e(TAG, "未找到消息处理handler,msgType=" + message.getHead().getMsgType()); 55 | } 56 | } catch (Exception e) { 57 | Log.e(TAG, "消息处理出错,reason=" + e.getMessage()); 58 | } 59 | } 60 | }); 61 | } 62 | 63 | /** 64 | * 发送消息 65 | * 66 | * @param message 67 | */ 68 | @Override 69 | public void sendMsg(final AppMessage message) { 70 | CThreadPoolExecutor.runInBackground(new Runnable() { 71 | 72 | @Override 73 | public void run() { 74 | boolean isActive = IMSClientBootstrap.getInstance().isActive(); 75 | if (isActive) { 76 | IMSClientBootstrap.getInstance().sendMessage(MessageBuilder.getProtoBufMessageBuilderByAppMessage(message).build()); 77 | } else { 78 | Log.e(TAG, "发送消息失败"); 79 | } 80 | } 81 | }); 82 | } 83 | 84 | /** 85 | * 发送消息 86 | * 87 | * @param message 88 | */ 89 | @Override 90 | public void sendMsg(ContentMessage message) { 91 | this.sendMsg(MessageBuilder.buildAppMessage(message)); 92 | } 93 | 94 | /** 95 | * 发送消息 96 | * 97 | * @param message 98 | */ 99 | @Override 100 | public void sendMsg(BaseMessage message) { 101 | this.sendMsg(MessageBuilder.buildAppMessage(message)); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /app/src/main/java/com/freddy/chat/im/MessageType.java: -------------------------------------------------------------------------------- 1 | package com.freddy.chat.im; 2 | 3 | /** 4 | *

@ProjectName: NettyChat

5 | *

@ClassName: MessageType.java

6 | *

@PackageName: com.freddy.chat.im

7 | * 8 | *

@Description: 消息类型

9 | *
10 | *

@author: FreddyChen

11 | *

@date: 2019/04/08 00:04

12 | *

@email: chenshichao@outlook.com

13 | */ 14 | public enum MessageType { 15 | 16 | /* 17 | * 握手消息 18 | */ 19 | HANDSHAKE(1001), 20 | 21 | /* 22 | * 心跳消息 23 | */ 24 | HEARTBEAT(1002), 25 | 26 | /* 27 | * 客户端提交的消息接收状态报告 28 | */ 29 | CLIENT_MSG_RECEIVED_STATUS_REPORT(1009), 30 | 31 | /* 32 | * 服务端返回的消息发送状态报告 33 | */ 34 | SERVER_MSG_SENT_STATUS_REPORT(1010), 35 | 36 | /** 37 | * 单聊消息 38 | */ 39 | SINGLE_CHAT(2001), 40 | 41 | /** 42 | * 群聊消息 43 | */ 44 | GROUP_CHAT(3001); 45 | 46 | private int msgType; 47 | 48 | MessageType(int msgType) { 49 | this.msgType = msgType; 50 | } 51 | 52 | public int getMsgType() { 53 | return this.msgType; 54 | } 55 | 56 | public enum MessageContentType { 57 | 58 | /** 59 | * 文本消息 60 | */ 61 | TEXT(101), 62 | 63 | /** 64 | * 图片消息 65 | */ 66 | IMAGE(102), 67 | 68 | /** 69 | * 语音消息 70 | */ 71 | VOICE(103); 72 | 73 | private int msgContentType; 74 | 75 | MessageContentType(int msgContentType) { 76 | this.msgContentType = msgContentType; 77 | } 78 | 79 | public int getMsgContentType() { 80 | return this.msgContentType; 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /app/src/main/java/com/freddy/chat/im/handler/AbstractMessageHandler.java: -------------------------------------------------------------------------------- 1 | package com.freddy.chat.im.handler; 2 | 3 | import com.freddy.chat.bean.AppMessage; 4 | 5 | /** 6 | *

@ProjectName: NettyChat

7 | *

@ClassName: AbstractMessageHandler.java

8 | *

@PackageName: com.freddy.chat.im.handler

9 | * 10 | *

@Description: 抽象的MessageHandler

11 | *
12 | *

@author: FreddyChen

13 | *

@date: 2019/04/10 03:41

14 | *

@email: chenshichao@outlook.com

15 | */ 16 | public abstract class AbstractMessageHandler implements IMessageHandler { 17 | 18 | @Override 19 | public void execute(AppMessage message) { 20 | action(message); 21 | } 22 | 23 | protected abstract void action(AppMessage message); 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/freddy/chat/im/handler/GroupChatMessageHandler.java: -------------------------------------------------------------------------------- 1 | package com.freddy.chat.im.handler; 2 | 3 | import android.util.Log; 4 | 5 | import com.freddy.chat.bean.AppMessage; 6 | 7 | /** 8 | *

@ProjectName: NettyChat

9 | *

@ClassName: GroupChatMessageHandler.java

10 | *

@PackageName: com.freddy.chat.im.handler

11 | * 12 | *

@Description: 类描述

13 | *
14 | *

@author: FreddyChen

15 | *

@date: 2019/04/10 03:43

16 | *

@email: chenshichao@outlook.com

17 | */ 18 | public class GroupChatMessageHandler extends AbstractMessageHandler { 19 | 20 | private static final String TAG = GroupChatMessageHandler.class.getSimpleName(); 21 | 22 | @Override 23 | protected void action(AppMessage message) { 24 | Log.d(TAG, "收到群聊消息,message=" + message); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/freddy/chat/im/handler/IMessageHandler.java: -------------------------------------------------------------------------------- 1 | package com.freddy.chat.im.handler; 2 | 3 | import com.freddy.chat.bean.AppMessage; 4 | 5 | /** 6 | *

@ProjectName: NettyChat

7 | *

@ClassName: IMessageHandler.java

8 | *

@PackageName: com.freddy.chat.im.handler

9 | * 10 | *

@Description: 类描述

11 | *
12 | *

@author: FreddyChen

13 | *

@date: 2019/04/10 03:41

14 | *

@email: chenshichao@outlook.com

15 | */ 16 | public interface IMessageHandler { 17 | 18 | void execute(AppMessage message); 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/freddy/chat/im/handler/MessageHandlerFactory.java: -------------------------------------------------------------------------------- 1 | package com.freddy.chat.im.handler; 2 | 3 | import android.util.SparseArray; 4 | 5 | import com.freddy.chat.im.MessageType; 6 | 7 | /** 8 | *

@ProjectName: NettyChat

9 | *

@ClassName: MessageHandlerFactory.java

10 | *

@PackageName: com.freddy.chat.im.handler

11 | * 12 | *

@Description: 消息处理handler工厂

13 | *
14 | *

@author: FreddyChen

15 | *

@date: 2019/04/10 03:44

16 | *

@email: chenshichao@outlook.com

17 | */ 18 | public class MessageHandlerFactory { 19 | 20 | private MessageHandlerFactory() { 21 | 22 | } 23 | 24 | private static final SparseArray HANDLERS = new SparseArray<>(); 25 | 26 | static { 27 | /** 单聊消息处理handler */ 28 | HANDLERS.put(MessageType.SINGLE_CHAT.getMsgType(), new SingleChatMessageHandler()); 29 | /** 群聊消息处理handler */ 30 | HANDLERS.put(MessageType.GROUP_CHAT.getMsgType(), new GroupChatMessageHandler()); 31 | /** 服务端返回的消息发送状态报告处理handler */ 32 | HANDLERS.put(MessageType.SERVER_MSG_SENT_STATUS_REPORT.getMsgType(), new ServerReportMessageHandler()); 33 | } 34 | 35 | /** 36 | * 根据消息类型获取对应的处理handler 37 | * 38 | * @param msgType 39 | * @return 40 | */ 41 | public static IMessageHandler getHandlerByMsgType(int msgType) { 42 | return HANDLERS.get(msgType); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/com/freddy/chat/im/handler/ServerReportMessageHandler.java: -------------------------------------------------------------------------------- 1 | package com.freddy.chat.im.handler; 2 | 3 | import android.util.Log; 4 | 5 | import com.freddy.chat.bean.AppMessage; 6 | 7 | /** 8 | *

@ProjectName: NettyChat

9 | *

@ClassName: ServerReportMessageHandler.java

10 | *

@PackageName: com.freddy.chat.im.handler

11 | * 12 | *

@Description: 服务端返回的消息发送状态报告

13 | *
14 | *

@author: FreddyChen

15 | *

@date: 2019/04/22 19:16

16 | *

@email: chenshichao@outlook.com

17 | */ 18 | public class ServerReportMessageHandler extends AbstractMessageHandler { 19 | 20 | private static final String TAG = ServerReportMessageHandler.class.getSimpleName(); 21 | 22 | @Override 23 | protected void action(AppMessage message) { 24 | Log.d(TAG, "收到消息状态报告,message=" + message); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/freddy/chat/im/handler/SingleChatMessageHandler.java: -------------------------------------------------------------------------------- 1 | package com.freddy.chat.im.handler; 2 | 3 | import android.util.Log; 4 | 5 | import com.freddy.chat.bean.AppMessage; 6 | import com.freddy.chat.bean.SingleMessage; 7 | import com.freddy.chat.event.CEventCenter; 8 | import com.freddy.chat.event.Events; 9 | 10 | /** 11 | *

@ProjectName: NettyChat

12 | *

@ClassName: SingleChatMessageHandler.java

13 | *

@PackageName: com.freddy.chat.im.handler

14 | * 15 | *

@Description: 类描述

16 | *
17 | *

@author: FreddyChen

18 | *

@date: 2019/04/10 03:43

19 | *

@email: chenshichao@outlook.com

20 | */ 21 | public class SingleChatMessageHandler extends AbstractMessageHandler { 22 | 23 | private static final String TAG = SingleChatMessageHandler.class.getSimpleName(); 24 | 25 | @Override 26 | protected void action(AppMessage message) { 27 | Log.d(TAG, "收到单聊消息,message=" + message); 28 | 29 | SingleMessage msg = new SingleMessage(); 30 | msg.setMsgId(message.getHead().getMsgId()); 31 | msg.setMsgType(message.getHead().getMsgType()); 32 | msg.setMsgContentType(message.getHead().getMsgContentType()); 33 | msg.setFromId(message.getHead().getFromId()); 34 | msg.setToId(message.getHead().getToId()); 35 | msg.setTimestamp(message.getHead().getTimestamp()); 36 | msg.setExtend(message.getHead().getExtend()); 37 | msg.setContent(message.getBody()); 38 | 39 | 40 | CEventCenter.dispatchEvent(Events.CHAT_SINGLE_MESSAGE, 0, 0, msg); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/com/freddy/chat/utils/CThreadPoolExecutor.java: -------------------------------------------------------------------------------- 1 | package com.freddy.chat.utils; 2 | 3 | import android.os.Handler; 4 | import android.os.Looper; 5 | import android.util.Log; 6 | 7 | import java.util.HashMap; 8 | import java.util.Iterator; 9 | import java.util.Map; 10 | import java.util.concurrent.Callable; 11 | import java.util.concurrent.ExecutorService; 12 | import java.util.concurrent.Executors; 13 | import java.util.concurrent.Future; 14 | import java.util.concurrent.LinkedBlockingQueue; 15 | import java.util.concurrent.ThreadFactory; 16 | import java.util.concurrent.ThreadPoolExecutor; 17 | import java.util.concurrent.TimeUnit; 18 | import java.util.concurrent.atomic.AtomicInteger; 19 | 20 | /** 21 | *

@ProjectName: BoChat

22 | *

@ClassName: CThreadPoolExecutor.java

23 | *

@PackageName: com.bochat.app.utils

24 | * 25 | *

@Description: 自定义固定大小的线程池 26 | * 每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。 27 | * 线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。 28 | *

29 | * 合理利用线程池能够带来三个好处: 30 | * 第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。 31 | * 第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。 32 | * 第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。 33 | * 我们可以通过ThreadPoolExecutor来创建一个线程池: 34 | * new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, milliseconds,runnableTaskQueue, handler); 35 | *

36 | * corePoolSize(线程池的基本大小): 37 | * 当提交一个任务到线程池时,线程池会创建一个线程来执行任务, 38 | * 即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。 39 | * 如果调用了线程池的prestartAllCoreThreads方法,线程池会提前创建并启动所有基本线程。 40 | *

41 | * runnableTaskQueue(任务队列):用于保存等待执行的任务的阻塞队列。 可以选择以下几个阻塞队列。 42 | * ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。 43 | *

44 | * LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。 45 | * 静态工厂方法Executors.newFixedThreadPool()使用了这个队列。 46 | *

47 | * SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态, 48 | * 吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。 49 | *

50 | * PriorityBlockingQueue:一个具有优先级的无限阻塞队列。 51 | *

52 | * maximumPoolSize(线程池最大大小): 53 | * 线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是如果使用了无界的任务队列这个参数就没什么效果。 54 | *

55 | * ThreadFactory: 56 | * 用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字。 57 | *

58 | * RejectedExecutionHandler(饱和策略):当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。 59 | * 这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。以下是JDK1.5提供的四种策略。 60 | * AbortPolicy:直接抛出异常。 61 | * CallerRunsPolicy:只用调用者所在线程来运行任务。 62 | * DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。 63 | * DiscardPolicy:不处理,丢弃掉。 64 | * 当然也可以根据应用场景需要来实现RejectedExecutionHandler接口自定义策略。如记录日志或持久化不能处理的任务。 65 | *

66 | * keepAliveTime(线程活动保持时间):线程池的工作线程空闲后,保持存活的时间。 67 | * 所以如果任务很多,并且每个任务执行的时间比较短,可以调大这个时间,提高线程的利用率。 68 | *

69 | * TimeUnit(线程活动保持时间的单位):可选的单位有天(DAYS),小时(HOURS),分钟(MINUTES), 70 | * 毫秒(MILLISECONDS),微秒(MICROSECONDS, 千分之一毫秒)和毫微秒(NANOSECONDS, 千分之一微秒)。

71 | *
72 | *

@author: FreddyChen

73 | *

@date: 2019/2/3 15:35

74 | *

@email: chenshichao@outlook.com

75 | * 76 | * @see http://www.infoq.com/cn/articles/java-threadPool 77 | */ 78 | public class CThreadPoolExecutor { 79 | 80 | private static final String TAG = CThreadPoolExecutor.class.getSimpleName(); 81 | private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();// CPU个数 82 | // private static final int CORE_POOL_SIZE = CPU_COUNT + 1;// 线程池中核心线程的数量 83 | // private static final int MAXIMUM_POOL_SIZE = 2 * CPU_COUNT + 1;// 线程池中最大线程数量 84 | private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));// 线程池中核心线程的数量 85 | private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;// 线程池中最大线程数量 86 | private static final long KEEP_ALIVE_TIME = 30L;// 非核心线程的超时时长,当系统中非核心线程闲置时间超过keepAliveTime之后,则会被回收。如果ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true,则该参数也表示核心线程的超时时长 87 | private static final int WAIT_COUNT = 128; // 最多排队个数,这里控制线程创建的频率 88 | 89 | private static ThreadPoolExecutor pool = createThreadPoolExecutor(); 90 | 91 | private static ThreadPoolExecutor createThreadPoolExecutor() { 92 | if (pool == null) { 93 | pool = new ThreadPoolExecutor( 94 | CORE_POOL_SIZE, 95 | MAXIMUM_POOL_SIZE, 96 | KEEP_ALIVE_TIME, 97 | TimeUnit.SECONDS, 98 | new LinkedBlockingQueue(WAIT_COUNT), 99 | new CThreadFactory("CThreadPool", Thread.NORM_PRIORITY - 2), 100 | new CHandlerException()); 101 | } 102 | 103 | return pool; 104 | } 105 | 106 | public static class CThreadFactory implements ThreadFactory { 107 | private AtomicInteger counter = new AtomicInteger(1); 108 | private String prefix = ""; 109 | private int priority = Thread.NORM_PRIORITY; 110 | 111 | public CThreadFactory(String prefix, int priority) { 112 | this.prefix = prefix; 113 | this.priority = priority; 114 | } 115 | 116 | public CThreadFactory(String prefix) { 117 | this.prefix = prefix; 118 | } 119 | 120 | public Thread newThread(Runnable r) { 121 | Thread executor = new Thread(r, prefix + " #" + counter.getAndIncrement()); 122 | executor.setDaemon(true); 123 | executor.setPriority(priority); 124 | return executor; 125 | } 126 | } 127 | 128 | /** 129 | * 抛弃当前的任务 130 | */ 131 | private static class CHandlerException extends ThreadPoolExecutor.AbortPolicy { 132 | 133 | @Override 134 | public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { 135 | Log.d(TAG, "rejectedExecution:" + r); 136 | Log.e(TAG, logAllThreadStackTrace().toString()); 137 | // Tips.showForce("任务被拒绝", 5000); 138 | if (!pool.isShutdown()) { 139 | pool.shutdown(); 140 | pool = null; 141 | } 142 | 143 | pool = createThreadPoolExecutor(); 144 | } 145 | } 146 | 147 | private static ExecutorService jobsForUI = Executors.newFixedThreadPool( 148 | CORE_POOL_SIZE, new CThreadFactory("CJobsForUI", Thread.NORM_PRIORITY - 1)); 149 | 150 | /** 151 | * 启动一个消耗线程,常驻后台 152 | * 153 | * @param r 154 | */ 155 | public static void startConsumer(final Runnable r, final String name) { 156 | runInBackground(new Runnable() { 157 | public void run() { 158 | new CThreadFactory(name, Thread.NORM_PRIORITY - 3).newThread(r).start(); 159 | } 160 | }); 161 | } 162 | 163 | /** 164 | * 提交到其他线程去跑,需要取数据的时候会等待任务完成再继续 165 | * 166 | * @param task 167 | * @return 168 | */ 169 | public static Future submitTask(Callable task) { 170 | return jobsForUI.submit(task); 171 | } 172 | 173 | /** 174 | * 强制清理任务 175 | * 176 | * @param task 177 | * @return 178 | */ 179 | public static void cancelTask(Future task) { 180 | if (task != null) { 181 | task.cancel(true); 182 | } 183 | } 184 | 185 | /** 186 | * 从 Future 中获取值,如果发生异常,打日志 187 | * 188 | * @param future 189 | * @param tag 190 | * @param name 191 | * @return 192 | */ 193 | public static T getFromTask(Future future, String tag, String name) { 194 | try { 195 | return future.get(); 196 | } catch (Exception e) { 197 | Log.e(tag, (name != null ? name + ": " : "") + e.toString()); 198 | } 199 | return null; 200 | } 201 | 202 | public static void runInBackground(Runnable runnable) { 203 | if (pool == null) { 204 | createThreadPoolExecutor(); 205 | } 206 | 207 | pool.execute(runnable); 208 | // Future future = pool.submit(runnable); 209 | // try { 210 | // future.get(); 211 | // } catch (InterruptedException e) { 212 | // e.printStackTrace(); 213 | // } catch (ExecutionException e) { 214 | // e.printStackTrace(); 215 | // } 216 | } 217 | 218 | private static Thread mainThread; 219 | private static Handler mainHandler; 220 | 221 | static { 222 | Looper mainLooper = Looper.getMainLooper(); 223 | mainThread = mainLooper.getThread(); 224 | mainHandler = new Handler(mainLooper); 225 | } 226 | 227 | public static boolean isOnMainThread() { 228 | return mainThread == Thread.currentThread(); 229 | } 230 | 231 | public static void runOnMainThread(Runnable r) { 232 | if (isOnMainThread()) { 233 | r.run(); 234 | } else { 235 | mainHandler.post(r); 236 | } 237 | } 238 | 239 | public static void runOnMainThread(Runnable r, long delayMillis) { 240 | if (delayMillis <= 0) { 241 | runOnMainThread(r); 242 | } else { 243 | mainHandler.postDelayed(r, delayMillis); 244 | } 245 | } 246 | 247 | // 用于记录后台等待的Runnable,第一个参数外面的Runnable,第二个参数是等待中的Runnable 248 | private static HashMap mapToMainHandler = new HashMap(); 249 | 250 | public static void runInBackground(final Runnable runnable, long delayMillis) { 251 | if (delayMillis <= 0) { 252 | runInBackground(runnable); 253 | } else { 254 | Runnable mainRunnable = new Runnable() { 255 | 256 | @Override 257 | public void run() { 258 | mapToMainHandler.remove(runnable); 259 | pool.execute(runnable); 260 | } 261 | }; 262 | 263 | mapToMainHandler.put(runnable, mainRunnable); 264 | mainHandler.postDelayed(mainRunnable, delayMillis); 265 | } 266 | } 267 | 268 | /** 269 | * 对runOnMainThread的,移除Runnable 270 | * 271 | * @param r 272 | */ 273 | public static void removeCallbackOnMainThread(Runnable r) { 274 | mainHandler.removeCallbacks(r); 275 | } 276 | 277 | public static void removeCallbackInBackground(Runnable runnable) { 278 | Runnable mainRunnable = mapToMainHandler.get(runnable); 279 | if (mainRunnable != null) { 280 | mainHandler.removeCallbacks(mainRunnable); 281 | } 282 | } 283 | 284 | public static void logStatus() { 285 | StringBuilder sb = new StringBuilder(); 286 | sb.append("getActiveCount"); 287 | sb.append(pool.getActiveCount()); 288 | sb.append("\ngetTaskCount"); 289 | sb.append(pool.getTaskCount()); 290 | sb.append("\ngetCompletedTaskCount"); 291 | sb.append(pool.getCompletedTaskCount()); 292 | Log.d(TAG, sb.toString()); 293 | } 294 | 295 | public static StringBuilder logAllThreadStackTrace() { 296 | StringBuilder builder = new StringBuilder(); 297 | Map liveThreads = Thread.getAllStackTraces(); 298 | for (Iterator i = liveThreads.keySet().iterator(); i.hasNext(); ) { 299 | Thread key = i.next(); 300 | builder.append("Thread ").append(key.getName()) 301 | .append("\n"); 302 | StackTraceElement[] trace = liveThreads.get(key); 303 | for (int j = 0; j < trace.length; j++) { 304 | builder.append("\tat ").append(trace[j]).append("\n"); 305 | } 306 | } 307 | return builder; 308 | } 309 | 310 | public static void main(String[] args) { 311 | for (int i = 0; i < 10000; i++) { 312 | final int index = i; 313 | System.out.println("index=" + index); 314 | CThreadPoolExecutor.runInBackground(new Runnable() { 315 | @Override 316 | public void run() { 317 | System.out.println("正在运行第[" + (index + 1) + "]个线程."); 318 | } 319 | }); 320 | try { 321 | Thread.sleep(10); 322 | } catch (InterruptedException e) { 323 | e.printStackTrace(); 324 | } 325 | } 326 | } 327 | } 328 | -------------------------------------------------------------------------------- /app/src/main/java/com/freddy/chat/utils/StringUtil.java: -------------------------------------------------------------------------------- 1 | package com.freddy.chat.utils; 2 | 3 | import android.telephony.PhoneNumberUtils; 4 | 5 | import java.util.regex.Matcher; 6 | import java.util.regex.Pattern; 7 | 8 | public class StringUtil { 9 | 10 | private final static Pattern phonePattern = Pattern 11 | .compile("^1\\d{10}$"); 12 | 13 | /** 14 | * 去除特殊字符或将所有中文标号替换为英文标号 15 | * 16 | * @param str 17 | * @return 18 | */ 19 | public static String stringFilter(String str) { 20 | str = str.replaceAll("【", "[").replaceAll("】", "]").replaceAll("!", "!").replaceAll(":", ":");// 替换中文标号 21 | String regEx = "[『』]"; // 清除掉特殊字符 22 | Pattern p = Pattern.compile(regEx); 23 | Matcher m = p.matcher(str); 24 | return m.replaceAll("").trim(); 25 | } 26 | 27 | /** 28 | * 半角转换为全角 29 | * 30 | * @param input 31 | * @return 32 | */ 33 | public static String toDBC(String input) { 34 | char[] c = input.toCharArray(); 35 | for (int i = 0; i < c.length; i++) { 36 | if (c[i] == 12288) { 37 | c[i] = (char) 32; 38 | continue; 39 | } 40 | if (c[i] > 65280 && c[i] < 65375) 41 | c[i] = (char) (c[i] - 65248); 42 | } 43 | return new String(c); 44 | } 45 | 46 | /** 47 | * string to int 48 | * 49 | * @param input 50 | * @return 51 | */ 52 | public static int stringtoint(String input) { 53 | try { 54 | return Integer.parseInt(input); 55 | } catch (Exception e) { 56 | return -1; 57 | } 58 | 59 | } 60 | 61 | /** 62 | * string to long 63 | * 64 | * @param input 65 | * @return 66 | */ 67 | public static long stringtolong(String input) { 68 | try { 69 | return Long.parseLong(input); 70 | } catch (Exception e) { 71 | return 0l; 72 | } 73 | 74 | } 75 | 76 | /** 77 | * 判断内容是否为空 78 | * 79 | * @date 2013-10-24下午4:20:03 80 | * @author hx 81 | * @param o 82 | * @return 83 | */ 84 | public static boolean isEmpty(Object o) { 85 | return (null == o || o.toString().trim().equals("")) ? true : false; 86 | } 87 | 88 | /** 89 | * 字符长度 90 | * 91 | * @date 2013-10-24下午4:20:03 92 | * @author hx 93 | * @param o 94 | * @return 95 | */ 96 | public static int getLength(Object o) { 97 | if (null == o || o.toString().trim().equals("")) { 98 | return 0; 99 | } else { 100 | return o.toString().trim().length(); 101 | } 102 | 103 | } 104 | 105 | /** 106 | * object to int 107 | * 108 | * @date 2014-1-3下午2:14:39 109 | * @author hx 110 | * @param o 111 | * @return 112 | */ 113 | public static int ObjectToInt(Object o) { 114 | if (null == o || o.toString().trim().equals("")) { 115 | return -1; 116 | } else { 117 | return Integer.parseInt(o.toString()); 118 | } 119 | 120 | } 121 | /** 122 | * 去除非法字符(换行、回车...) 123 | * @author liu_haifang 124 | * @date 2014-11-7 下午2:36:48 125 | * @param str 126 | * @return 127 | */ 128 | public static String rmUnqualified (String str){ 129 | if (!isEmpty(str)) { 130 | Pattern p = Pattern.compile("\\s*|\t|\r|\n"); 131 | Matcher m = p.matcher(str); 132 | return m.replaceAll(""); 133 | } 134 | return null; 135 | } 136 | 137 | public static boolean isNotEmpty(Object o) { 138 | return !(null == o || o.toString().trim().equals("")) ; 139 | } 140 | 141 | /** 142 | * 比较两个字符串(大小写敏感)。 143 | * 144 | *
145 | 	 *
146 | 	 *    StringUtil.equals(null, null)   = true
147 | 	 *    StringUtil.equals(null, "abc")  = false
148 | 	 *    StringUtil.equals("abc", null)  = false
149 | 	 *    StringUtil.equals("abc", "abc") = true
150 | 	 *    StringUtil.equals("abc", "ABC") = false
151 | 	 *
152 | 	 * 
153 | * 154 | * @param str1 155 | * 要比较的字符串1 156 | * @param str2 157 | * 要比较的字符串2 158 | * 159 | * @return 如果两个字符串相同,或者都是 null ,则返回 true 160 | */ 161 | public static boolean equals(String str1, String str2) 162 | { 163 | if ( str1 == null ) 164 | { 165 | return str2 == null; 166 | } 167 | 168 | return str1.equals(str2); 169 | } 170 | 171 | /** 172 | * 利用正则表达式判断字符串是否是数字 173 | * @param str 174 | * @return 175 | */ 176 | public static boolean isNumeric(String str){ 177 | Pattern pattern = Pattern.compile("[0-9]*"); 178 | Matcher isNum = pattern.matcher(str); 179 | if( !isNum.matches() ){ 180 | return false; 181 | } 182 | return true; 183 | } 184 | 185 | /** 186 | * 获取较纯净的手机号码
187 | * 删除前缀、空格等 188 | * 189 | * @param phone 190 | * @return 191 | */ 192 | public static String getValidPhoneNumber(String phone) { 193 | if (phone == null) 194 | return ""; 195 | if (phone.startsWith("0086")) { 196 | phone = phone.substring(4); 197 | } 198 | if (phone.startsWith("+86")) { 199 | phone = phone.substring(3); 200 | } 201 | PhoneNumberUtils.stripSeparators(phone); 202 | phone = phone.replace("-", "").replace(" ", "").trim(); 203 | return phone; 204 | } 205 | 206 | /** 207 | * 判断是不是一个合法的手机号码 208 | */ 209 | public static boolean isPhone(CharSequence phoneNum) { 210 | if (isEmpty(phoneNum)) 211 | return false; 212 | return phonePattern.matcher(phoneNum).matches(); 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /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 | 2 | 8 | 9 | 13 | 14 |