分析 Package manager has died

这是今天遇到的一个issue,由于Binder造成的。虽然比较简单,还是保持记录下吧。

先来开看一下Crash log:

E/HpnsService(24810): HPNS Version is 5.0java.lang.RuntimeException: Package manager has died
E/HpnsService(24810): 	at android.app.ApplicationPackageManager.getPackageInfo(ApplicationPackageManager.java:111)
E/HpnsService(24810): 	at com.xx.xxx.util.AppUtil.checkInstalledPackageVersionCode(AppUtil.java:568)
E/HpnsService(24810): 	at com.xx.xxx.util.AppUtil.checkAppStatus(AppUtil.java:653)
E/HpnsService(24810): 	at com.xx.xxx.view.AppListView$LoadingAppThread.run(AppListView.java:723)
E/HpnsService(24810): Caused by: android.os.TransactionTooLargeException
E/HpnsService(24810): 	at android.os.BinderProxy.transactNative(Native Method)
E/HpnsService(24810): 	at android.os.BinderProxy.transact(Binder.java:496)
E/HpnsService(24810): 	at android.content.pm.IPackageManager$Stub$Proxy.getPackageInfo(IPackageManager.java:1786)
E/HpnsService(24810): 	at android.app.ApplicationPackageManager.getPackageInfo(ApplicationPackageManager.java:106)
E/HpnsService(24810): 	... 3 more

为什么会发生Package manager has died

frameworks/base/core/java/android/app/ApplicationPackageManager.java:

102    @Override
103    public PackageInfo getPackageInfo(String packageName, int flags)
104            throws NameNotFoundException {
105        try {
106            PackageInfo pi = mPM.getPackageInfo(packageName, flags, mContext.getUserId());
107            if (pi != null) {
108                return pi;
109            }
110        } catch (RemoteException e) {
111            throw new RuntimeException("Package manager has died", e);
112        }
113
114        throw new NameNotFoundException(packageName);
115    }


这是一个Binder调用,造成这个的原因是因为发生了RemoteException。


那为什么友会发生RemoteException

其实也就是下面的这句Caused by: android.os.TransactionTooLargeException造成的。


为什么会造成TransactionTooLargeException

frameworks/base/core/jni/android_util_Binder.cpp:

682        case FAILED_TRANSACTION:
683            ALOGE("!!! FAILED BINDER TRANSACTION !!!");
684            // TransactionTooLargeException is a checked exception, only throw from certain methods.
685            // FIXME: Transaction too large is the most common reason for FAILED_TRANSACTION
686            //        but it is not the only one.  The Binder driver can return BR_FAILED_REPLY
687            //        for other reasons also, such as if the transaction is malformed or
688            //        refers to an FD that has been closed.  We should change the driver
689            //        to enable us to distinguish these cases in the future.
690            jniThrowException(env, canThrowRemoteException
691                    ? "android/os/TransactionTooLargeException"
692                            : "java/lang/RuntimeException", NULL);
693            break;


可以看出如果Binder的使用超出了一个进程的限制就会抛出TransactionTooLargeException这个异常。

如果是其他原因造成Binder crash的话就会抛出RuntimeException。


那一个进程的Binder内存限制是多少?

frameworks/native/libs/binder/ProcessState.cpp:

44 #define BINDER_VM_SIZE ((1*1024*1024) - (4096 *2))
这便是一个进程中binder的大小,大约1M。

给Binder分配内存的代码:

349#if !defined(HAVE_WIN32_IPC)
350        // mmap the binder, providing a chunk of virtual address space to receive transactions.
351        mVMStart = mmap(0, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);
352        if (mVMStart == MAP_FAILED) {
353            // *sigh*
354            ALOGE("Using /dev/binder failed: unable to mmap transaction memory.\n");
355            close(mDriverFD);
356            mDriverFD = -1;
357        }


通过上面的清理,知道了如果一个进程中使用的Binder内容超过了1M,就会crash.

而如果这时候恰巧在用getPackageManager()做事情,就会提示Package manager has died。


可以事实真的是这样的吗?

写了个demo来证明一下:

public class MainActivity extends Activity {

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

	private void test() {
		for (int i = 0; i < 2; i++) {
		new Thread() {
			@Override
			public void run() {
				int count = 0;
					List<PackageInfo> list = getPackageManager()
							.getInstalledPackages(0);
					for (PackageInfo info : list) {
						if(count >=1000){
							break;
						}
						try {
							PackageInfo pi = getPackageManager()
									.getPackageInfo(info.packageName,
											0);
							Log.e("yanchen", "yanchen threadid:"+Thread.currentThread().getId() 
									+ ",i:" + count++);
						} catch (NameNotFoundException e) {
						}
				}
			}
		}.start();
	}
	}
}

这个Demo就是同时创建两个线程来进行Binder调用.

运行打印的log:

E/yanchen (21180): yanchen threadid:4097,i:271
E/yanchen (21180): yanchen threadid:4097,i:272
E/yanchen (21180): yanchen threadid:4097,i:273
E/yanchen (21180): yanchen threadid:4097,i:274
E/yanchen (21180): yanchen threadid:4097,i:275
E/yanchen (21180): yanchen threadid:4097,i:276

此时也如预期发生了Crash:

E/JavaBinder(31244): !!! FAILED BINDER TRANSACTION !!!
E/AndroidRuntime(31244): FATAL EXCEPTION: Thread-4798
E/AndroidRuntime(31244): Process: com.example.testdl, PID: 31244
E/AndroidRuntime(31244): java.lang.RuntimeException: Package manager has died
E/AndroidRuntime(31244): 	at android.app.ApplicationPackageManager.getPackageInfo(ApplicationPackageManager.java:155)
E/AndroidRuntime(31244): 	at com.example.testdl.MainActivity$1.run(MainActivity.java:40)
E/AndroidRuntime(31244): Caused by: android.os.TransactionTooLargeException
E/AndroidRuntime(31244): 	at android.os.BinderProxy.transactNative(Native Method)
E/AndroidRuntime(31244): 	at android.os.BinderProxy.transact(Binder.java:496)
E/AndroidRuntime(31244): 	at android.content.pm.IPackageManager$Stub$Proxy.getPackageInfo(IPackageManager.java:2208)
E/AndroidRuntime(31244): 	at android.app.ApplicationPackageManager.getPackageInfo(ApplicationPackageManager.java:150)
E/AndroidRuntime(31244): 	... 1 more
D/EnterpriseDeviceManagerService( 3021): isMana


解决方式:

     其实只要避免多个线程同时来调用Binder就可以了,毕竟一个线程用了会释放,所以理论上是很难发生的。

修改后的Demo:

							synchronized(MainActivity.class){
								PackageInfo pi = getPackageManager()
										.getPackageInfo(info.packageName,
												PackageManager.GET_ACTIVITIES);
							}

再次运行就不会Crash了。


©️2020 CSDN 皮肤主题: 猿与汪的秘密 设计师: 上身试试 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值