Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

插件中使用 litepal 这个库,当声明 storage 为 external 时, 会产生 File xxx.db contains a path separator 异常 #1049

Closed
Heart-Beats opened this issue Sep 15, 2022 · 8 comments · Fixed by #1311
Labels
bug Something isn't working PR welcome 欢迎贡献代码

Comments

@Heart-Beats
Copy link

大佬,已有项目改造插件中使用了 litepal 数据库框架, litepal 声明存储方式如下:
image

当调用 LitePal.getDatabase() 即会产生异常,整个调用堆栈异常如下图:
image
跟踪代码发现 litepal 初始化数据库名称时根据如下方法:

        Connector.java

	private static LitePalOpenHelper buildConnection() {
		LitePalAttr litePalAttr = LitePalAttr.getInstance();
		litePalAttr.checkSelfValid();
		if (mLitePalHelper == null) {
			String dbName = litePalAttr.getDbName();
			if ("external".equalsIgnoreCase(litePalAttr.getStorage())) {
				dbName = LitePalApplication.getContext().getExternalFilesDir("") + "/databases/" + dbName;
			} else if (!"internal".equalsIgnoreCase(litePalAttr.getStorage()) && !TextUtils.isEmpty(litePalAttr.getStorage())) {
				// internal or empty means internal storage, neither or them means sdcard storage
				String dbPath = Environment.getExternalStorageDirectory().getPath() + "/" + litePalAttr.getStorage();
				dbPath = dbPath.replace("//", "/");
				if (BaseUtility.isClassAndMethodExist("androidx.core.content.ContextCompat", "checkSelfPermission") &&
						ContextCompat.checkSelfPermission(LitePalApplication.getContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
					throw new DatabaseGenerateException(String.format(DatabaseGenerateException.EXTERNAL_STORAGE_PERMISSION_DENIED, dbPath));
				}
				File path = new File(dbPath);
				if (!path.exists()) {
					path.mkdirs();
				}
				dbName = dbPath + "/" + dbName;
			}
			mLitePalHelper = new LitePalOpenHelper(dbName, litePalAttr.getVersion());
		}
		return mLitePalHelper;
	}

因此当声明方式为 external 或者非 internal 时 dbName 都为 路径,当调用 LitePal.getDatabase() 方法时最终会调用到 SQLiteOpenHelper 的 getDatabaseLocked 这个方法,其中会有这行代码调用 : final File filePath = mContext.getDatabasePath(mName); ,当在插件中时最终会调用到 SubDirContextThemeWrapper 类的如下方法:
image
当插件的 businessName 不为 null 时,会按 "ShadowPlugin_" + mBusinessName+ dbName 拼接数据库名称,最终调用 ContextImpl 类的如下方法产生异常:
image

因此可知,当在插件中调用 context.getDatabasePath(dbName) 方法,当 dbName 为路径时且 businessName 非空时即会产生异常

但按 getDatabasePath() 方法的设计应该是需要兼容路径这种方式的,希望大佬有时间可以看看是否考虑兼容下传入路径这种方式!

@shifujun
Copy link
Collaborator

简要来说就是Database的name是允许包含/路径分隔符的,Shadow在businessName不为空时不支持这种路径。

兼容的方案应该是在有分隔符时,找出第一段作为需要添加前缀的名字。

可以在SubDirContextThemeWrapperTest中先补充一下测试用例。

你如果急用的话,可以先自己尝试修复一下。

@shifujun shifujun added bug Something isn't working PR welcome 欢迎贡献代码 labels Sep 16, 2022
@Heart-Beats
Copy link
Author

不急用,只是昨天发现这个问题然后排查了一下向您反馈该问题,目前 litepal 我将数据库存储到 内部目录中也可以使用

@dylanZk1
Copy link

其实不光是数据库,当你在插件中创建文件或文件夹的时候,shadow都会默认创建"ShadowPlugin_"+businessName这样的文件夹,可能是shadow想把宿主和插件的文件分割开。但是我觉得没啥必要

@dylanZk1
Copy link

源码层面,你可以修改com.tencent.shadow.core.runtime.ShadowContext.java中的getSubDirName方法,修改这个方法返回null的逻辑,或者直接返回null。

如果是Maven的话,你可能需要找到gradle缓存的jar包,解压后放入Android Studio,并添加"com.tencent.shadow.core:activity-container:$shadow_version"的依赖后再做上述操作,最后再打包好放入原路径

@Heart-Beats
Copy link
Author

其实不光是数据库,当你在插件中创建文件或文件夹的时候,shadow都会默认创建"ShadowPlugin_"+businessName这样的文件夹,可能是shadow想把宿主和插件的文件分割开。但是我觉得没啥必要

我觉得还是有必要的,特别是将多个老项目做成插件时,很多时候老项目数据库那部分使用的代码都差不多,所以如果不同项目需要数据隔离话,这个还是很有用的,当然不止数据库会,SP 也会有此方面的需求。

源码层面,你可以修改com.tencent.shadow.core.runtime.ShadowContext.java中的getSubDirName方法,修改这个方法返回null的逻辑,或者直接返回null。

如果是Maven的话,你可能需要找到gradle缓存的jar包,解压后放入Android Studio,并添加"com.tencent.shadow.core:activity-container:$shadow_version"的依赖后再做上述操作,最后再打包好放入原路径

至于你说的修改方法,我不是不会,而是此问题实际为 Shadow 设计然后代码本身的 bug, 仅仅为了解决 bug 改变 Shadow 该设计并非好的解决方案。

@dylanZk1
Copy link

我也明白,我也不想改,插件分离是好,但是也要考虑一些特殊情况,比如shadow可以预留一个读取宿主数据库和文件目录的接口,毕竟有的时候数据并不是定义在插件里,有时插件可能只是把部分功能给分离了出去,不同的宿主用到的数据可能并不相通,如果按照目前的逻辑,插件要执行涉及文件的操作时就会出现创建新的数据库或目录的情况导致无法读取。

当然,如果是插件之间的交互,那无可厚非

@Heart-Beats
Copy link
Author

简要来说就是Database的name是允许包含/路径分隔符的,Shadow在businessName不为空时不支持这种路径。

兼容的方案应该是在有分隔符时,找出第一段作为需要添加前缀的名字。

可以在SubDirContextThemeWrapperTest中先补充一下测试用例。

你如果急用的话,可以先自己尝试修复一下。

没想到这个问题今天还是存在没修复,我改造最新的 sunflower 时,其内部使用了 WorkManager,初始化时创建数据库时同样报错,debug 相关调试如下图:

image

image

其中的 name 传入的路径是全路径,若不修改 name 添加前缀,是可以正常执行的。

我理解的是对于这种全路径的是不好添加啥前缀的,而且这个路径也是调用相关方法生成的,已经包含插件标识, 但是对于其他的不带插件标识的全路径,我理解也是真实存在的路径,以供后续正常使用,所以都不应该去修改 name。但是对于非全路径这种,似乎不好判断,除了判断是否为应用对应的目录外或者是否为 path ? 但我感觉正常应该没人会这样去传路径吧,因此对于 SubDirContextThemeWrapper 里的 makeSubName() 方法,我认为可以直接判断路径即可。

相关的修改已经提交,见 pull request

对于 SubDirContextThemeWrapperTest 的测试用例,弄半天这个测试项目都运行不起来,实在不知道如何跑用例😂

@shifujun
Copy link
Collaborator

之前没看明白,这个问题跟SubName的设计压根没关系。就是getDatabasePath是支持绝对路径传入原样传回来的。

对于 SubDirContextThemeWrapperTest 的测试用例,弄半天这个测试项目都运行不起来,实在不知道如何跑用例😂

@Heart-Beats 运行测试用例可以看下f31081a

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working PR welcome 欢迎贡献代码
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants