Android 进程相关——学习笔记

一:多进程的作用

1:多进程主要用于实现应用的多模块化,使一个应用分解为多个子项模块,方便控制与使用。
2:多进程还可以避免OOM的问题,由于内存调用是针对进程单位的,进程是系统资源调度的基本单位,所以采用多进程可以实现不同模块的不同资源分配,并且能合理地运用内存,在不需要的时候杀掉进程,在需要的时候再开启。
3:多进程还能带来一个好处就是,单一进程崩溃并不影响整体应用的使用。例如我在图片浏览进程打开了一个过大的图片,java heap 申请内存失败,但是不影响我主进程的使用,而且,还能通过监控进程,将这个错误上报给系统,告知他在什么机型、环境下、产生了什么样的Bug,提升用户体验。
4:再一个好处就是,当我们的应用开发越来越大,模块越来越多,团队规模也越来越大,协作开发也是个很麻烦的事情。项目解耦,模块化,是这阶段的目标。通过模块解耦,开辟新的进程,独立的JVM,来达到数据解耦目的。模块之间互不干预,团队并行开发,责任分工也明确。

二:开启进程:Android:Process

Android :Process属性:
这个进程的名字就是正在运行的服务所在的进程,通常来说,所有组件和应用在默认的进程中运行,也就是应用包名,在application中应用此属性,将会为所有的组件开启一个不同的进程,但是组件能够覆盖application中的进程,允许你应用在跨进程通信。
如果process属性以:开头(:simon),那么将在需要的时候和服务需要运行在另外一个进程的时候开启一个属于此应用的私有进程,如果以小写字母开头(com.simon),(不能以数字开头,并且要符合命名规范,必须要有.否则将会出现这种错误: Invalid process name simon in package com.wind.check: must have at least one ‘.’)服务将在以这个名字命名的全局进程中,如果这是被允许的话。这个将允许组件在不同的应用中共享同一个进程,减少资源占用。

三:进程的种类

1:前台进程:(foreground process)
当前正在与用户进行交互的进程。如果一个进程满足以下任一条件,即视为前台进程:
(1):用户正在交互的 Activity——已调用 Activity 的 onResume() 方法
(2):某个 Service占用的进程,并且服务正在绑定用户正在交互的 Activity
(3):托管正在“前台”运行的 Service——服务已调用 startForeground()
(4):托管正执行一个生命周期回调的 Service——onCreate()、onStart() 或 onDestroy()
(5):托管正执行其 onReceive() 方法的 BroadcastReceiver
通常,在任意给定时间前台进程都为数不多。只有在内存不足以支持它们同时继续运行这一万不得已的情况下,系统才会终止它们。 此时,设备往往已达到内存分页状态,因此需要终止一些前台进程来确保用户界面正常响应。

2:可见进程:
没有任何前台组件、但仍会影响用户在屏幕上所见内容的进程。 如果一个进程满足以下任一条件,即视为可见进程:
(1):托管不在前台、但仍对用户可见的 Activity(已调用其 onPause() 方法)。例如,如果前台 Activity 启动了一个对话框,允许在其后显示上一 Activity,则有可能会发生这种情况。
(2):托管绑定到可见(或前台)Activity (已调用其 onPause() 方法)的 Service。
可见进程被视为是极其重要的进程,除非为了维持所有前台进程同时运行而必须终止,否则系统不会终止这些进程。

3:服务进程:
正在运行已使用 startService() 方法启动的服务且不属于上述两个更高类别进程的进程。尽管服务进程与用户所见内容没有直接关联,但是它们通常在执行一些用户关心的操作(例如,在后台播放音乐或从网络下载数据)。因此,除非内存不足以维持所有前台进程和可见进程同时运行,否则系统会让服务进程保持运行状态。

4:后台进程:
一些与当前交互的Activity、Service 无关的进程,关闭之后不会对用户交互有影响,对用户是不可见的,如Activity调用onStop()方法。
系统可能随时终止它们,以回收内存供前台进程、可见进程或服务进程使用。 通常会有很多后台进程在运行,因此它们会保存在 LRU (最近最少使用)列表中,以确保包含用户最近查看的 Activity 的进程最后一个被终止。如果某个 Activity 正确实现了生命周期方法,并保存了其当前状态,则终止其进程不会对用户体验产生明显影响,因为当用户导航回该 Activity 时,Activity 会恢复其所有可见状态。

5:空进程
主要用于缓存,以缩短下次在其中运行组件所需的启动时间。 为使总体系统资源在进程缓存和底层内核缓存之间保持平衡,系统往往会终止这些进程。

注:根据进程中当前活动组件的重要程度,Android 会将进程评定为它可能达到的最高级别。例如,如果某进程托管着服务和可见 Activity,则会将此进程评定为可见进程,而不是服务进程。

此外,一个进程的级别可能会因其他进程对它的依赖而有所提高,即服务于另一进程的进程其级别永远不会低于其所服务的进程。 例如,如果进程 A 中的内容提供程序为进程 B 中的客户端提供服务,或者如果进程 A 中的服务绑定到进程 B 中的组件,则进程 A 始终被视为至少与进程 B 同样重要。

由于运行服务的进程其级别高于托管后台 Activity 的进程,因此启动长时间运行操作的 Activity 最好为该操作启动服务,而不是简单地创建工作线程,当操作有可能比 Activity 更加持久时尤要如此。例如,正在将图片上传到网站的 Activity 应该启动服务来执行上传,这样一来,即使用户退出 Activity,仍可在后台继续执行上传操作。使用服务可以保证,无论 Activity 发生什么情况,该操作至少具备“服务进程”优先级。 同理,广播接收器也应使用服务,而不是简单地将耗时冗长的操作放入线程中。

四:进程间通信:IPC机制

IPC共有6种实现机制,分别是Bundle、文件共享、ContentProvider、Messenger、Socket以及AIDL。各自有各自的特点以及应用场景,在合适的场景下选择合适的方法进行IPC。

1:Bundle --------->简单数据传输

类似Intent 在四大组件中传递数据,Bundle主要运用于四大组件之间的进程通信,简单易用,但是只能传递一些简单的数据,而且这些数据必须是Bundle支持的数据类型:八大基本类型、Serializable、Parcelable以及其他一些类型。

2:文件共享 --------->单线程读写

简单易用 , 但不适合在高并发的情况下 并且读取文件需要时间, 不能即时通信 , 常用于并发程度不高 并且实时性要求不高的情况。这种方式在单线程读写的时候比较好用 如果有多个线程并发读写的话需要限制线程的同步读写 。另外 SharePreference是个特例 , 它底层基于xml实现 ,但是系统对它的读写会基于缓存,也就是说再多进程模式下就变得不那么可靠了,有很大几率丢失数据

3:ContentProvider(基于Binder) --------->跨进程(应用)的数据访问

主要实现对另一个应用进程开放provider数据的查询(增、删、查、改)
在数据源访问方面功能强大 支持一对多并发操作 可扩展call方法 ,可以理解为约束版的AIDL , 提供CRUD操作和自定义函数, 常用于一对多的数据共享场合。

4:Messenger(基于Binder) --------->简化版AIDL

用于可存放在message中的数据的传递,Messenger 其实就是 AIDL 的简化版,它把接口都封装好,我们只需在一个进程创建一个 Handler 传递给 Messenger,Messenger 帮我们把消息跨进程传递到另一个进程,我们在另一个进程的 Handler 在处理消息就可以了。


Messenger的使用.png

有点类似Handler机制中的发送Message和handleMessage实现子线程更新UI。
具体使用见:https://blog.csdn.net/u011240877/article/details/72836178

demo代码如下:

客户端:

/**
 * Messenger实现IPC-----客户端
 */
public class MessengerActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_messenger);

        Intent intent = new Intent(MessengerActivity.this, MyService.class);
        //intent.setComponent(new ComponentName("com.example.administrator.processtest", "com.example.administrator.processtest.MyService"));
        bindService(intent, conn, BIND_AUTO_CREATE);
    }

    /**
     * 服务回调方法
     */
    private ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //创建消息者
            Messenger messenger = new Messenger(service);
            //创建空消息
            Message message = Message.obtain(null, 1);
            Bundle bundle = new Bundle();
            bundle.putString("data", "这是一条由客户端发送的消息");
            //往消息中存数据
            message.setData(bundle);
            //发消息
            try {
                messenger.send(message);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(conn);
    }
}

服务端:

/**
 * 利用Messenger实现IPC———服务端
 */
public class MyService extends Service {
    public MyService() {
    }

    private class MessageHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case 1:
                    Bundle bundle = msg.getData();
                    String str = bundle.getString("data");
                    Log.d(" ", "接收到的客户端消息为:   " + str + "\n 当前进程ID为: " + android.os.Process.myPid());

