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,实名认证等多个模块变为独立进程运行,即使独立进程的模块内出现了崩溃也不会影响核心的接单与送单,最大程度的提升跑男的使用体验。后续还将继续钻研多进程的使用,发挥多进程更大的作用。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注