ContentProvider Cannot perform this operation because the connection pool has been closed 细节讲解配上详细图解

Posted Engineer-Jsp

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ContentProvider Cannot perform this operation because the connection pool has been closed 细节讲解配上详细图解相关的知识,希望对你有一定的参考价值。

 

如题,ContentProvider java.lang.IllegalStateException: Cannot perform this operation because the connection pool has been closed.

上个星期有个群友问我一个关于ContentProvider 和SQLiteDatabase异常的问题,正好这段时间我接触这两个东西的次数也非常频繁,正好今天也是周六,笔者打算写一篇详细的博客顺带把解决思路也一起写在博客里,满满的都是干货,张同学请接好!

他的问题如上述图片所示,然后他也把相关代码截图给了我,出于对他工作安全问题的考虑,笔者这里就不贴出来了,反正笔者是看了几个小时,然后做了一个测试就发现了问题所在

第一步:确定解决思路

首先明确一点,那就是需要确定具体是哪一方引起的异常,然后在针对性的去解决问题,根据群友小张提供的图片,笔者写了两个类似demo放到手机上运行,为了不混淆看官老爷们的思维,笔者把小张写的App叫张三App,他同事的用户信息收集App叫李四App,现在的问题就是张三App需要访问李四App里面的用户信息,但是张三App在访问李四App数据库信息的时候一直报错

首先笔者根据图片分析了张三App、李四App关于数据库这块的架构,然后写成两个App demo,张三这边的截图只有一张,那就是关于ContentResolver类的query方法,他说是在Service里调用的,笔者创建了一个新的张三App demo作为查询方,再根据李四App提供的两张代码截图创建了新的李四App demo作为被查询方

首先看张三App demo的代码(写在一个Activity里,点一次Button执行一次query):

	public void query(View view) 
		Toast.makeText(getApplicationContext(), "query", Toast.LENGTH_SHORT).show();

		AsynIoTask.getTaskManager().execute(new Runnable() 

			@Override
			public void run() 
				Uri uri = Uri.parse("......");
				Cursor cursor = resolver.query(uri, null, null, null, null);
				if (cursor.getCount() > 0) 
					while (cursor.moveToNext()) 
						......
					
				
				cursor.close();
			
		);
	

再来看李四App demo的代码(因为是跨进程,加上看了另一张图也没有代码问题,所以看自定义ContentProvider就够了):

	@Override
	public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) 

	    // 从数据库管理中获取读的对象
		SQLiteDatabase db = DatabaseManager.getDatabase().getReadable();
		Cursor cursor = null;

		switch (sUriMatcher.match(uri)) 
		case Media:
		    // 查询并获取结果cursor
			cursor = db.query(table, projection, selection, selectionArgs, null, null, sortOrder);
			break;
		default:
			Log.d(TAG, "Query Uri : " + uri.toString());
			break;
		
		// 关闭数据库、连接池
		DatabaseManager.getDatabase().close();
		// 返回cursor
		return cursor;
	

看完张三App、李四App数据库部分代码,笔者再来介绍一下进程中有自定义Application、自定义ContentProvider时系统的加载流程是怎么样的,这里笔者画了一个大致启动流程图,如下图所示:

当然这部分启动逻辑应该是放在李四App里面的,如果想在自定义Application中加载数据库,那么就需要放在自定义Application的attachBaseContext方法中,如果没有这个要求,可以放自定义Application的onCreate方法中或放在自定义ContentProvider的onCreate方法中。但要注意一点,如果在自定义Application的onCreate方法中加载数据库,那么在自定义ContentProvider的onCreate方法中最好不要做数据库相关操作,因为自定义Application的onCreate方法要在自定义ContentProvider的onCreate方法执行后才被执行

还需要注意一点,如果李四App要在自定义Application的attachBaseContext方法中初始化数据库,需要在super.attachBaseContext方法之后初始化,否则系统在installProvider时获取包名方法会报空指针异常,笔者来分析一下具体原因。利用一张图,然后再通过结合图与文字描述为什么会报异常

由图可知,当自定义的Application重写了attachBaseContext方法时,系统会把attachBaseContext方法交由自定义的Application处理,否则由ContextWrapper处理,如果自定义的Application没有复写super.attachBaseContext(base)时,则系统会直接把attachBaseContext方法交由自定义的Application处理,如果复写了super.attachBaseContext(base),则依旧还是由ContextWrapper处理,如果自定义的Application不复写super.attachBaseContext(base),则系统在installProvider时通过ContextWrapper获取包名方法时会报空指针异常,这一点大家也要引起注意

第二步:测试与佐证

