1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > Android 进程间通信方式和线程间通信方式

Android 进程间通信方式和线程间通信方式

时间:2023-03-17 11:56:36

相关推荐

Android 进程间通信方式和线程间通信方式

1.进程和线程

进程:是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。

线程:是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源,只拥有一些在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。

区别:

 ①一个程序至少有一个进程,一个进程至少有一个线程;

 ②线程的划分尺度小于进程,使得多线程程序的并发性高;

 ③进程在执行过程中拥有独立的内存单元,而多个线程共享内存,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉。

2.进程

当一个应用开始运行时,系统会为它创建一个进程,一个应用默认只有一个进程,这个进程(主进程)的名称就是应用的包名。

首先,进程一般指一个执行单元,在移动设备上就是一个程序或应用,我们在Android中所说的多进程(IPC)一般指一个应用包含多个进程。之所以要使用多进程有两方面原因:某些模块由于特殊的需求要运行在单独的进程;增加应用可用的内存空间。

进程的特点:

①进程是系统资源和分配的基本单位,而线程是调度的基本单位。

②每个进程都有自己独立的资源和内存空间

③其它进程不能任意访问当前进程的内存和资源④系统给每个进程分配的内存会有限制。

根据以上特点可以看出,使用多进程的场景为:需要使apk所使用的内存限制扩大。

进程的等级:

进程按优先级可以分为五类,优先级从高到低排列:

①前台进程:该进程包含正在与用户进行交互的界面组件,比如一个Activity。在接收关键生命周期方法时会让一个进程临时提升为前台进程,包括任何服务的生命周期方法onCreate()和onDestroy()和任何广播接收器onReceive()方法。这样做确保了这些组件的操作是有效的原子操作,每个组件都能执行完成而不被杀掉。

②可见进程:该进程中的组件虽然没有和用户交互,但是仍然可以被看到。activity可见的时候不一定在前台。一个简单的例子是前台的 activity 使用对话框启动了一个新的 activity 或者一个透明 activity 。另一个例子是当调用运行时权限对话框时(事实上它就是一个 activity!)。

③服务进程:该进程包含在执行后台操作的服务组件,比如播放音乐的Service。对于许多在后台做处理(如加载数据)而没有立即成为前台服务的应用都属于这种情况。

请特别注意从onStartCommand()返回的常量,如果服务由于内存压力被杀掉,它表示控制什么发生什么:

START_STICKY表示希望系统可用的时候自动重启服务,但不关心是否能获得最后一次的 Intent (例如,可以重建自己的状态或者控制自己的 start/stop 生命周期)。

START_REDELIVER_INTENT是为那些在被杀死之后重启时重新获得 Intent 的服务的,直到用传递给 onStartCommand() 方法的 startId 参数调用stopSelf()为止。这里会使用 Intent 和 startId 作为队列完成工作。

START_NOT_STICKY用于那些杀掉也没关系的服务。这适合那些管理周期性任务的服务,它们只是等待下一个时间窗口工作。

④后台进程:该进程包含的组件没有与用户交互,用户也看不到 Service。在一般操作场景下,设备上的许多内存就是用在这上面的,使可以重新回到之前打开过的某个 activity 。

⑤空进程:没有任何界面组件、服务组件,或触发器组件,只是出于缓存的目的而被保留(为了更加有效地使用内存而不是完全释放掉),只要 Android 需要可以随时杀掉它们。

多进程的创建:

Android开启多进程只有一种方法,就是在AndroidManifest.xml中注册Service、Activity、Receiber、ContentProvider时指定"android:process”属性即可。命名之后,就成了一个单独的进程。例如:

< service

android:name=".MyService"

android:process=":remote">

< /service>

< activity

android:name=".MyActivity"

android:process=“com.shh.ipctest.remote2”>

< /activity>

process分私有进程和全局进程:

①私有进程的名称前面有冒号,例如:

< service android:name=".MusicService"

android:process=":musicservice"/>

以:开头是一种简写,系统会在当前进程名前附上当前包名,完整的进程名为:com.shh.ipctest:musicservice。

以:开头的进程属于当前应用的私有进程,其它应用的组件不能和它跑在同一进程。

