当前位置: 首页 > news >正文

Android IPC机制(三)进程间通信方式

在Android中有以下几种进程间通信方式:

目录

1.Bundle

2.文件共享

3.Messenger

 4.ContentProvider

5.AIDL


1.Bundle

  Bundle是Android中用于存储一组键值对的类,它实现了Parcelable接口。这使得Bundle能够在不同的进程之间传递数据。当我们通过Intent启动其他应用的组件(如ActivityServiceBroadcastReceiver)时,可以将数据存储在Bundle中,并将其附加到Intent中,从而实现跨进程通信。

下面是一个简单的示例,展示如何在MainActivity中使用Bundle将数据传递到SecondActivity

在MainActivity中:

Intent intent = new Intent(this, SecondActivity.class);
Bundle bundle = new Bundle();
bundle.putString("test", "这是Bundle跨进程通信");
intent.putExtra("test", bundle);
startActivity(intent);

在上述代码中,我们创建了一个Intent对象,用于启动SecondActivity。然后,我们创建了一个Bundle对象,并使用putString方法将一条字符串信息存储在其中。接着,我们将这个Bundle附加到Intent中,并调用startActivity方法启动目标活动。

在SecondActivity中接收数据:

SecondActivity中,我们可以通过以下代码来接收并使用传递过来的Bundle数据:

Bundle result = getIntent().getBundleExtra("test");
Log.e("data", "onCreate:" + result.getString("test"));

在这里,我们使用getIntent().getBundleExtra("test")方法获取传递过来的Bundle,然后通过getString方法提取出我们之前存储的字符串。最后,我们使用Log.e打印出接收到的数据。

运行结果:

当我们运行这个示例时,SecondActivity将会在日志中输出以下内容:

2.文件共享

       两个进程通过读/写同一个文件来交互数据,从而实现通信。但是Android上面没有对文件的并发读/写做限制,甚至两个进程同时对一个文件进行写操作都是运行的,因此有可能发送异常,所以,文件共享发送适合在对数据同步要求不高的进程间通信,并且要妥善处理并发的读/写问题。

      文件共享的实现,在我们的示例中,我们在MainActivity中序列化一个 User对象到应用的内部存储文件中。然后在SecondActivity 中去反序列化,我们期望在 SecondActivity中能够正确地恢复 User 对象的值。关键代码如下:

在MainActivity中序列化到文件:

 private void persistToFile(){try{User user=new User(1,"hello word",false);File file=new File(getExternalCacheDir(),"share_data.txt");if(!file.exists()){file.createNewFile();}FileOutputStream fos = new FileOutputStream(file);ObjectOutputStream objectOutputStream=new ObjectOutputStream(fos);objectOutputStream.writeObject(user);Log.e("xxx","persist user:"+user);fos.close();objectOutputStream.close();} catch (IOException e) {e.printStackTrace();}}

在 SecondActivity中从文件恢复:

 private void recoverFromFile(){try {User user = null;File file = new File(getExternalCacheDir(), "share_data.txt");if (!file.exists()) {file.createNewFile(); }ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(file));user = (User) objectInputStream.readObject();Log.e("xxx", "recover user:" + user);objectInputStream.close();} catch (Exception e) {e.printStackTrace();}}

下面看一下log,很显然,在SecondActivity 中成功地从文件从恢复了之前存储的 User对象的内容,这里之所以说内容,是因为反序列化得到的对象只是在内容上和序列化之前的对象是一样的,但它们本质上还是两个对象。

SharedPreferences的局限性:

我们知道SharedPerference是存储xml文件里面,从本质上说也是文件的一种,但是由于系统对它的读写有一定的缓存策略,即在内存中会有一份SharedPerference文件的缓存,因此多进程模式下,系统对它的读/写就变得不可靠,当面对高并发的读/写访问时,SharedPerference有很大概率会丢失数据,因此不建议进程间的通信使用SharedPerference。

3.Messenger

        Messenger 可以翻译为信使,通过它可以在不同进程中传递 Message 对象,在Message中放入我们需要传递的数据,就可以轻松地实现数据的进程间传递了。

为什么说Messenger的底层是基于AIDL实现?

我们大致看一下Messenger 这个类的构造方法就明白了。下面是Messenger 的两个构造方法,从构造方法的实现上我们可以明显看出AIDL的痕迹,不管是IMessenger还是 Stub.asInterface,这种使用方法都表明它的底层是 AIDL。

    public Messenger(Handler target) {mTarget = target.getIMessenger();}public Messenger(IBinder target) {mTarget = IMessenger.Stub.asInterface(target);}

既然Messenger是基于AIDL实现的,那么它与AIDL有什么区别呢?

  • Messenger是通过Bundle传递数据,多以只能传递基本数据类型,而AIDL可以传递实体类,其实Bundler也可以传递序列化之后的实体类。
  • Messenger适合传递数据少,数据量少的情况,AIDL适合数据多,数据量大的情况。
  • Messenger返回值是异步的,AIDL是同步的。

下面是一个简单的示例,展示如何在Android应用中使用Messenger进行进程间通信。

客户端代码(MainActivity)

public class MainActivity extends AppCompatActivity {private Handler mHandler=new Handler(Looper.getMainLooper()){@Overridepublic void handleMessage(@NonNull Message msg) {super.handleMessage(msg);if(msg.what==11) {Bundle data = msg.getData();String test = data.getString("test");Log.d("xxx", "handleMessage:" + test);}}};private final  Messenger mMessager=new Messenger(mHandler); // 用于与服务通信的Messenger@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);Intent intent=new Intent(this,MessengerService.class);// 创建服务的IntentbindService(intent,mServiceConnection, Context.BIND_AUTO_CREATE);// 绑定服务}private final ServiceConnection mServiceConnection=new ServiceConnection(){@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {Messenger messenger=new Messenger(service);// 获取服务的MessengerMessage message= Message.obtain();// 创建消息message.what=1;Bundle bundle=new Bundle();bundle.putString("test","Messenger传递数据");message.setData(bundle);message.replyTo=mMessager;// 设置回复的Messengertry {messenger.send(message);// 发送消息到服务} catch (RemoteException e) {e.printStackTrace();}}@Overridepublic void onServiceDisconnected(ComponentName name) {}};@Overrideprotected void onDestroy() {super.onDestroy();if(mServiceConnection!=null){unbindService(mServiceConnection);// 解绑服务}}
}

 服务端代码(MessengerService)

public class MessengerService extends Service {private Handler mHandler=new Handler(Looper.getMainLooper()){@Overridepublic void handleMessage(@NonNull Message msg) {super.handleMessage(msg);if(msg.what==1) {Bundle data = msg.getData();String test = data.getString("test");Log.d("xxx", "handleMessage:" + test);Messenger replyTo=msg.replyTo;Message message=new Message();message.what=11;Bundle bundle=new Bundle();bundle.putString("test","这是服务器回传数据到客户端");message.setData(bundle);message.replyTo=mMessenger;try {replyTo.send(message);} catch (RemoteException e) {e.printStackTrace();}}}};private Messenger mMessenger=new Messenger(mHandler);@Nullable@Overridepublic IBinder onBind(Intent intent) {return mMessenger.getBinder();}
}

AndroidManifest.xml配置

在AndroidManifest.xml中,需要声明服务并指定其运行在单独的进程中:

<service android:name=".MessengerService" android:process=":test"/>

运行结果: 服务端收到了客户端发送的信息,而客户端也收到了服务端的回复。

 4.ContentProvider

ContentProvider是 Android 中提供的专门用于不同应用间进行数据共享的方式,虽然ContentProvider的底层实现是 Binder,但是它的使用过程要比 AIDL简单许多,这是因为系统已经为我们做了封装,使得我们无须关注底层细节即可轻松实现IPC。

实现一个 ContentProvider 涉及几个步骤,包括创建类、定义数据模型、实现必要的方法以及在 AndroidManifest.xml 中注册。以下是一个简单的示例,展示如何实现一个 ContentProvider

1. 创建数据模型

首先,我们需要定义一个数据模型。假设我们要管理一个简单的联系人信息,包括姓名和电话号码。

public class Contact {public static final String TABLE_NAME = "contacts";public static final String COLUMN_ID = "_id";public static final String COLUMN_NAME = "name";public static final String COLUMN_PHONE = "phone";
}

2. 创建 ContentProvider 类

接下来,我们创建一个继承自 ContentProvider 的类,并实现必要的方法。

