android中进程间通信的几种方式

article/2025/10/9 13:15:33

进程间通信(IPC)方式

  • 使用Bundle
  • 使用文件共享
  • 使用Messenger
  • 使用AIDL
  • 使用COntentProvider
  • 使用Socket

一、使用Bundle

  我们都知道Android中三大组件Activity,Service,Receiver都支持在Intent中传递Bundle数据,而Bundle实现了Parcelable接口,所以它可以方便的在不同的进程间进行传输。当我我们在一个进程中启动另外一个进程的Activity、Service、Receiver时,我们就可以在Bundle中附加我们所需要传输给远程进程的信息并通过intent发送出去。这里注意,我们传输的数据必须能够被序列化。

  下面我们看一下利用Bundle进行进程间通信的例子:

private void startWithIntent(){Intent intent = new Intent();//制定要打开的程序的包名(必须写全包名,不然会报错)和地址(activity名)intent.setComponent(new ComponentName("PackageName", "PackageName.intentIpcActivity"));//通过budle传递数据,可以携带序列化数据Bundle bundle = new Bundle();bundle.putInt("intextra", 0);bundle.putString("stringextra", "测试数据");intent.putExtras(bundle);try{startActivity(intent);}catch(Exception e){ToastUtils.showMessage("没有找到对应文件");}
}

  利用Bundle进行进程间通信是很容易的,大家应该也注意到,这种方式进行进程间通信只能是单方向的简单数据传输,它的使用有一定的局限性。

二、使用文件共享

共享文件也是种不错的进程间通信的方式,两个进程通过读/写同一个文件来交换数据,比如A进程把数据写入文件FILE,B进程可以通过读取这个文件来获取这个数据。通过这种方式,除了可以交换简单的文本信息外,我们还可以序列化一个对象到文件系统中,另一个进程可以通过反序列化恢复这个对象。

  比如A在进程中创建一个线程进行写数据

new Thread(new Runnable(){@Overridepublic void run(){User user = new User(1, "user", false);File cachedFile = new File(CACHE_FILE_PATH);ObjectOutputStream objectOutputStream = null;try{objectOutputStream = new ObjectOutputStream(new FileOutputStream(cachedFile));objectOutputStream.writeObject(user);}catch(IOException e){e.printStackTrace();}finally{objectOutputStream.close();}}
}).start();

  在B进程创建一个线程进行读取数据

new Thread(new Runnable(){@Overridepublic void run(){User user = null;File cachedFile = new File(CACHE_FILE_PATH);if (cachedFile.exists()){ObjectInputStream objectInputStream = null;try{objectInputStream = new ObjectInputStream(new FileInputStream(cachedFile));user = objectInputStream.readObject(user);} catch(IOException e){e.printStackTrace();}finally{objectInputStream.close();}}try{objectOutputStream = new ObjectOutputStream(new FileOutputStream(cachedFile));objectOutputStream.writeObject(user);}catch (IOException e){e,printStackTrace();}finally{objectOutputStream.close();}}

  通过文件共享的这种方式来共享数据对文件的格式是没有具体要求的,比如可以是文本文件、也可以是XML文件,只要读写双方约定数据格式即可。这种方式进行进程间通信虽然方便,可是也是有局限性的,比如并发读/写,这会导致比较严重的问题,如读取的数据不完整或者读取的数据不是最新的。因此通过文件共享的方式适合在对数据同步要求不高的进程之间通信,并且要妥善处理并发读/写问题。

三、使用Messenger

  Messenger是一种轻量级的IPC方案,它的底层实现是AIDL,可以在不同进程中传递Messenger对象,在Messenger中放入我们需要传递的数据。它一次只处理一个请求,在服务端不需要考虑线程同步的问题,服务端不存在并发执行的情形。

  Messenger的使用方法也是比较简单的,实现一个Messenger有如下几步,分为服务端和客户端:

  服务端进程:在A进程创建一个Service来处理其他进程的连接请求,同时创建一个Handler并通过他来创建一个Messenger对象,然后在Service的onBind中返回这个Messneger对象底层的Binder即可。

public class MessengerService extends Service {private static final String TAG = MessengerService.class.getSimpleName();private class MessengerHandler extends Handler {/*** @param msg*/@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case Constants.MSG_FROM_CLIENT:Log.d(TAG, "receive msg from client: msg = [" + msg.getData().getString(Constants.MSG_KEY) + "]");Toast.makeText(MessengerService.this, "receive msg from client: msg = [" + msg.getData().getString(Constants.MSG_KEY) + "]", Toast.LENGTH_SHORT).show();Messenger client = msg.replyTo;Message replyMsg = Message.obtain(null, Constants.MSG_FROM_SERVICE);Bundle bundle = new Bundle();bundle.putString(Constants.MSG_KEY, "我已经收到你的消息,稍后回复你!");replyMsg.setData(bundle);try {client.send(replyMsg);} catch (RemoteException e) {e.printStackTrace();}break;default:super.handleMessage(msg);}}}private Messenger mMessenger = new Messenger(new MessengerHandler());@Nullable@Overridepublic IBinder onBind(Intent intent) {return mMessenger.getBinder();}
}

  客户端进程:首先绑定服务端 Service ,绑定成功之后用服务端的 IBinder 对象创建一个 Messenger ,通过这个 Messenger 就可以向服务端发送消息了,消息类型是 Message 。如果需要服务端响应,则需要创建一个 Handler 并通过它来创建一个 Messenger(和服务端一样),并通过 Message 的 replyTo 参数传递给服务端。服务端通过 Message 的 replyTo 参数就可以回应客户端了。

