使用 RenderScript 时的一个bug

1,616 阅读1分钟

项目用到RenderScript,调用 RenderScript.create(getContext()) 时抛出异常 RenderScript code cache directory uninitialized,这个调用在7.1上正常,在7.0上却会导致应用 crash,打开 RenderScript.java,追溯 create 方法,在internalCreate 中调用了 getCachePath

    /**
     * Gets the path to the code cache.
     */
    static synchronized String getCachePath() {
        if (mCachePath == null) {
            final String CACHE_PATH = "com.android.renderscript.cache";
            if (RenderScriptCacheDir.mCacheDir == null) {
                throw new RSRuntimeException("RenderScript code cache directory uninitialized.");
            }
            File f = new File(RenderScriptCacheDir.mCacheDir, CACHE_PATH);
            mCachePath = f.getAbsolutePath();
            f.mkdirs();
        }
        return mCachePath;
    }

当RenderScriptCacheDir.mCacheDir == null 时,抛出了这个异常,查看 RenderScriptCacheDir 的代码:

/**
 * Used only for tracking the RenderScript cache directory.
 * @hide
 */
public class RenderScriptCacheDir {
     /**
     * Sets the directory to use as a persistent storage for the
     * renderscript object file cache.
     *
     * @hide
     * @param cacheDir A directory the current process can write to
     */
    public static void setupDiskCache(File cacheDir) {
        // Defer creation of cache path to nScriptCCreate().
        mCacheDir = cacheDir;
    }

    static File mCacheDir;

}

实现很简单,就是管理一个变量 mCacheDir,在安卓7.0 和 7.1的源码里面全局搜索setupDiskCache这个方法的调用信息,发现只有一处调用,在ActivityThread 的 setupGraphicsSupport 里,这个方法在handleBindApplication 方法里被调用,这里只需明白这个方法的调用在应用java层的早期,在应用刚刚把ApplicationThread 注册到AMS之后,查看7.0 的实现:

int uid = Process.myUid();
String[] packages = getPackageManager().getPackagesForUid(uid);
// If there are several packages in this application we won't
// initialize the graphics disk caches
if (packages != null && packages.length == 1) {
  	ThreadedRenderer.setupDiskCache(cacheDir);
  	RenderScriptCacheDir.setupDiskCache(cacheDir);
}

而以下是7.1 的实现:

int uid = Process.myUid();
String[] packages = getPackageManager().getPackagesForUid(uid);

if (packages != null) {
 	 ThreadedRenderer.setupDiskCache(cacheDir);
 	 RenderScriptCacheDir.setupDiskCache(cacheDir);
}

减少了一个判断 packages.length == 1 ,从Uid读出对应包名,在7.0 上若包名不为1就跳过赋值的代码,而我们的应用为系统应用,Uid为android.uid.system,对应几十个应用,所以导致RenderScriptCacheDir.mCacheDir == null ,RenderScript.create 抛出异常,7.1的代码中去除了这个判断。

所以应用想兼容7.1之前的版本,可以在RenderScript.create 之前 先为RenderScriptCacheDir.mCacheDir 赋值,根据 ActivityThread 里面的赋值逻辑,这个赋值应该是 Context.getCodeCacheDir();RenderScriptCacheDir类为隐藏类,所以需要反射赋值。

另外,可以使用兼容包绕开这个bug。