                    break;
            }
            super.handleMessage(msg);
        }
    }

    Messenger messenger = new Messenger(new MessageHandler());


    @Override
    public IBinder onBind(Intent intent) {

        return messenger.getBinder();

    }
}

5:Socket --------->网络数据通信

主要应用于涉及到网络数据的通信的情况,支持一对多的并发通信.
由于涉及到网络知识,所以可以顺便回顾下网络TCP/IP 四层模型、osi七层模型、TCP三次握手,四次挥手,TCP与UDP的区别(特点)、TCP的流量控制、拥塞控制以及Socket套接字 等等网络基础知识。
网络基础以及Socket实现通信具体用法见:https://blog.csdn.net/u011240877/article/details/72860483

demo:
上面网址中有TCP 的demo 所以 附上UDP的demo,用法大致相似,具体如下

UDP客户端:

public class UDPClient {

    private static final int SERVER_PORT = 6000;

    private DatagramSocket dSocket = null;

    private String msg;

    /**
     * @param msg
     */
    public UDPClient(String msg) {
        super();
        this.msg = msg;
    }

    /**
     * 发送信息到服务器
     */
    public String send() {
        StringBuilder sb = new StringBuilder(100);
        InetAddress local = null;
        try {
            local = InetAddress.getByName("localhost"); // 本机测试
            sb.append("已找到服务器,连接中..."+"\n");
        } catch (UnknownHostException e) {
            sb.append("未找到服务器."+"\n");
            e.printStackTrace();
        }
        try {
            dSocket = new DatagramSocket(); // 注意此处要先在配置文件里设置权限,否则会抛权限不足的异常
            sb.append("正在连接服务器..."+"\n");
        } catch (SocketException e) {
            e.printStackTrace();
            sb.append("服务器连接失败."+"\n");
        }
        int msg_len = msg == null ? 0 : msg.length();
        DatagramPacket dPacket = new DatagramPacket(msg.getBytes(), msg_len,
                local, SERVER_PORT);
        try {
            dSocket.send(dPacket);
            sb.append("消息发送成功!\n");
        } catch (IOException e) {
            e.printStackTrace();
            sb.append("消息发送失败.\n");
        }
        dSocket.close();
        return sb.toString();
    }
}

UDP 服务端:

public class UDPServer implements Runnable {

    private static final int PORT = 6000;

    private byte[] msg = new byte[1024];

    private boolean life = true;

    public UDPServer() {
    }

    /**
     * @return the life
     */
    public boolean isLife() {
        return life;
    }

    /**
     * @param life
     *            the life to set
     */
    public void setLife(boolean life) {
        this.life = life;
    }

    @Override
    public void run() {
        DatagramSocket dSocket = null;
        DatagramPacket dPacket = new DatagramPacket(msg, msg.length);
        try {
            dSocket = new DatagramSocket(PORT);
            while (life) {
                try {
                    dSocket.receive(dPacket);
                    Log.i("msg sever received", new String(dPacket.getData()));
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        } catch (SocketException e) {
            e.printStackTrace();
        }
    }
}

入口活动:

public class UDPActivity extends AppCompatActivity {

    EditText msg_et = null;
    Button send_bt = null;
    TextView info_tv = null;
    String str = null;


    @SuppressLint("HandlerLeak")
    private Handler myHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what){
                case 1:
                    if(!TextUtils.isEmpty(str)) {
                        info_tv.setText(str);
                    }
                    break;
                default:
                    break;
            }

        }
    };
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_udp);

        msg_et = (EditText) findViewById(R.id.msg_et);
        send_bt = (Button) findViewById(R.id.send_bt);
        info_tv = (TextView) findViewById(R.id.info_tv);

        // 开启服务器
        ExecutorService exec = Executors.newCachedThreadPool();
        UDPServer server = new UDPServer();
        exec.execute(server);

        // 发送消息
        send_bt.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                final UDPClient client = new UDPClient(msg_et.getText().toString());
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                      str =  client.send();
                      myHandler.sendEmptyMessage(1);
                    }
                }).start();


            }
        });
    }
}

6:AIDL(基于Binder) --------->远程方法调用

AIDL(Android 接口定义语言) 是 Android 提供的一种进程间通信 (IPC) 机制。我们可以利用它定义客户端与服务使用进程间通信 (IPC) 进行相互通信时都认可的编程接口。
通过这种机制,我们只需要写好 aidl 接口文件,编译时系统会帮我们生成 Binder 接口。

我的理解是创建好AIDL文件后在其中定义一些希望客户端调用或实现的方法,然后在服务端中创建生成的本地 Binder 对象,实现 AIDL 制定的方法,然后利用服务的onBind()方法实现客户端与服务端绑定时的回调,返回 mIBinder 后客户端就可以通过它远程调用服务端的方法,即实现了通讯。

具体使用:https://blog.csdn.net/u011240877/article/details/72765136

demo:

AIDL 客户端:

public class AidlClient extends AppCompatActivity {

    @BindView(R.id.tv_aidl)
    TextView tvAidl;
    @BindView(R.id.button)
    Button button;
    private IMyAidl myAidl;
    private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //连接后拿到 Binder,转换成 AIDL,在不同进程会返回个代理
            myAidl = IMyAidl.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            myAidl = null;
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_aidl_client);
        ButterKnife.bind(this);

        Intent intent1 = new Intent(getApplicationContext(), AidlService.class);
        //要执行 IPC,必须使用 bindService() 将应用绑定到服务上。
        bindService(intent1, serviceConnection, BIND_AUTO_CREATE);
    }

    @OnClick(R.id.button)
    public void addPerson() {
        Person person = new Person("凌鸿基");

        try {
            myAidl.addPerson(person);
            List listPerson = myAidl.getPersonList();
            tvAidl.setText(listPerson.toString());
        } catch (RemoteException e) {
            e.printStackTrace();
        }

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(serviceConnection);
    }
}

AIDL服务端:

public class AidlService extends Service {
    private final String TAG = this.getClass().getSimpleName();
    private ArrayList personArrayList;
    public AidlService() {
    }



    /**
     * 创建生成的本地 Binder 对象,实现 AIDL 制定的方法
     */
    private IBinder mIbingder = new IMyAidl.Stub() {
        @Override
        public void addPerson(Person person) throws RemoteException {
            personArrayList.add(person);
        }

        @Override
        public List getPersonList() throws RemoteException {
            return personArrayList;
        }
    };

    /**
     * 客户端与服务端绑定时的回调,返回 mIBinder 后客户端就可以通过它远程调用服务端的方法,即实现了通讯
     * @param intent
     * @return
     */

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
      personArrayList = new ArrayList<>();
        Log.d(TAG, "AIDL服务端已绑定");
        return mIbingder;
    }


}

7:Binder连接池 --------->加强版AIDL

以下内容来自《Android 开发艺术探索》
如果有多个业务模块都需要使用AIDL来进行IPC,那么我们该怎么处理?最容易想到的就是为每一个模块创建一个服务,然后利用上面6中的使用方法一 一实现,但是如果有1000个业务模块,我们不可能创建1000个服务来实现IPC,因为服务也是四大组件之一,也会占用资源,这样难免会造成资源的短缺以及资源利用、管理的难题。
我把AIDL放在六种方式的最后一种,原因就是它是一种“重量级”的IPC方式,如何让它减重成为一个轻量级的,只需要很少的服务就能实现的IPC方式呢,就用到了让AIDL如虎添翼的东西——Binder连接池

在这种有Binder连接池的模式下,整个工作机制是这样的:每个业务模块创建自己的AIDL接口并实现此接口,这个时候不同业务模块之间是不能有耦合的,所有实现细节我们要单独开来,然后向服务端提供自己的唯一标识和其对应的Binder对象;对于服务端来说,只需要一个Service就可以了,服务端提供一个querBinder接口,这个接口能够根据业务模块的特征来返回相应的Binder对象给它们,不同的业务模块拿到所需要的Binder对象后就可以进行远程方法调用了。由此可见,Binder连接池的主要作用就是将每个业务模块的Binder请求统一转发到远程Service中去执行,从而避免了重复创建Service的过程。

主要工作原理:

Binder连接池工作原理

参考网址:
这可能是最全的Android:Process (进程)讲解了
Android中实现IPC的几种方式详细分析及比较
Android 进阶13:几种进程通信方式的对比总结
Android 进阶10:进程通信之 Messenger 使用与解析
Android 进阶12:进程通信之 Socket (顺便回顾 TCP UDP)
Android 进阶7:进程通信之 AIDL 的使用

你可能感兴趣的:(Android 进程相关——学习笔记)