public class MainActivity extends AppCompatActivity {private static final String TAG = MainActivity.class.getSimpleName();private Messenger mGetReplyMessenger = new Messenger(new MessageHandler());private Messenger mService;//为了收到Service的回复,客户端需要创建一个接收消息的Messenger和Handlerprivate class MessageHandler extends Handler {@Overridepublic void handleMessage(Message msg) {//消息处理switch (msg.what) {case Constants.MSG_FROM_SERVICE:Log.d(TAG, "received msg form service: msg = [" + msg.getData().getString(Constants.MSG_KEY) + "]");Toast.makeText(MainActivity.this, "received msg form service: msg = [" + msg.getData().getString(Constants.MSG_KEY) + "]", Toast.LENGTH_SHORT).show();break;default:super.handleMessage(msg);}}}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);}public void bindService(View v) {Intent mIntent = new Intent(this, MessengerService.class);bindService(mIntent, mServiceConnection, Context.BIND_AUTO_CREATE);}public void sendMessage(View v) {Message msg = Message.obtain(null,Constants.MSG_FROM_CLIENT);Bundle data = new Bundle();data.putString(Constants.MSG_KEY, "Hello! This is client.");msg.setData(data);msg.replyTo = mGetReplyMessenger;try {mService.send(msg);} catch (RemoteException e) {e.printStackTrace();}}@Overrideprotected void onDestroy() {unbindService(mServiceConnection);super.onDestroy();}private ServiceConnection mServiceConnection = new ServiceConnection() {/*** @param name* @param service*/@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {//根据得到的IBinder对象创建MessengermService = new Messenger(service);//通过得到的mService 可以进行通信Message msg = Message.obtain(null,Constants.MSG_FROM_CLIENT);Bundle data = new Bundle();data.putString(Constants.MSG_KEY, "Hello! This is client.");msg.setData(data);//msg.replyTo = mGetReplyMessenger;try {mService.send(msg);} catch (RemoteException e) {e.printStackTrace();}}/*** @param name*/@Overridepublic void onServiceDisconnected(ComponentName name) {}};
}

  这里画一张Messenger的工作原理图,以便于更好的理解Messenger:

  Messenger内部消息处理使用Handler实现的,所以他是以串行的方式处理客户端发送过来的消息的,如果有大量的消息发送给服务端,服务端只能一个一个处理,如果并发量大的话用Messenger就不合适了,而且Messenger的主要作用是为了传递消息的,很多时候我们需要跨进程调用服务端的方法,这种需求Messenger就无法做到了。

  注意:客户端和服务端是通过拿到对方的 Messenger 来发送 Message 的。只不过客户端通过 bindService onServiceConnected 而服务端通过 message.replyTo 来获得对方的 Messenger 。Messenger 中有一个 Hanlder 以串行的方式处理队列中的消息。不存在并发执行,因此我们不用考虑线程同步的问题。

