安卓 tcp 客户端
Server:8888 是Qt 写的Tcp 服务器 ip 是 192.168.2.103 port是8888
安卓手机运行 kotlin 语法的Tcp Client ,连接,收发数据
效果如下图
Tcpclient
package com.example.myapplication
import android.os.Handler
import android.os.Looper
import android.util.Log
import java.io.BufferedReader
import java.io.BufferedWriter
import java.io.InputStreamReader
import java.io.OutputStreamWriter
import java.net.Socket
class TcpClient(private val ipAddress: String, private val port: Int) {
private lateinit var socket: Socket
private lateinit var reader: BufferedReader
private lateinit var writer: BufferedWriter
// 接收线程
private val messageReceiverThread = Thread {
//主消息处理器,用于向外部发送tcp收到的数据
val handler = Handler(Looper.getMainLooper())
val buffer = StringBuilder()
val charBuffer = CharArray(1024) // 调整缓冲区大小
while (!Thread.currentThread().isInterrupted) {
try {
// val receivedData = reader.readLine() ?: ""
// Log.d("TcpClient",receivedData)
// handler.post {
// onDataReceived(receivedData)
// }
val bytesRead = reader.read(charBuffer)
if (bytesRead == -1) {
// 如果没有更多数据可读,则退出循环
Log.d("TcpClient","continue")
continue
}
// 清空缓冲区
buffer.clear()
// 将读取的数据追加到缓冲区
buffer.append(charBuffer, 0, bytesRead)
// 通知UI线程更新UI
handler.post {
Log.d("TcpClient","buffer : "+buffer.toString())
onDataReceived(buffer.toString())
}
} catch (e: Exception) {
Log.e("TcpClient","Exception")
e.printStackTrace()
break
}
}
}
// 外部调用,定义数据接收监听器接口
interface DataReceivedListener {
fun onDataReceived(data: String)
}
private var dataReceivedListener: DataReceivedListener? = null
// 外部调用,设置数据接收监听器
fun setDataReceivedListener(listener: DataReceivedListener) {
dataReceivedListener = listener
}
// 通知数据接收事件
private fun onDataReceived(data: String) {
dataReceivedListener?.onDataReceived(data)
}
// 1
fun connectToServer() {
try {
socket = Socket(ipAddress, port)
reader = BufferedReader(InputStreamReader(socket.getInputStream()))
writer = BufferedWriter(OutputStreamWriter(socket.getOutputStream()))
} catch (e: Exception) {
e.printStackTrace()
}
}
fun sendMessage(message: String) {
try {
writer.write(message)
// writer.newLine()
writer.flush()
} catch (e: Exception) {
e.printStackTrace()
}
}
// 2
fun startMessageReceiver() {
messageReceiverThread.start()
}
fun stopMessageReceiver() {
messageReceiverThread.interrupt()
}
fun close() {
try {
socket.close()
} catch (e: Exception) {
e.printStackTrace()
}
}
}
MainActivity
package com.example.myapplication
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.text.Editable
import android.util.Log
import android.widget.Button
import android.widget.EditText
import android.widget.TextView
class MainActivity : AppCompatActivity() {
private lateinit var ipAddress: String
private var port: Int = 0
private lateinit var recvText: EditText
private lateinit var tcpClient: TcpClient
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
recvText = findViewById(R.id.recvText)
// start tcp
val startButton: Button = findViewById(R.id.startBtn)
// 设置按钮点击事件
startButton.setOnClickListener {
// 设置IP地址和端口(请根据需要修改)
ipAddress = findViewById<EditText?>(R.id.ipText).text.toString()
port = findViewById<EditText?>(R.id.portNum).text.toString().toInt()
// 创建TcpClient实例
tcpClient = TcpClient(ipAddress, port)
// 设置数据接收监听器
tcpClient.setDataReceivedListener(object : TcpClient.DataReceivedListener {
override fun onDataReceived(data: String) {
// 在数据接收回调中更新UI
updateUI(data)
}
})
// 在新线程中执行连接操作
Thread {
tcpClient.connectToServer()
tcpClient.startMessageReceiver()
}.start()
}
// stop tcp
val stopBtn: Button = findViewById(R.id.stopBtn)
stopBtn.setOnClickListener {
Thread {
tcpClient.stopMessageReceiver()
tcpClient.close()
}.start()
}
// send on thread
val sendBtn: Button = findViewById(R.id.sendBtn)
sendBtn.setOnClickListener {
Thread {
var sendText : EditText= findViewById(R.id.sendText)
tcpClient.sendMessage(sendText.text.toString())
}.start()
}
var cleanBtn:Button = findViewById(R.id.cleanBtn)
cleanBtn.setOnClickListener {
recvText.text.clear()
}
}
private fun updateUI(data: String) {
Log.d("MainActivity","data:"+data)
val editableText = Editable.Factory.getInstance().newEditable(data)
recvText.text?.append(editableText)
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<LinearLayout
android:layout_width="406dp"
android:layout_height="53dp"
android:orientation="horizontal">
<EditText
android:id="@+id/ipText"
android:layout_width="204dp"
android:layout_height="47dp"
android:layout_weight="4"
android:ems="10"
android:inputType="text"
android:text="192.168.2.103" />
<EditText
android:id="@+id/portNum"
android:layout_width="204dp"
android:layout_height="45dp"
android:layout_weight="1"
android:ems="10"
android:inputType="number"
android:text="8888" />
</LinearLayout>
<LinearLayout
android:layout_width="409dp"
android:layout_height="55dp"
android:orientation="horizontal">
<Button
android:id="@+id/startBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="start" />
<Button
android:id="@+id/stopBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="stop" />
<Space
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1" />
</LinearLayout>
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="send data"
android:textSize="20sp" />
<EditText
android:id="@+id/sendText"
android:layout_width="match_parent"
android:layout_height="148dp"
android:layout_weight="4"
android:ems="10"
android:gravity="start|top"
android:inputType="textMultiLine" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/sendBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="send" />
<Button
android:id="@+id/cleanBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="clean recv" />
</LinearLayout>
<TextView
android:id="@+id/textView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="recv data"
android:textSize="20sp" />
<EditText
android:id="@+id/recvText"
android:layout_width="match_parent"
android:layout_height="202dp"
android:layout_weight="4"
android:ems="10"
android:gravity="start|top"
android:inputType="textMultiLine" />
</LinearLayout>
AndroidManifest.xml 配置清单
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myapplication">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MyApplication">
<activity
android:name=".MainActivity"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
build.gradle (app)
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
}
android {
compileSdkVersion 30
buildToolsVersion "30.0.3"
defaultConfig {
applicationId "com.example.myapplication"
minSdkVersion 28
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.2.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}
build.gradle (my proj )
使用国内镜像
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
maven { url 'https://maven.aliyun.com/repository/google' }
maven { url 'https://maven.aliyun.com/repository/jcenter' }
maven { url 'https://maven.aliyun.com/repository/public' }
maven { url 'https://maven.aliyun.com/repository/gradle-plugin' }
// google()
// mavenCentral()
}
dependencies {
classpath "com.android.tools.build:gradle:4.2.1"
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
maven { url 'https://maven.aliyun.com/repository/google' }
maven { url 'https://maven.aliyun.com/repository/jcenter' }
maven { url 'https://maven.aliyun.com/repository/public' }
maven { url 'https://maven.aliyun.com/repository/gradle-plugin' }
// google()
// mavenCentral()
// jcenter() // Warning: this repository is going to shut down soon
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}