com.shh.ipctest.remote2:这是完整的命名方式,不会附加包名,其它应用如果和该进程的ShareUID、签名相同,则可以和它跑在同一个进程,实现数据共享。

②全局进程的名称前面没有冒号,例如:

< service android:name=".MusicService" android:process=“com.trampcr.musicdemo.service”/>

com.trample.music demo.service这是完整的命名方式,不会附加包名,其它应用如果和该进程的ShareUID、签名相同,则可以和它跑在同一个进程,实现数据共享。

多进程引发的问题:

开启多进程虽简单,但会引发如下问题,必须引起注意。

①静态成员和单例模式失效

②线程同步机制失效

③SharedPreferences 可靠性降低

④Application 被多次创建

对于前两个问题,可以这么理解,在Android中,系统会为每个应用或进程分配独立的虚拟机,不同的虚拟机自然占有不同的内存地址空间,所以同一个类的对象会产生不同的副本,导致共享数据失败,必然也不能实现线程的同步。

由于SharedPreferences底层采用读写XML的文件的方式实现,多进程并发的的读写很可能导致数据异常。

Application被多次创建和前两个问题类似,系统在分配多个虚拟机时相当于把同一个应用重新启动多次,必然会导致Application多次被创建,为了防止在 Application中出现无用的重复初始化,可使用进程名来做过滤,只让指定进程的才进行全局初始:

public class MyApplication extends Application{

@Override

public void onCreate() {

super.onCreate();

String processName = “com.shh.ipctest”;

if(getPackageName().equals( processName)){

// do some init

}

为了节省系统内存,在退出该Activity的时候可以将其杀掉(如果没有人为杀掉该进程,在程序完全退出时该进程会被系统杀掉)

3.进程间通信方式

同一个进程的多个线程是共享该进程的所有资源,但多个进程间内存是不可见的,也就是说多个进程间内存是不共享的。为了在不同应用程序之间交互数据(跨进程通讯),在android SDK中提供了4种用于跨进程通讯的方式。这4种方式正好对应于android系统中4种应用程序组件:Activity、Content Provider、Broadcast和Service。其中Activity可以跨进程调用其他应用程序的Activity;Content Provider可以跨进程访问其他应用程序中的数据(以Cursor对象形式返回),当然,也可以对其他应用程序的数据进行增、删、改操 作;Broadcast可以向android系统中所有应用程序发送广播,而需要跨进程通讯的应用程序可以监听这些广播;Service和Content Provider类似,也可以访问其他应用程序中的数据,但不同的是,Content Provider返回的是Cursor对象,而Service返回的是Java对象,这种可以跨进程通讯的服务叫AIDL服务。

①Intent(Bundle) 

由于Activity,Service,Receiver都是可以通过Intent来携带Bundle传输数据的,所以我们可以在一个进程中通过Intent将携带数据的Bundle发送到另一个进程的组件。bundle支持传输的类型很多,如基本类型、实现了Parcelable或Serializable接口的对象。

缺点:无法传输Bundle不支持的数据类型。

Activity的跨进程访问与进程内访问略有不同。虽然它们都需要Intent对象,但跨进程访问并不需要指定Context对象和Activity的Class对象,而需要指定的是要访问的Activity所对应的Action(activity隐式调用)。有些Activity还需要指定一个Uri(通过 Intent构造方法的第2个参数指定)。

在android系统中有很多应用程序提供了可以跨进程访问的Activity,例如,下面的代码可以直接调用拨打电话的Activity。

Intent callIntent = new Intent(Intent.ACTION_CALL, Uri.parse(“tel:1234” );

startActivity(callIntent);

②ContentProvider

ContentProvider是Android四大组件之一,以表格的方式来储存数据,提供给外界,即Content Provider可以跨进程访问其他应用程序中的数据。

用法是继承ContentProvider,实现onCreate,query,update,insert,delete和getType方法,onCreate是负责创建时做一些初始化的工作,增删查改的方法就是对数据的查询和修改,getType是返回一个String,表示Uri请求的类型。注册完后就可以使用ContentResolver去请求指定的Uri。

③文件

两个进程可以到同一个文件去交换数据,我们不仅可以保存文本文件,还可以将对象持久化到文件,从另一个文件恢复。要注意的是,当并发读/写时可能会出现并发的问题。

④广播Broadcast

Broadcast可以向android系统中所有应用程序发送广播,而需要跨进程通讯的应用程序可以监听这些广播。

⑤AIDL方式

Service和Content Provider类似,也可以访问其他应用程序中的数据,Content Provider返回的是Cursor对象,而Service返回的是Java对象,这种可以跨进程通讯的服务叫AIDL服务。

AIDL通过定义服务端暴露的接口,以提供给客户端来调用,AIDL使服务器可以并行处理,而Messenger封装了AIDL之后只能串行运行,所以Messenger一般用作消息传递。

⑥Messenger

Messenger是基于AIDL实现的,服务端(被动方)提供一个Service来处理客户端(主动方)连接,维护一个Handler来创建Messenger,在onBind时返回Messenger的binder。双方用Messenger来发送数据,用Handler来处理数据。Messenger处理数据依靠Handler,所以是串行的,也就是说,Handler接到多个message时,就要排队依次处理。

⑦Socket

Socket方法是通过网络来进行数据交换,注意的是要在子线程请求,不然会堵塞主线程。客户端和服务端建立连接之后即可不断传输数据,比较适合实时的数据传输。

3.线程间通信方式

线程间通信包括主线程(也叫UI线程)和子线程之间的通信、子线程之间的通信两种。下面分别介绍。

主线程与子线程之间的通信方式:

①AsyncTask机制

AsyncTask,异步任务,也就是说在UI线程运行的时候,可以在后台执行一些异步的操作;AsyncTask可以很容易且正确地使用UI线程,AsyncTask允许进行后台操作,并在不显示使用工作线程或Handler机制的情况下,将结果反馈给UI线程。但是AsyncTask只能用于短时间的操作(最多几秒就应该结束的操作),如果需要长时间运行在后台,就不适合使用AsyncTask了,只能去使用Java提供的其他API来实现。

②Handler机制

Handler,继承自Object类,用来发送和处理Message对象或Runnable对象。

Handler在创建时会与当前所在的线程的Looper对象相关联(如果当前线程的Looper为空或不存在,则会抛出异常,此时需要在线程中主动调用Looper.prepare()来创建一个Looper对象)。

使用Handler的主要作用就是在后面的过程中发送和处理Message对象和让其他的线程完成某一个动作(如在工作线程中通过Handler对象发送一个Message对象,让UI线程进行UI的更新,然后UI线程就会在MessageQueue中得到这个Message对象(取出Message对象是由其相关联的Looper对象完成的),并作出相应的响应)。

子线程之间的通信方式:

主线程和子线程之间的通信可以通过主线程中的handler把子线程中的message发给主线程中的looper,或者,主线程中的handler通过post向looper中发送一个runnable。但looper默认存在于main线程中,子线程中没有Looper,该怎么办呢?其实原理很简单,把looper绑定到子线程中,并且创建一个handler。在另一个线程中通过这个handler发送消息,就可以实现子线程之间的通信了。

子线程创建handler的两种方式:

方式一:给子线程创建Looper对象:

new Thread(new Runnable() {

public void run() {

Looper.prepare(); // 给这个Thread创建Looper对象,一个Thead只有一个Looper对象

Handler handler = new Handler(){

@Override

public void handleMessage(Message msg) {

Toast.makeText(getApplicationContext(), “handleMessage”, Toast.LENGTH_LONG).show();

}

};

handler.sendEmptyMessage(1);

Looper.loop(); // 不断遍历MessageQueue中是否有消息

};

}).start();

方式二:获取主线程的looper,或者说是UI线程的looper:

new Thread(new Runnable() {

public void run() {

Handler handler = new Handler(Looper.getMainLooper()){ // 区别在这!!!

@Override

public void handleMessage(Message msg) {

Toast.makeText(getApplicationContext(), “handleMessage”, Toast.LENGTH_LONG).show();

}

};

handler.sendEmptyMessage(1);

};

}).start();

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。