四、使用AIDL

  AIDL (Android Interface Definition Language)是一种IDL语言,用于生成可以在Android设备上两个进程之间进行进程间通信(IPC)的代码。如果在一个进程中(例如Activity)要调用另一个进程中(例如Service)对象的操作,就可以使用AIDL生成可序列化的参数。

  AIDL是IPC的一个轻量级实现,用了对于Java开发者来说很熟悉的语法。Android也提供了一个工具,可以自动创建Stub(类构架,类骨架)。当我们需要在应用间通信时,我们需要按以下几步走:

  •  定义一个AIDL接口
  • 为远程服务(Service)实现对应Stub
  • 将服务“暴露”给客户程序使用

  官方文档中对AIDL有这样一段介绍:Using AIDL is necessary only if you allow clients from different applications to access your service for IPC and want to handle multithreading in your service. If you do not need to perform concurrent IPC across different applications, you should create your interface by implementing a Binder or, if you want to perform IPC, but do not need to handle multithreading, implement your interface using a Messenger. Regardless, be sure that you understand Bound Services before implementing an AIDL.

  第一句最重要,“只有当你允许来自不同的客户端访问你的服务并且需要处理多线程问题时你才必须使用AIDL”,其他情况下你都可以选择其他方法,如使用Messager,也能跨进程通讯。可见AIDL是处理多线程、多客户端并发访问的。而Messager是单线程处理。

  Messenger 是以串行的方式处理客户端发来的消息,如果大量消息同时发送到服务端,服务端只能一个一个处理,所以大量并发请求就不适合用 Messenger ,而且 Messenger 只适合传递消息,不能跨进程调用服务端的方法。AIDL 可以解决并发和跨进程调用方法的问题,要知道 Messenger 本质上也是 AIDL ,只不过系统做了封装方便上层的调用而已。

AIDL文件支持的数据类型

  • 基本数据类型
  • String 和 CharSequence
  • ArrayList,里面的元素必须能够被AIDL支持;
  • HashMap,里面的元素必须能够被AIDL支持;
  • Parcelable,实现Parcelable接口的对象;注意:如果 AIDL 文件中用到了自定义的 Parcelable 对象,必须新建一个和它同名的 AIDL 文件。
  • AIDL。AIDL接口本身也可以在AIDL文件中使用。

AIDL很大的好处就是我们直接可以调用服务端进程所暴露出来的方法,下面简单介绍一下AIDL的使用:

服务端

服务端首先要创建一个Service用来监听客户端的请求,然后创建一个AIDL文件,将暴露给客户端的接口在这个AIDL文件中声明,最后在Service中实现这个AIDL接口即可。

(1)创建AIDL接口文件

  AIDL使用简单的语法来声明接口,描述其方法以及方法的参数和返回值。这些参数和返回值可以是任何类型,甚至是其他AIDL生成的接口。重要的是必须导入所有非内置类型,哪怕是这些类型是在与接口相同的包中。

package com.example.android;
interface IRemoteService {int getPid();void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,double aDouble, String aString);
}

(2)向客户端暴露接口

public class DDService extends Service {@Overridepublic void onCreate() {super.onCreate();System.out.println("DDService onCreate........" + "Thread: " + Thread.currentThread().getName());}@Overridepublic IBinder onBind(Intent arg0) {System.out.println("DDService onBind");return mBinder;}private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {public int getPid(){System.out.println("Thread: " + Thread.currentThread().getName());System.out.println("DDService getPid ");return Process.myPid();}public void basicTypes(int anInt, long aLong, boolean aBoolean,float aFloat, double aDouble, String aString) {System.out.println("Thread: " + Thread.currentThread().getName());System.out.println("basicTypes aDouble: " + aDouble +" anInt: " + anInt+" aBoolean " + aBoolean+" aString " + aString);}};
}

  这样我们的服务端就完成了,把服务端运行到模拟器(或者手机上),等一会可以看一下打印信息,重点看“线程名”。

客户端

客户端所做的事情就要简单很多了,首先需要绑定服务端Service,绑定成功后将服务端返回的Binder对象转成AIDL接口所属的类型,接着就可以调用AIDL中的方法了。

客户端调用远程服务的方法,被调用的方法运行在服务端的 Binder 线程池中,同时客户端的线程会被挂起,如果服务端方法执行比较耗时,就会导致客户端线程长时间阻塞,导致 ANR 。客户端的 onServiceConnected 和 onServiceDisconnected 方法都在 UI 线程中。