import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;public class ContactProvider extends ContentProvider {private static final String AUTHORITY = "com.example.app.provider";private static final String BASE_PATH = "contacts";public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + BASE_PATH);private static final int CONTACTS = 1;private static final int CONTACT_ID = 2;private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);static {uriMatcher.addURI(AUTHORITY, BASE_PATH, CONTACTS);uriMatcher.addURI(AUTHORITY, BASE_PATH + "/#", CONTACT_ID);}private SQLiteDatabase database;@Overridepublic boolean onCreate() {// 初始化数据库DatabaseHelper dbHelper = new DatabaseHelper(getContext());database = dbHelper.getWritableDatabase();return database != null;}@Overridepublic Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {Cursor cursor;switch (uriMatcher.match(uri)) {case CONTACTS:cursor = database.query(Contact.TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder);break;case CONTACT_ID:selection = Contact.COLUMN_ID + " = ?";selectionArgs = new String[]{uri.getLastPathSegment()};cursor = database.query(Contact.TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder);break;default:throw new IllegalArgumentException("Unknown URI: " + uri);}cursor.setNotificationUri(getContext().getContentResolver(), uri);return cursor;}@Overridepublic Uri insert(Uri uri, ContentValues values) {long id = database.insert(Contact.TABLE_NAME, null, values);getContext().getContentResolver().notifyChange(uri, null);return Uri.parse(BASE_PATH + "/" + id);}@Overridepublic int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {int rowsUpdated;switch (uriMatcher.match(uri)) {case CONTACTS:rowsUpdated = database.update(Contact.TABLE_NAME, values, selection, selectionArgs);break;case CONTACT_ID:selection = Contact.COLUMN_ID + " = ?";selectionArgs = new String[]{uri.getLastPathSegment()};rowsUpdated = database.update(Contact.TABLE_NAME, values, selection, selectionArgs);break;default:throw new IllegalArgumentException("Unknown URI: " + uri);}if (rowsUpdated != 0) {getContext().getContentResolver().notifyChange(uri, null);}return rowsUpdated;}@Overridepublic int delete(Uri uri, String selection, String[] selectionArgs) {int rowsDeleted;switch (uriMatcher.match(uri)) {case CONTACTS:rowsDeleted = database.delete(Contact.TABLE_NAME, selection, selectionArgs);break;case CONTACT_ID:selection = Contact.COLUMN_ID + " = ?";selectionArgs = new String[]{uri.getLastPathSegment()};rowsDeleted = database.delete(Contact.TABLE_NAME, selection, selectionArgs);break;default:throw new IllegalArgumentException("Unknown URI: " + uri);}if (rowsDeleted != 0) {getContext().getContentResolver().notifyChange(uri, null);}return rowsDeleted;}@Overridepublic String getType(Uri uri) {switch (uriMatcher.match(uri)) {case CONTACTS:return "vnd.android.cursor.dir/" + AUTHORITY + "." + BASE_PATH;case CONTACT_ID:return "vnd.android.cursor.item/" + AUTHORITY + "." + BASE_PATH;default:throw new IllegalArgumentException("Unknown URI: " + uri);}}private static class DatabaseHelper extends SQLiteOpenHelper {private static final String DATABASE_NAME = "contacts.db";private static final int DATABASE_VERSION = 1;public DatabaseHelper(Context context) {super(context, DATABASE_NAME, null, DATABASE_VERSION);}@Overridepublic void onCreate(SQLiteDatabase db) {String createTable = "CREATE TABLE " + Contact.TABLE_NAME + " (" +Contact.COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +Contact.COLUMN_NAME + " TEXT, " +Contact.COLUMN_PHONE + " TEXT" + ");";db.execSQL(createTable);}@Overridepublic void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {db.execSQL("DROP TABLE IF EXISTS " + Contact.TABLE_NAME);onCreate(db);}}
}

3. 在 AndroidManifest.xml 中注册 ContentProvider

在 AndroidManifest.xml 文件中注册 ContentProvider,并设置权限(如果需要)。

<providerandroid:name=".ContactProvider"android:authorities="com.example.app.provider"android:exported="true" />

4. 使用 ContentProvider

其他应用程序可以通过 ContentResolver 来访问 ContentProvider 提供的数据。例如,查询联系人数据:

Cursor cursor = getContentResolver().query(ContactProvider.CONTENT_URI, null, null, null, null);
if (cursor != null) {while (cursor.moveToNext()) {String name = cursor.getString(cursor.getColumnIndex(Contact.COLUMN_NAME));String phone = cursor.getString(cursor.getColumnIndex(Contact.COLUMN_PHONE));// 处理数据}cursor.close();
}

5.AIDL

     在Android平台中,每个应用程序都运行在自己的独立进程中,并且可以启动另一个应用进程的服务,而且经常需要在不同的进程间传递数据对象。但是一个进程不能直接访问另一个进程的内存空间,所以要想对话,需要将对象分解成操作系统可以理解的基本单元,并且有序的进行进程边界。而AIDL就用于生成可以在Android设备上两个进程之间进行进程间通信的代码。

在AIDL文件中,并不是所有的数据类型都是可以使用的,那么到底AIDL文件支持哪些数据类型呢?如下所示。

  • 基本数据类型(int、long、char、boolean、double等);
  • String 和 CharSequence;
  • List:只支持ArrayList,里面每个元素都必须能够被 AIDL支持;
  • Map:只支持 HashMap,里面的每个元素都必须被 AIDL支持,包括key和 value;
  • Parcelable:所有实现了Parcelable接口的对象:
  • AIDL:所有的 AIDL接口本身也可以在 AIDL 文件中使用。

参数定向TAG:

AIDL除了基本数据类型,其他类型的参数都需要一个定向tag来指出数据的流向,不管是in,out,还是inout,基于参数的定向tag默认是并且只能是in。

  • in:表示数据只能由客户端流向服务端
  • out:表示数据只能由服务端流向客户端
  • inout:表示数据可在服务端与客户端之间双向流通

以下是AIDL的基本实现步骤和示例:

1. 创建AIDL文件

首先,在你的Android项目中创建一个AIDL文件。通常,这个文件会放在 src/main/aidl 目录下。

例如,创建一个名为 MyAidl.aidl 的文件,内容如下:

// MyAidl.aidl
package com.ex.binder1;interface MyAidl {void add();
}

2. 编写服务端实现

在服务端,你需要实现这个接口。创建一个服务类,继承 Service,并实现AIDL接口。


public class AidlService extends Service {private final Binder mbinder=new MyAidl.Stub(){@Overridepublic void add() throws RemoteException {Log.e("xxx","服务端add被调用");}};@Nullable@Overridepublic IBinder onBind(Intent intent) {return mbinder;}
}

3. 在AndroidManifest.xml中注册服务

在 AndroidManifest.xml 中注册你的服务:

  <serviceandroid:name=".aidl.AidlService"android:process=":aidltest" />

4. 客户端调用

在客户端,你需要绑定到服务并调用AIDL接口。以下是一个简单的示例:

public class AidlActivity extends AppCompatActivity {private ServiceConnection mServiceConnection=new ServiceConnection(){@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {MyAidl myAidl= MyAidl.Stub.asInterface(service);try {myAidl.add();} catch (RemoteException e) {e.printStackTrace();}}@Overridepublic void onServiceDisconnected(ComponentName name) {}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_aidl);Intent intent=new Intent(this,AidlService.class);bindService(intent,mServiceConnection, Context.BIND_AUTO_CREATE);}
}

运行结果:


 


http://www.mrgr.cn/news/64929.html

相关文章:

  • GitHub | 发布到GitHub仓库并联文件夹的方式
  • 【计网不挂科】计算机网络期末考试中常见【选择题&填空题&判断题&简述题】题库(3)
  • Docker部署Portainer CE结合内网穿透实现容器的可视化管理与远程访问
  • Elasticsearch 实战应用详解!
  • 嵌入式linux跨平台基于mongoose的TCP C++类的源码
  • 深入探讨 Jenkins 中 HTML 格式无法正常显示的现象及解决方案
  • CentOS8.5.2111(6)冬日阳光下跳一曲桑巴--SAMBA共享存储
  • 免费送源码:Java+springboot+MySQL springboot 线上线下一体化的宠物交易 计算机毕业设计原创定制
  • 关于Mac打包ipa的配置小结
  • 算法中使用的数据结构解释*
  • Ubuntu18.04服务器非root用户在虚拟环境下的python版本设定
  • [SICTF Round4] PWN
  • 【算法】选择排序
  • 基于STM32的农业监测与管理系统设计思路介绍(代码示例)
  • 程序员的减压秘籍:高效与健康的平衡艺术
  • Docker篇(registry私服)
  • C++ 优先算法 —— 查找总价格为目标值的两个商品(双指针)
  • 代码随想录day15| 110.平衡二叉树 、 257. 二叉树的所有路径 、 404.左叶子之和、 222.完全二叉树的节点个数
  • dockerfile/docker-compose构建镜像上下文目录编写要点
  • 进程间通信小练习
  • 电子电气架构 --- Trace 32(劳特巴赫)多核系统的调试
  • 第1篇 引言
  • vscode ssh+clion+idea等本周小结-2024.11.3
  • 阿里巴巴Seata分布式事务解决方案
  • ubuntu20安装opencv3.2记录
  • 【linux指令】----如何创建一个子进程