前面几篇总结了进程、线程相关的知识。这里总结下关于Android中的多进程、多线程及其使用。java
这里总结的Android中的多进程、多线程也是一个基础,可扩展的不少。linux
常见的几种使用android
这个方法,调用程序外的 脚本或命令程序,它会生成一个新的进程去调用 返回一个Process对象。shell
如:windows下,调用记事本。数据库
Runtime.getRuntime().exec("notepad.exe");
linux下(Android)下,调用系统自己的ps命令后,经过返回的Process对象的输入流 读取了调用内容。windows
try { String[] commandStr = new String[] {"/bin/sh","-c", "ps -ef"}; //String commandStr = "/bin/sh -c ps"; Process process1 = Runtime.getRuntime().exec( commandStr ); Log.d( TAG, "onCreate: process1=" + process1 ); byte [] data = new byte[1024]; int len = 0; while( -1 != (len = process1.getInputStream().read(data)) ) { String str = new String(data, 0, len , "UTF-8"); Log.d( TAG, "onCreate: \n" + str ); } } catch (IOException e) { e.printStackTrace(); }
执行后,经过log,很容易看出,Runtime.getRuntime().exec("xxx")调用就是新建的进程。数组
这个方法一样能够调用程序外的 脚本或命令程序。安全
相似Runtime.getRuntime().exec(),如:window下打开计算器。网络
new ProcessBuilder("calc.exe").start();
llinux:多线程
try { String[] commandStr = new String[] {"/bin/sh","-c", "ps -ef"}; ProcessBuilder processBuilder = new ProcessBuilder(commandStr); processBuilder.redirectErrorStream( true ); Process process2 = processBuilder.start(); Log.d( TAG, "onCreate: process2=" + process2 + ";processBuilder.directory="+processBuilder.directory()); byte [] data = new byte[1024]; int len = 0; while( -1 != (len = process2.getInputStream().read(data)) ) { String str = new String(data, 0, len , "UTF-8"); Log.d( TAG, "onCreate: \n" + str ); } } catch (IOException e) { e.printStackTrace(); }
注:
Runtime.getRuntime().exec(param)和 ProcessBuilder(param).start()关联
经过源码跟踪,很容易看到Runtime.getRuntime().exec()最终也是调用的ProcessBuilder().start()来实现的。
因此它们不少相似,都是建立一个新的进程来执行程序外的脚本或命令程序,并返回Process实例,经过实例能够获取进程信息及控制进程状态。
传递参数有所不一样,Runtime.getRuntime().exec()能够是单独字符串(用空格分隔可执行命令程序和参数,如例子中注释的那条),也能够是字符串数组。ProcessBuilder().start()只能是字符串数组或集合,一样第一个参数是可执行命令程序。
应用的AndroidManifest.xml的清单文件中,能够经过设置android:process标签 实现多进程。
默认,同一应用全部组件运行相同进程中,进程名即包名。
四大组件(Activity,Service,ContentProvider,BroadcastReceive)都支持android:process标签,组件元素-(<activity>、<service>、<receiver> 和 <provider>),经过此属性指定组件在哪一个进程中运行。
<application>也支持android:process标签,它只设置全部组件默认运行进程的默认值。
若是以冒号(“:”)开头,建立的 则是应用的私有进程。
如配置android:process=":myprocess1",包名是com.android.test,则实际进程名即com.android.test:myprocess1。
若是不是以冒号而是以小写字母开头,则是全局进程,只要有权限便可访问。不一样应用的组件能够共享该进程。
以下两个重要的值,若是超过,则会出现OOM。经过 adb shell getprop | grep xxx(如adb shell getprop | grep dalvik可以看到不少配置,不只仅下面两个 )能够查询到相应手机中的配置,不一样手机多是不一样的。
dalvik.vm.heapsize ---单个dalvik虚拟机最大内存
dalvik.vm.heapgrowthlimit ---单个进程的最大内存
进程间通讯总结中提到比较全面,能够参考下。//TODO
系统中也存在不少包括Activity,Service,Broadcast,ContentProvider都有这样的实现。
比较经常使用,须要了解和掌握的:Bundle(序列化,四大组件经常使用),AIDL,Messenger,ContentProvider,Socket
当应用启动后,系统即会为应用建立一个线程---“main”,即主线程。主线程,也称为UI线程,它负责事件的分派,包括绘制事件。
通常全部组件都在主线程中实例化。
当耗时操做(如网络访问或数据库操做)时就会阻塞主线程,会致使事件没法分派,包括绘制事件,甚至5s ANR。
保证应用界面的响应能力,关键是不能阻塞界面线程。若是执行的操做不能即时完成,则应确保它们在单独的线程中运行。这个单独的线程即工做线程。
注意:
1.不要阻塞主线程,即耗时操做不要放在主线程中。
2.只有主线程能够更新UI,其余全部线程都没法更新UI。
因为第二点,系统提供了从其余线程进入主线程的几种方法:
例如
public void onClick(View v) { new Thread(new Runnable() { public void run() { // a potentially time consuming task final Bitmap bitmap = processBitMap("image.png"); imageView.post(new Runnable() { public void run() { imageView.setImageBitmap(bitmap); } }); } }).start(); }
可是,随着操做日趋复杂,这类代码也会变得复杂且难以维护。
解决://TODO
1.若经过工做线程完成复杂交互,考虑在工做线程中使用Handler处理来自主线程的消息。
2.扩展AsyncTask类。AsyncTask经过异步通讯和消息传递,将工做线程中的结果传递到主线程,更新相关UI操做。
先看下面的例子,onCreate中开启了10个线程,调用plusOne方法,对num进行加1处理。
private static final String TAG = "ProcessThread"; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate( savedInstanceState ); for (int i = 0; i < 5; i++) { new Thread_thread().start(); new Thread( runnable ).start(); } } int num = 0; //继承Thread类 private class Thread_thread extends Thread { @Override public void run() { plusOne(); } } //实现Runnable接口 private Runnable runnable = new Runnable() { @Override public void run() { plusOne(); } }; private void plusOne() { num++; Log.d( TAG, "num=" + num ); }
打印的log以下:
2020-05-28 10:58:12.731 9531-9567/com.flx.process_thread D/ProcessThread: num=1 2020-05-28 10:58:12.741 9531-9568/com.flx.process_thread D/ProcessThread: num=3 2020-05-28 10:58:12.741 9531-9570/com.flx.process_thread D/ProcessThread: num=3 2020-05-28 10:58:12.744 9531-9569/com.flx.process_thread D/ProcessThread: num=4 2020-05-28 10:58:12.761 9531-9571/com.flx.process_thread D/ProcessThread: num=5 2020-05-28 10:58:12.762 9531-9572/com.flx.process_thread D/ProcessThread: num=6 2020-05-28 10:58:12.787 9531-9574/com.flx.process_thread D/ProcessThread: num=7 2020-05-28 10:58:12.791 9531-9573/com.flx.process_thread D/ProcessThread: num=8 2020-05-28 10:58:12.801 9531-9575/com.flx.process_thread D/ProcessThread: num=9 2020-05-28 10:58:12.802 9531-9576/com.flx.process_thread D/ProcessThread: num=10
从log看到num值存在重复。像上述的状况,多是两个线程同时操做了num,操做时num都是同样的。这种状况就是线程不安全的。
线程安全就是,多个线程访问同一个对象时,若是不用考虑这些线程在运行时环境下的调度和交替执行,也不须要进行额外的同步,或者在调用方进行任何其余操做,调用这个对象的行为均可以得到正确的结果,那么这个对象就是线程安全的。
上述例子中,分别使用了建立线程经常使用的两种方法:
继承Thread类 和 实现Runnable接口
以下,应该都很熟悉,不作解释。
private synchronized void plusOne() { num++; Log.d( TAG, "num=" + num ); }
ReentrantLock是Lock的一个子类。
以下示例,通常使用,unlock放在finally中,执行方法体放在try中。
private final ReentrantLock lock = new ReentrantLock(); private void plusOne() { lock.lock(); try { num++; Log.d( TAG, "num=" + num ); }finally { lock.unlock(); } }
注:
volatile保证了不一样线程对某个变量进行操做时的可见性,即一个线程修改了某个变量的值,这新值对其余线程来讲是当即可见的。
volatile不能保证原子性,所以不能保证线程安全。
//当前进程ID,用户ID Log.d( TAG, "onCreate: currPID=" + android.os.Process.myPid() + ";currUID=" + android.os.Process.myUid() ); //当前线程ID。下面两种方法获取的值是不同的。 //第一个是系统级的,系统分配管理;第二个是java级的,Thread管理的。因为java跨平台 Log.d( TAG, "onCreate: currTid=" + android.os.Process.myTid() + ";currTid2=" + Thread.currentThread().getId() ); //主线程ID Log.d( TAG, "onCreate: mainTid=" + Looper.getMainLooper().getThread().getId() );
打印状况:
2020-05-28 10:58:12.703 9531-9531/com.flx.process_thread D/ProcessThread: onCreate: currPID=9531;currUID=10133 2020-05-28 10:58:12.704 9531-9531/com.flx.process_thread D/ProcessThread: onCreate: currTid=9531;currTid2=2 2020-05-28 10:58:12.704 9531-9531/com.flx.process_thread D/ProcessThread: onCreate: mainTid=2