public class MainActivity extends Activity {private IRemoteService remoteService;@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);}ServiceConnection conn = new ServiceConnection() {@Overridepublic void onServiceDisconnected(ComponentName name) {}@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {remoteService = IRemoteService.Stub.asInterface(service);try {int pid = remoteService.getPid();int currentPid = Process.myPid();System.out.println("currentPID: " + currentPid +"  remotePID: " + pid);remoteService.basicTypes(12, 1223, true, 12.2f, 12.3, "我们的爱,我明白");} catch (RemoteException e) {e.printStackTrace();}System.out.println("bind success! " + remoteService.toString());}};/*** 监听按钮点击* @param view*/public void buttonClick(View view) {System.out.println("begin bindService");Intent intent = new Intent("duanqing.test.aidl");bindService(intent, conn, Context.BIND_AUTO_CREATE);}@Overrideprotected void onDestroy() {super.onDestroy();unbindService(conn);}
}

  这样就实现了通过AIDL进行进程间通信了,是不是也很简单,不过这个看似简单,其实底层Android为我们做了很多的事情,核心就是Binder,感兴趣的读者可以学习一下Binder原理。

五、使用ContentProvider

   ContentProvider(内容提供者)是Android中的四大组件之一,为了在应用程序之间进行数据交换,Android提供了ContentProvider,ContentProvider是不同应用之间进行数据交换的API,一旦某个应用程序通过ContentProvider暴露了自己的数据操作接口,那么不管该应用程序是否启动,其他应用程序都可以通过接口来操作接口内的数据,包括增、删、改、查等。ContentProvider分为系统的和自定义的,系统的也就是例如联系人,图片等数据。

开发一个ContentProvider的步骤很简单:

  定义自己的ContentProvider类,该类继承ContentProvider基类;

  在AndroidManifest.xml中注册这个ContentProvider,类似于Activity注册,注册时要给ContentProvider绑定一个域名;

  当我们注册好ContentProvider后,其他应用就可以访问ContentProvider暴露出来的数据了。

  ContentProvider只是暴露出来可供其他应用操作的数据,其他应用则需要通过ContentReslover来操作ContentProvider所暴露出来的数据。Context提供了getContentResolver()方法来获取ContentProvider对象,获取之后皆可以对暴露出来的数据进行增、删、改、查操作了。

使用ContentResolver操作数据的步骤也很简单:

  调用Activity的getContentResolver()获取ContentResolver对象

  根据调用的ContentResolver的insert()、delete()、update()、和query()方法操作数据库即可。

代码示例:

BookProvider.java

 

public class BookProvider extends ContentProvider {private static final String TAG = "BookProvider";public static final String AUTHORITY = "com.jc.ipc.Book.Provider";public static final Uri BOOK_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/book");public static final Uri USER_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/user");public static final int BOOK_URI_CODE = 0;public static final int USER_URI_CODE = 1;private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);static {sUriMatcher.addURI(AUTHORITY, "book", BOOK_URI_CODE);sUriMatcher.addURI(AUTHORITY, "user", USER_URI_CODE);}private Context mContext;private SQLiteDatabase mDB;@Overridepublic boolean onCreate() {mContext = getContext();initProviderData();return true;}private void initProviderData() {//不建议在 UI 线程中执行耗时操作mDB = new DBOpenHelper(mContext).getWritableDatabase();mDB.execSQL("delete from " + DBOpenHelper.BOOK_TABLE_NAME);mDB.execSQL("delete from " + DBOpenHelper.USER_TABLE_NAME);mDB.execSQL("insert into book values(3,'Android');");mDB.execSQL("insert into book values(4,'iOS');");mDB.execSQL("insert into book values(5,'Html5');");mDB.execSQL("insert into user values(1,'haohao',1);");mDB.execSQL("insert into user values(2,'nannan',0);");}@Nullable@Overridepublic Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {Log.d(TAG, "query, current thread"+ Thread.currentThread());String table = getTableName(uri);if (table == null) {throw new IllegalArgumentException("Unsupported URI" + uri);}return mDB.query(table, projection, selection, selectionArgs, null, null, sortOrder, null);}@Nullable@Overridepublic String getType(Uri uri) {Log.d(TAG, "getType");return null;}@Nullable@Overridepublic Uri insert(Uri uri, ContentValues values) {Log.d(TAG, "insert");String table = getTableName(uri);if (table == null) {throw new IllegalArgumentException("Unsupported URI" + uri);}mDB.insert(table, null, values);// 通知外界 ContentProvider 中的数据发生变化mContext.getContentResolver().notifyChange(uri, null);return uri;}@Overridepublic int delete(Uri uri, String selection, String[] selectionArgs) {Log.d(TAG, "delete");String table = getTableName(uri);if (table == null) {throw new IllegalArgumentException("Unsupported URI" + uri);}int count = mDB.delete(table, selection, selectionArgs);if (count > 0) {mContext.getContentResolver().notifyChange(uri, null);}return count;}@Overridepublic int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {Log.d(TAG, "update");String table = getTableName(uri);if (table == null) {throw new IllegalArgumentException("Unsupported URI" + uri);}int row = mDB.update(table, values, selection, selectionArgs);if (row > 0) {getContext().getContentResolver().notifyChange(uri, null);}return row;}private String getTableName(Uri uri) {String tableName = null;switch (sUriMatcher.match(uri)) {case BOOK_URI_CODE:tableName = DBOpenHelper.BOOK_TABLE_NAME;break;case USER_URI_CODE:tableName = DBOpenHelper.USER_TABLE_NAME;break;default:break;}return tableName;}
}