在第一步中笔者理清了张三App与李四App关于数据库这块的业务,现在需要加以测试和佐证以达到第一步的目标,就是明确解决思路。首先笔者查看了张三App的代码,因为代码很简单就是一个简单的Uri查询而已,压根就看不出毛病,所以笔者把矛头指向了李四App,笔者的思路是既然在李四App端的自定义ContentProvider没有报crash,那么数据到达张三App按理也不会出问题才对,理清头绪以后,我在李四App的自定义ContentProvider类的query方法中加入如下逻辑,看数据查询是不是在源头就出现了问题,代码如下:

	@Override
	public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) 

	    // 从数据库管理中获取读的对象
		SQLiteDatabase db = DatabaseManager.getDatabase().getReadable();
		Cursor cursor = null;

		switch (sUriMatcher.match(uri)) 
		case Media:
		    // 查询并获取结果cursor
			cursor = db.query(table, projection, selection, selectionArgs, null, null, sortOrder);
			break;
		default:
			Log.d(TAG, "Query Uri : " + uri.toString());
			break;
		
		// 关闭数据库、连接池
		DatabaseManager.getDatabase().close();
		try 
			if (cursor != null) 
				if (cursor.getCount() > 0) 
					while (cursor.moveToNext()) 
						......
					
				
			
		 catch (Exception e) 
			Log.d(TAG, "Query Exception : " + e.toString());
		
		// 返回 cursor
		return cursor;
	

加入try catch部分代码后运行程序,在张三App处执行query,李四App报如下异常:

Query Exception : java.lang.IllegalStateException: Cannot perform this operation because the connection pool has been closed.

到这步,可能很多小伙伴已经发现问题所在了,没错!我们已经找到了问题所在!

第三步:确认问题,解决问题

经过前两步的努力,笔者已经找到了问题所在,那就是张三App在执行query时,通过跨进程IPC通信通过代理调用Binder最终执行李四App的自定义ContentProvider类的query方法并得到查询结果后,最终return给代理等一系列过程最终返回到张三App,但是在李四App自定义ContentProvider类的query方法return之前,李四App执行了数据库的关闭,这导致return结果给张三App之后连接池已然已经关闭,导致张三App再次执行Cursor getCount方法时,直接报连接池已经关闭异常,所有调用过程如下图所示:

 由图可知,引起异常的问题主要是由橙色背景的①和②两个步骤引起,导致菱形步骤③抛出了异常

问题已然明确,然后现在给出解决办法,当然解决办法肯定不只一种,笔者这里给一个最简单的办法,那就是在李四App中将自定义ContentProvider类的所有暴露给外部使用的接口,将全部的数据库close注释掉,然后注册一个广播供张三App在执行完query之后使用,张三App执行query后发送该广播给李四,让李四去执行数据库的close,这样问题就能迎刃而解

至此小张同学的问题就解决了!

最后再额外讲讲关于自定义ContentProvider使用的一些小经验,使用自定义ContentProvider时一定要注意安全,在暴露接口给外部使用时要注意控制好权限问题,要不然被恶意程序攻击黑你的App数据可不好!

首先自定义ContentProvider是可以使用权限申请的,介绍如下几个常用权限

readPermission:数据库读权限

writePermission:数据库写权限

permission:数据库读写权限

exported:true表示外部应用可以访问你的数据库,false只有你的应用内部可以使用

顺便提一下,readPermission、writePermission权限都高于permission权限

如果readPermission、permission同时注册,permission就不起作用,表示只对外提供readPermission权限

如果writePermission、permission同时注册,permission就不起作用,表示只对外提供writePermission权限

三个同时注册应该也不用多说了,因为permission等价于readPermission+writePermission

详细使用(这里笔者以张三App和李四App为例):

李四App需要做的处理如下:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="......"
    android:versionCode="x"
    android:versionName="x.x" >

    <permission android:name="package name.read" />
    <permission android:name="package name.write" />


    <application
		......
        <provider
            android:name="李四App自定义ContentProvider"
            android:authorities="李四App自定义ContentProvider的authorities"
            android:exported="true"
            android:readPermission="package name.read"
            android:writePermission="package name.write" />
    </application>

</manifest>

张三App需要做的处理如下:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="......"
    android:versionCode="x"
    android:versionName="x.x" >

    <uses-permission android:name="package name.read" />
    <uses-permission android:name="package name.write" />

    <application
		......
    </application>

</manifest>

需要注意的是package name可以是任意内容,只要张三App的读写权限跟李四App的读写权限name一致就没问题

以上是关于ContentProvider Cannot perform this operation because the connection pool has been closed 细节讲解配上详细图解的主要内容,如果未能解决你的问题,请参考以下文章

解决javax.servlet.jsp.JspException cannot be resolved to a type

jsp页面老提示Multiple annotations found at this line: - javax.servlet.jsp.JspException cannot be resolve(

四大组件之ContentProvider-ContentProvider的数据存储

四大组件之ContentProvider-ContentProvider的权限使用和监听

四大组件之ContentProvider-轻轻松松自定义ContentProvider

安卓 ContentProvider