赞
踩
本文来自刘兆贤的博客_CSDN博客-Java高级,Android旅行,Android基础领域博主 ,引用必须注明出处!
大的方向上
第一、使用进程共享的方式,往往使用android:process=remote,这样开启一个新的进程,使得所有进程都可以访问这个进程,使服务可以在多进程共享;而android:process=:remote相当于给当前进程一个私有进程,用来维护其自身的业务处理。开启新进程可以用在activity、service、broadcastReceiver、ContentProvider等组件。
If the name assigned to this attribute begins with a colon (':'), a new process, private to the application, is created when it's needed and the service runs in that process. If the process name begins with a lowercase character, the service will run in a global process of that name, provided that it has permission to do so. This allows components in different applications to share a process, reducing resource usage.
第二、使用广播的方式,可以设置category、component、package来区别广播,同时使用自定义权限来做限制
方法1、使用AIDL的方式进行
第三、使用Message+Binder
第四、使用Socket
第五、共享内存,如ContentProvider
linux有管道(Pipe)、信号(Signal)、报文(Message)、Trace、ShareMemory等。
Binder能支持跨进程通信的原因:是它实现IBinder接口,系统定义实现此接口即赋予进程通信的功能。
优势:做数据拷贝只用一次,则Pipe、Socket都需要两次;其次安全性高,不会像Socket会暴露地址,被人替换;
通信机制:Service向ServiceManager注册,得到虚拟的Uid和Pid;Client向ServiceManager请求,得到虚拟的Uid和Pid,以及目标对象的Proxy,底层通过硬件协议传输,使用Binder通讯。
通信时Client手持Proxy,ServiceManger进行转换,调用到Service。三者运行在三个独立进程中。Client/Sever全双工,互为Sever/Client。
ServiceManager的主要作用:注册和查询,将Service注册服务,根据参数和名字查询服务。
获得进程是否位于前台:
public static boolean isRunningForeground(Application application) { ActivityManager activityManager = (ActivityManager) application.getSystemService("activity"); List appProcessInfos = activityManager.getRunningAppProcesses(); Iterator var3 = appProcessInfos.iterator(); ActivityManager.RunningAppProcessInfo appProcessInfo; do { if (!var3.hasNext()) { return false; } appProcessInfo = (ActivityManager.RunningAppProcessInfo) var3.next(); } while (appProcessInfo.importance != 100 || !appProcessInfo.processName.equals(application.getApplicationInfo().processName)); return true; }
讲一个实例:
场景:小米手机,使用okhttp下载56M的安装包,每当下载至45%时,抛出异常:java.lang.RuntimeException: android.os.TransactionTooLargeException: data parcel size 521092 bytes
异常:android.os.TransactionTooLargeException: data parcel size 521092 bytes
at android.os.BinderProxy.transactNative(Native Method)
at android.os.BinderProxy.transact(Binder.java:769)
at android.app.INotificationManager$Stub$Proxy.enqueueNotificationWithTag(INotificationManager.java:1394)
at android.app.NotificationManager.notifyAsUser(NotificationManager.java:323)
at android.app.NotificationManager.notify(NotificationManager.java:292)
at android.app.NotificationManager.notify(NotificationManager.java:276)
分析:下载任务在子线程中执行,更新进度通过调用Notification显示,有时进度未更新也notify一下,结果导致调用系统服务次数过多,Binder的B/S服务响应不及时,出现此异常。
方案:减少不必要的调用。
BinderProxy:
void signalExceptionForError(JNIEnv* env, jobject obj, status_t err,
bool canThrowRemoteException, int parcelSize)
{
switch (err) {
case UNKNOWN_ERROR:
jniThrowException(env, "java/lang/RuntimeException", "Unknown error");
break;
......
case FAILED_TRANSACTION: {
ALOGE("!!! FAILED BINDER TRANSACTION !!! (parcel size = %d)", parcelSize);
const char* exceptionToThrow;
char msg[128];
// TransactionTooLargeException is a checked exception, only throw from certain methods.
// FIXME: Transaction too large is the most common reason for FAILED_TRANSACTION
// but it is not the only one. The Binder driver can return BR_FAILED_REPLY
// for other reasons also, such as if the transaction is malformed or
// refers to an FD that has been closed. We should change the driver
// to enable us to distinguish these cases in the future.
if (canThrowRemoteException && parcelSize > 200*1024) {
// bona fide large payload
exceptionToThrow = "android/os/TransactionTooLargeException";
snprintf(msg, sizeof(msg)-1, "data parcel size %d bytes", parcelSize);
} else {
// Heuristic: a payload smaller than this threshold "shouldn't" be too
// big, so it's probably some other, more subtle problem. In practice
// it seems to always mean that the remote process died while the binder
// transaction was already in flight.
exceptionToThrow = (canThrowRemoteException)
? "android/os/DeadObjectException"
: "java/lang/RuntimeException";
snprintf(msg, sizeof(msg)-1,
"Transaction failed on small parcel; remote process probably died");
}
jniThrowException(env, exceptionToThrow, msg);
} break;
.......
}
}
大于200k即报android/os/TransactionTooLargeException,而我们需要传输的数据已经达到500k。
另外通知不可以在主线程中持续更新,否则容易导致应用卡顿。
进程间通信方式:Intent/aidl/messenger/broadcastreceiver/file/socket
Linux进程隔离,进行IPC方式:socket/pipe/semaphone/fifo等,Android只保留了前两种。
IPC通信机制:先将应用进程的数据放到数据缓存区,然后在内核空间开辟一块缓存区,使用copy_from_user将数据从应用缓存区拷贝到内核缓存区;接方的服务进程,同样在用户空间开辟一块缓存区,然后将内核缓存区的数据拷贝过来。应用->内核->服务,计拷贝2次。
2次拷贝,就像寄快递的过程,寄的人和收的人处在用户空间,快递员处在内核空间。
Linux利用加载内核模块(Loadable Kernal Module)机制,将Binder模块动态加载进来,用于IPC通信。
Socket两种实现方式:一、listen某个端口,通过read获得数据的阻塞式,二、select某个文件操作符,等有数据再去读的非阻塞式。可以说一个监听端口,一个监听文件。
Android多数IPC调用使用Binder,Zygote使用Socket,Kill Process使用Signal
为什么Zygote使用Socket,不用Binder?Zygote进行fork的作用是,在单线程条件下,将父进程的资源和内存拷贝到子进程,而Binder是多线程操作的,容易造成死锁;比如子进程在等待Zygote进程的资源,但Zygote进程有锁,并未拷贝过来。
使用Binder的原因:
1、数据拷贝1次(一、应用进程到内核传递数据指针。二、该块数据在应用进程中申请内存。三、从内核到应用层,再次传递指针,即只有第1次发生数据拷贝,为什么不直接映射同一物理空间,因为多线程资源有同步问题);
2、安全系数高(通过分配的pid和uid,可以对应用进行身份验证,比如权限;另一方面不像socket开放端口,使有知道协议就可以访问)
3、稳定性好,Client和Server,通过ServiceManager进行通信,可以有效降低死锁、请求过载等情况。
应用空间之间不能共享资源,而内核空间可以
使用Signal的场景:杀进程Process.killProcess(),通知
Binder原理:架构分为三层:业务层、IPC层和驱动层
Binder通信协议分为两类:BC(Binder Command Protocol,请求码,从IPC层传递到驱动层)和BR(Binder Response Procotol,响应码,从驱动层传递到IPC层)
所有注册的服务,在死亡时会告知ServiceManager,从而可以降低Client直接检测造成的负载过重。
硬件开发:
Android架构分为四级,应用程序、Framework、Hal层(Hardware Abstraction Layer 硬件厂商可以把算法等核心技术放在这一层,不需要开源)、内核驱动层。
Android移植包含两方面:应用移植和系统移植。前者指应用程序,针对不同版本和硬件的系统,做兼容处理;后者指Android系统,在不同硬件如CPU架构、蓝牙等进行适配,必要时还要移植Linux内核驱动和HAL层代码。
监测应用工具:Profiler(CPU、内存、电量),App Inspection(数据库、网络和后台服务),LeakCanary。
参考:
https://zhuanlan.zhihu.com/p/138357525
http://gityuan.com/2015/11/01/binder-driver/
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。