View Code

DBOpenHelper.java

 

public class DBOpenHelper extends SQLiteOpenHelper {private static final String DB_NAME = "book_provider.db";public static final String BOOK_TABLE_NAME = "book";public static final String USER_TABLE_NAME = "user";private static final int DB_VERSION = 1;private String CREATE_BOOK_TABLE = "CREATE TABLE IF NOT EXISTS "+ BOOK_TABLE_NAME + "(_id INTEGER PRIMARY KEY," + "name TEXT)";private String CREATE_USER_TABLE = "CREATE TABLE IF NOT EXISTS "+ USER_TABLE_NAME + "(_id INTEGER PRIMARY KEY," + "name TEXT,"+ "sex INT)";public DBOpenHelper(Context context) {super(context, DB_NAME, null, DB_VERSION);}@Overridepublic void onCreate(SQLiteDatabase db) {db.execSQL(CREATE_BOOK_TABLE);db.execSQL(CREATE_USER_TABLE);}@Overridepublic void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {}
}

View Code

ProviderActivity.java

 

public class ProviderActivity extends AppCompatActivity {private static final String TAG = ProviderActivity.class.getSimpleName();private TextView displayTextView;private Handler mHandler;@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_provider);displayTextView = (TextView) findViewById(R.id.displayTextView);mHandler = new Handler();getContentResolver().registerContentObserver(BookProvider.BOOK_CONTENT_URI, true, new ContentObserver(mHandler) {@Overridepublic boolean deliverSelfNotifications() {return super.deliverSelfNotifications();}@Overridepublic void onChange(boolean selfChange) {super.onChange(selfChange);}@Overridepublic void onChange(boolean selfChange, Uri uri) {Toast.makeText(ProviderActivity.this, uri.toString(), Toast.LENGTH_SHORT).show();super.onChange(selfChange, uri);}});}public void insert(View v) {ContentValues values = new ContentValues();values.put("_id",1123);values.put("name", "三国演义");getContentResolver().insert(BookProvider.BOOK_CONTENT_URI, values);}public void delete(View v) {getContentResolver().delete(BookProvider.BOOK_CONTENT_URI, "_id = 4", null);}public void update(View v) {ContentValues values = new ContentValues();values.put("_id",1123);values.put("name", "三国演义新版");getContentResolver().update(BookProvider.BOOK_CONTENT_URI, values , "_id = 1123", null);}public void query(View v) {Cursor bookCursor = getContentResolver().query(BookProvider.BOOK_CONTENT_URI, new String[]{"_id", "name"}, null, null, null);StringBuilder sb = new StringBuilder();while (bookCursor.moveToNext()) {Book book = new Book(bookCursor.getInt(0),bookCursor.getString(1));sb.append(book.toString()).append("\n");}sb.append("--------------------------------").append("\n");bookCursor.close();Cursor userCursor = getContentResolver().query(BookProvider.USER_CONTENT_URI, new String[]{"_id", "name", "sex"}, null, null, null);while (userCursor.moveToNext()) {sb.append(userCursor.getInt(0)).append(userCursor.getString(1)).append(" ,").append(userCursor.getInt(2)).append(" ,").append("\n");}sb.append("--------------------------------");userCursor.close();displayTextView.setText(sb.toString());}
}

