UU跑腿跑男端的技术支撑是我司重要的业务之一,而且跑男端的业务特殊,不同于普通APP,是跑男师傅们跑单的重要工具,需要在跑男的手机上长时间运行,而且集成了众多第三方服务,软件的稳定性和健壮性显得尤为重要,所以我们也在不断的研究怎么提升跑男端APP的性能,多进程便是我们的方法之一。
Android APP 的多进程是 Android 系统中的一个重要特性,它可以为开发者提供一种可以同时运行多个进程的解决方案。本文将详细介绍 Android APP 的多进程,包括为什么要使用多进程,多进程的好处,以及如何使用多进程。
什么是Android的多进程?
在 Android 系统中,每个 APP 都运行在自己的进程中,即使是同一个 APP 的不同组件(如 Activity、Service、BroadcastReceiver 等)也可能运行在不同的进程中。多进程就是指一个 APP 中可以同时运行多个进程,每个进程都独立运行,拥有自己的虚拟机、内存空间、线程等资源。
为什么要使用多进程,带来的好处是什么?
使用多进程可以解决一些特殊的问题,例如:
(1)内存优化:当 APP 运行时,系统会为其分配一定的内存空间。在单进程应用中,如果内存占用过大,会导致应用崩溃。通过将 APP 拆分成多个进程,可以使每个进程的内存占用更小,从而降低 APP 崩溃的概率。多进程应用可以更好地利用系统资源,从而提升应用的稳定性和性能。
(2)并行处理:多进程可以同时处理多个任务,提高了 APP 的性能和响应速度。
(3)保障进程的稳定性:当一个进程发生崩溃时,其他进程不会受到影响,从而保障了整个 APP 的稳定性。
(4)提高应用的安全性:通过将一些重要的任务放在独立的进程中运行,可以避免其他进程的干扰,从而提高应用的安全性。
(5)实现应用的模块化:多进程应用可以将不同的模块运行在不同的进程中,从而实现应用的模块化。这样可以降低模块之间的耦合度,提高代码的可维护性和可扩展性
例:微信的小程序就是在一个独立的进程中运行(见下图),这样做的原因是小程序是由第三方开发者开发,开发者水平参差不齐,代码质量无法保证,可能在运行中出现崩溃现象,而且微信同时支持打开多个小程序,独立进程带来的好处是微信的核心功能不受小程序的影响,且小程序多开能带来更好的用户体验。
Android如何进行多进程间的通信?
方式 | 优点 | 缺点 | 适用场景 |
Bundle | 使用简单,可以传递多个不同类型的数据 | 数据传输受限于 Intent,无法传输大量数据和复杂数据结构 | 四大组件间的进程通信 |
File | 使用简单 | 不支持并发,无法及时通信 | 无并发访问,且无实时通信要求 |
AIDL | 支持一对多并发通信,支持实时通信,可以传递大量数据和复杂数据结构 | 使用复杂,需要编写复杂的代码和接口定义文件 | 一对多,且有RPC需求 |
Contentprivider | 支持一对多并发数据共享,可以实现数据共享,避免数据冲突和竞争 | 受约束的AIDL,主要提供数据源的CRUD操作,无法实现同步通信和实时通信 | 一对多的进程间数据共享 |
Socket | 通过网络传输字节流,支持一对多并发实时通信 | 实现细节繁琐,不支持直接的RPC | 网络数据交互 |
使用AIDL实现进程间通信
在uu跑腿跑男端,选用的是通过AIDL实现进程间的交互。
AIDL 是 Android Interface Definition Language 的缩写,意思是Android接口定义语言,用于让某个Service与多个应用程序组件之间进行跨进程通信,从而可以实现多个应用程序共享同一个Service的功能。
其使用可以简单的概括为服务端和客户端,类似于Socket 一样,服务端服务于所有客户端,支持一对多服务。
AIDL支持的数据类型
- 基本数据类型
- String和CharSequence
- List 接口(会自动将List接口转为 ArrayList),且集合的每个元素都必须能够被 AIDL 支持
- Map 接口(会自动将 Map 接口转为 HashMap),且每个元素的 key 和 value 都必须被 AIDL 支持
- Parcelable 的实现类
- AIDL 接口本身
AIDL的定义与实现
服务端
右键点击新建一个AIDL文件,命名为 Book.aidl
则会在项目的 app/src/main/aidl 自动生成一个和项目package name 一致的包名,并自动创建了 Book.aidl 文件,也可以对路径进行调整。
例如:将Book.aidl放入 .bean
路径下:
此时再在src/main/java的目录下定义一个Book.java
类,
注意:Book.java 的路径名要和 Book.aidl 一致。
package server_demo.com.myapplication.bean; import android.os.Parcel; import android.os.Parcelable; public class Book implements Parcelable { private String name; public Book(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(this.name); } public void readFromParcel(Parcel dest) { name = dest.readString(); } protected Book(Parcel in) { this.name = in.readString(); } public static final Parcelable.Creator<Book> CREATOR = new Parcelable.Creator<Book>() { @Override public Book createFromParcel(Parcel source) { return new Book(source); } @Override public Book[] newArray(int size) { return new Book[size]; } }; }
再来修改 Book.aidl 文件,将之改为声明Parcelable数据类型的AIDL文件:
然后再定义一个 IBookManager.aidl 文件,定义需要的方法:
创建 AIDL 文件后需要重新编译下项目,以便 AS 生成需要的文件。
现在需要来创建一个 Service 供客户端远程绑定了
package server_demo.com.myapplication; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import server_demo.com.myapplication.bean.Book; public class BookManagerRemoteService extends Service { private List<Book> bookList; @Override public IBinder onBind(Intent intent) { // int i = checkCallingOrSelfPermission("定义的权限"); // // if (i == PackageManager.PERMISSION_DENIED) { // return null; // } return new IBookManager.Stub() { @Override public void inSaveBook(Book book) throws RemoteException { Log.e("========server","inSaveBook==="+book); bookList.add(book); } @Override public void outSaveBook(Book book) throws RemoteException { Log.e("========server","outSaveBook==="+book); } @Override public void inOutSaveBook(Book book) throws RemoteException { Log.e("========server","inOutSaveBook==="+book); } @Override public List<Book> getAllBook() throws RemoteException { Log.e("========server","getAllBook==="); return bookList; } }; } @Override public void onCreate() { super.onCreate(); bookList = new CopyOnWriteArrayList<>(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { return super.onStartCommand(intent, flags, startId); } }
最后,服务端还有一个地方需要注意,因为服务端的Service需要被客户端来远程绑定,所以客户端要能够找到这个Service,可以通过先指定包名,之后再配置Action值或者直接指定Service类名的方式来绑定Service
如果是通过指定Action值的方式来绑定Service,那还需要将Service的声明改为如下所示:
<service android:name=".BookManagerRemoteService" > <intent-filter> <action android:name="server_demo.com.myapplication.BookManagerRemoteService.ACTION" /> </intent-filter> </service>
客户端
如果客户端和服务端同属于一个App,则是不需要再进行 AIDL 的拷贝的。
如果客户端和服务端属于不同的APP,则需要将服务端的 AIDL 文件拷贝到客户端,且路径要与服务端保持一致。
客户端连接服务端
之后,需要创建和服务端Book类所在的相同包名来存放 Book类:
在需要的地方,客户端可以对服务端进行绑定服务
Intent intent = new Intent(); //服务端service所在的包名 intent.setPackage("server_demo.com.myapplication"); //服务端service配置的Action intent.setAction("server_demo.com.myapplication.BookManagerRemoteService.ACTION"); bindService(intent, new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { Log.e("========client", "onServiceConnected==="); //得到 IBookManager 对象 iBookManager=IBookManager.Stub.asInterface(service); } @Override public void onServiceDisconnected(ComponentName name) { Log.e("========client", "onServiceDisconnected===" + name); } @Override public void onBindingDied(ComponentName name) { Log.e("========client", "onBindingDied===" + name); } @Override public void onNullBinding(ComponentName name) { Log.e("========client", "onNullBinding===" + name); } }, Context.BIND_AUTO_CREATE);
绑定成功后即可利用 IBookManager 对象进行相关方法的调用。
try { iBookManager.inSaveBook(Book("书名")); } catch (RemoteException e) { e.printStackTrace(); } //或者 try { List<Book> allBook = iBookManager.getAllBook(); Log.e("========client", "getAllBook===" + allBook.toString()); } catch (RemoteException e) { e.printStackTrace(); }
到此为止,客户端和服务端之间的通信已经实现了,客户端获取到了服务端的数据,也向服务端传送了数据。
当然,AIDL还有更高级的用法,这里就不再进一步讲解了,感兴趣的可以自行学习。
多进程的注意事项
需要注意的是,使用多进程也会带来一些问题,
- 例如静态成员和单例模式失效等问题,需要开发者在设计应用时注意避免这些问题的出现。
- 通过多进程可以分担应用内主进程的压力,但最好的解决方案还是要做好性能优化。
- 多进程会增加系统的负担,可能会导致更多的内存占用和更慢的响应速度,因此必须慎重使用多进程。
多进程在跑男端的应用
在UU跑男端,因为APP要长时间的运行,为了APP的稳定健壮,已经将百度语音播报、订单简报、webview,实名认证等多个模块变为独立进程运行,即使独立进程的模块内出现了崩溃也不会影响核心的接单与送单,最大程度的提升跑男的使用体验。后续还将继续钻研多进程的使用,发挥多进程更大的作用。