View Code

六、使用Socket

 Socaket也是实现进程间通信的一种方式,Socaket也成为“套接字”,是网络通信中的概念,通过Socaket我们可以很方便的进行网络通信,都可以实现网络通信录,那么实现跨进程通信不是也是相同的么。

Socket起源于 Unix,而 Unix 基本哲学之一就是“一切皆文件”,都可以用“打开 open –读写 write/read –关闭 close ”模式来操作。Socket 就是该模式的一个实现,网络的 Socket 数据传输是一种特殊的 I/O,Socket 也是一种文件描述符。Socket 也具有一个类似于打开文件的函数调用: Socket(),该函数返回一个整型的Socket 描述符,随后的连接建立、数据传输等操作都是通过该 Socket 实现的。

常用的 Socket 类型有两种:流式 Socket(SOCK_STREAM)和数据报式 Socket(SOCK_DGRAM)。流式是一种面向连接的 Socket,针对于面向连接的 TCP 服务应用;数据报式 Socket 是一种无连接的 Socket ,对应于无连接的 UDP 服务应用。

Socket 本身可以传输任意字节流。

谈到Socket,就必须要说一说 TCP/IP 五层网络模型:

  • 应用层:规定应用程序的数据格式,主要的协议 HTTP,FTP,WebSocket,POP3 等;
  • 传输层:建立“端口到端口” 的通信,主要的协议:TCP,UDP;
  • 网络层:建立”主机到主机”的通信,主要的协议:IP,ARP ,IP 协议的主要作用:一个是为每一台计算机分配 IP 地址,另一个是确定哪些地址在同一子网;
  • 数据链路层:确定电信号的分组方式,主要的协议:以太网协议;
  • 物理层:负责电信号的传输。

Socket 是连接应用层与传输层之间接口(API)。

只实现 TCP Socket 。

Client 端代码:

public class TCPClientActivity extends AppCompatActivity implements View.OnClickListener{private static final String TAG = "TCPClientActivity";public static final int MSG_RECEIVED = 0x10;public static final int MSG_READY = 0x11;private EditText editText;private TextView textView;private PrintWriter mPrintWriter;private Socket mClientSocket;private Button sendBtn;private StringBuilder stringBuilder;private Handler mHandler = new Handler(){@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case MSG_READY:sendBtn.setEnabled(true);break;case MSG_RECEIVED:stringBuilder.append(msg.obj).append("\n");textView.setText(stringBuilder.toString());break;default:super.handleMessage(msg);}}};@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.tcp_client_activity);editText = (EditText) findViewById(R.id.editText);textView = (TextView) findViewById(R.id.displayTextView);sendBtn = (Button) findViewById(R.id.sendBtn);sendBtn.setOnClickListener(this);sendBtn.setEnabled(false);stringBuilder = new StringBuilder();Intent intent = new Intent(TCPClientActivity.this, TCPServerService.class);startService(intent);new Thread(){@Overridepublic void run() {connectTcpServer();}}.start();}private String formatDateTime(long time) {return new SimpleDateFormat("(HH:mm:ss)").format(new Date(time));}private void connectTcpServer() {Socket socket = null;while (socket == null) {try {socket = new Socket("localhost", 8888);mClientSocket = socket;mPrintWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())), true);mHandler.sendEmptyMessage(MSG_READY);} catch (IOException e) {e.printStackTrace();}}// receive messageBufferedReader bufferedReader = null;try {bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));} catch (IOException e) {e.printStackTrace();}while (!isFinishing()) {try {String msg = bufferedReader.readLine();if (msg != null) {String time = formatDateTime(System.currentTimeMillis());String showedMsg = "server " + time + ":" + msg+ "\n";mHandler.obtainMessage(MSG_RECEIVED, showedMsg).sendToTarget();}} catch (IOException e) {e.printStackTrace();}}}@Overridepublic void onClick(View v) {if (mPrintWriter != null) {String msg = editText.getText().toString();mPrintWriter.println(msg);editText.setText("");String time = formatDateTime(System.currentTimeMillis());String showedMsg = "self " + time + ":" + msg + "\n";stringBuilder.append(showedMsg);}}@Overrideprotected void onDestroy() {if (mClientSocket != null) {try {mClientSocket.shutdownInput();mClientSocket.close();} catch (IOException e) {e.printStackTrace();}}super.onDestroy();}
}

Server端代码:

public class TCPServerService extends Service {private static final String TAG = "TCPServerService";private boolean isServiceDestroyed = false;private String[] mMessages = new String[]{"Hello! Body!","用户不在线!请稍后再联系!","请问你叫什么名字呀?","厉害了,我的哥!","Google 不需要Science上网是真的吗?","扎心了,老铁!!!"};@Overridepublic void onCreate() {new Thread(new TCPServer()).start();super.onCreate();}@Overridepublic void onDestroy() {isServiceDestroyed = true;super.onDestroy();}@Nullable@Overridepublic IBinder onBind(Intent intent) {return null;}private class TCPServer implements Runnable {@Overridepublic void run() {ServerSocket serverSocket = null;try {serverSocket = new ServerSocket(8888);} catch (IOException e) {e.printStackTrace();return;}while (!isServiceDestroyed) {// receive request from clienttry {final Socket client = serverSocket.accept();Log.d(TAG, "=============== accept ==================");new Thread(){@Overridepublic void run() {try {responseClient(client);} catch (IOException e) {e.printStackTrace();}}}.start();} catch (IOException e) {e.printStackTrace();}}}}private void responseClient(Socket client) throws IOException {//receive messageBufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));//send messagePrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(client.getOutputStream())),true);out.println("欢迎来到聊天室!");while (!isServiceDestroyed) {String str = in.readLine();Log.d(TAG, "message from client: " + str);if (str == null) {return;}Random random = new Random();int index = random.nextInt(mMessages.length);String msg = mMessages[index];out.println(msg);Log.d(TAG, "send Message: " + msg);}out.close();in.close();client.close();}
}

http://chatgpt.dhexx.cn/article/GYHNhXfy.shtml

相关文章

操作系统——进程间通信

文章目录 其他文章管道消息队列共享内存信号量信号Socket总结 个人博客网站: https://xingkongdiyiren.github.io/myblog/,完整的Java知识体系,包括408,架构,业务,产品,软技能等 其他文章 操作系统——概…

进程间的通信方式(六种)

进程之间的通信 参考文章:https://blog.csdn.net/qq_34827674/article/details/107678226 前提知识:每个进程都有自己的用户空间,而内核空间是每个进程共享的。因此进程之间想要进行通信,就需要通过内核来实现。 管道&#xff1…

【操作系统】进程间通信的五种方式

引言1.进程对白:管道、记名管道、套接字1.管道2.虫洞:套接字3.信号 4.信号旗语:信号量5.进程拥抱:共享内存 引言 进程作为人类的发明,自然免不了脱离人类的习性,也有通信需求。如果进程之间不进行任何通信…

进程之间的通信方式

进程之间的通信方式包括管道,消息队列,共享内存,信号,信号量,socket六种方式,下面来对这6种方式分别进行介绍。 一、管道 管道的结构示意图如上所示,管道包含一个输入端和一个输出端&#xff0…

进程间通信的六种常见方式

目录 进程间通信(IPC): 一、管道 二、FIFO 三、消息队列 四、共享内存 五、信号 六、信号量 七、进程间通信方式总结: 进程间通信(IPC): 进程间通信的方式有很多,这里主要…

idea数据库管理工具配置连接数据库

idea数据库管理工具配置连接数据库 —————————————————————————————————————————————————————— 在cmd中操作数据库太麻烦了,还好idea为我们提供了很方便的数据库管理工具,下面看看如何用idea连接…

idea连接数据库失败解决办法

一.IDEA连接Mysql报错: 未找到驱动程序类 ‘com.mysql.cj.jdbc.Driver‘.  Change driver class 报错详细内容:未找到驱动程序类 ‘com.mysql.cj.jdbc.Driver’. Change driver class 报错原因:Mysql版本为5.0,找不到com.mysql.…

IDEA中如何连接数据库并显示数据库信息。

我的相关博客: java代码程序中连接mysql数据库的方法及代码 mysql数据库并发上锁问题,java代码 关于IDEA中怎么连接mysql数据库 相信部分朋友在使用IDEA操作数据库的时候会出现有关数据库信息的报错。 显示没有此表,或者无数据库等错误信息…

idea连接数据库失败原因及解决方案

这是因为安装mysql的时候时区设置的不正确 mysql默认的是美国的时区,而我们中国大陆要比他们迟8小时,采用8:00格式。使用的数据库是MySQL,在你没有指定MySQL驱动版本的情况下它自动依赖的驱动是8.0.12很高的版本,这是由于数据库和…

IDEA如何连接数据库 / IDEA连接数据库 新手,图解

如果在下面操作中遇到了问题,可以查看我的这篇笔记 https://blog.csdn.net/qq_44627608/article/details/115442815 1. 打开IDEA的数据库设置 2. 新建数据库链接 左上角的便是你链接的数据库了,第一次打开时左上角应该是空的,需要你从下面的…

IDEA使用Database连接数据库

一,连接数据库 1.点击右侧Database后,点击左上角按钮,然后选中Data Source ,无论使用的是MariaDB还是MySQL都选中MySQL 2.点击非常隐蔽的 … 按钮 3.选中你需要使用的数据库 4.填写数据的账号,密码,数据库名称&…

idea代码连接mysql数据库操作

此文章仅为作者学习上的问题记录,如有错误,欢迎指正。 首先是准备工作 先创建一个Module 之后在此Module下创建一个lib包 然后将下载的连接包复制到lib包下,连接包下载地址: https://cdn.mysql.com//Downloads/Connector-J/mysq…

IDEA中配置数据库连接

1.点击IDEA右边框的 Database ,在展开的界面点击 选择 Data Source ,再选择 MySQL 2.在弹出的界面进行基本信息的填写 3.填写完后,点击Test Connection 测试一下 这样就是填写的没问题,如果是第一次点击这个,需要下载…

Idea连接MySQL数据库教程 (简单明了)

使用Idea连接数据库 具体步骤:点击右侧DataBase → 点击号 → 点击Data Source 选择MySQL → 输入用户名、密码、连接的数据库名称(连接路径会自动生成) → 可点击下面的Test Connection来测试连接 注意事项一: 第一次连接需要下…

IntelliJ IDEA配置连接MySQL数据库

如图: 1、点击主界面右侧边栏Database 2、点击""号 3、点击Data Source 4、点击MySQL 如图填写数据库名,用户名和密码,之后点击下方Test Connection测试 连接成功会显示上图字样 这时发现已经可以查看到数据库信息,说…

IDEA连接mysql数据库

1.保证mysql数据库和IDEA安装成功后,找到IDEA中mysql数据库的连接方式 按照图上的顺序 2.配置连接 在第一次使用的时候,除了要配置连接,还有配置相应的驱动,否则连接的时候会报错。 图中的①②③④⑤分别表示为: ①…

如何使用IDEA连接数据库?

一定要下载IDEA专业版 之前我一直用的是社区版,有诸多内容限制,且无直连数据库功能 通过学生认证(我是认证失败直接找某宝了)直接可以获得1年的免费试用时长 具体操作 然后输入用户、密码、数据库 这里值得一说的是&#xff0c…

Idea连接数据库并执行SQL语句

1、Idea显示Database 2、连接数据库 1、打开界面 2、配置连接信息 3、测试连接 4、面板基本信息 5、选择要显示的数据库 6、表的基本信息 7、新建查询 8、设置sql的备注名称 9、编写sql执行 10、执行结果 3、连接可能出现的问题 1、Idea显示Database idea显示Data…

IDEA连接MySQL数据库的四种方法

首先右击此电脑点击管理,进入页面 再服务栏确保MySQL是正常运行状态 打开IDEA, 左边栏选择Maven Archetype,新建一个名为javaweb的新工程 进行如图编辑完成新建 在Main包下新建一个java包,右击java包进行下图操作,java包拥有新建class的权限…

Idea 连接 MySQL 数据库

文章目录 前言配置 MySQL安装添加环境变量检查配置 MysQL服务状态开启关闭 在idea Ultimate中建立连接引入 Drivers 驱动添加表创建 schema 架构创建 Table 表 写入数据信息 测试类 前言 开始链接前,请确保本机上安装的 idea 是 Ultimate 专业版,点我下…