RenderScript 让你的Android计算速度快的飞上天!

简介: RenderScript 让你的Android计算速度快的飞上天!

RenderScript介绍


根据Android官方网站的介绍:RenderScript是Android平台上用于运行计算密集任务的框架。RenderScript主要是面向数据并行计算,当然了,RenderScript中使用串行计算效率也很好。


RenderScript是充分利用GPU,CPU的计算能力,由于不同的硬件对应的并行执行不同,RenderScript会编译2次,首先是我们的PC编译器编译到apk中,然后在apk安装的时候,再编译一次。这样的好处是,可以充分利用不同的硬件,我们编写的代码无需关心具体的硬件的不同,都能写出高性能的代码。


RenderScript相关文档并不多,导致很难去学好RenderScript。但是其实用起来并不复杂,结合SDK中的两个例子和官方文档,基本可以入门了。


在使用RenderScript之前,请在eclipse的project.properties加上:

renderscript.target=18
renderscript.support.mode=true




Hello RenderScript


概念说太多没啥用,先来一段简单代码。需求很简单,我们需要将一张图片中的每个像素的颜色取反色,即分别将255减去当前像素点的R、G、B,得到的新的RGB作为当前像素点的新颜色。如果不用RenderScript,实现起来也非常简单,通过两个for循环,遍历每个像素点,然后替换像素就好,如下:


int width = mInBitmap.getWidth();
int height = mInBitmap.getHeight();
for (int x = 0; x < width; x++) {
    for (int y = 0; y < height; y++) {
        int color = mInBitmap.getPixel(x, y);
        int r=  255-(Color.red(color) ;
        int g=  255-(Color.green(color) ;
        int b=  255-(Color.blue(color) ;
        int c = Color.rgb(gray, gray, gray);
        mOutBitmap.setPixel(x, y, c);
     }
}


这是使用普通java代码实现,如果对一张较大的图执行这段代码,其耗时可想而知!再去看看RenderScript是如何实现相同的功能的:


首先,在代码目录下(即包目录下)创建rs文件,取名可以任意,我们新建一个hello.rs文件:

#pragma version(1)
#pragma rs java_package_name(com.hc.renderscript)
uchar4 __attribute__((kernel)) invert(uchar4 in)
{
  uchar4 out = in;
  out.r =255- in.r;
  out.g = 255-in.g;
  out.b = 255-in.b;
  return out;
}


看不懂?不要急!我们一行一行解释。仔细看会发现其实大部分跟C语言很像,首先#pragma是给编译器看的,#pragma version(1)是指版本号,目前只能选择1,没有更高的版本了。#pragma rs java_package_name(com.hc.renderscript)是告诉编译器,包的名称。因为每个rs文件都会自动生成对应的Java代码,比如,我们新建的hello.rs文件,会自动生成ScriptC_hello类,因此,我们需要在rs声明包的名称。


接下来比较重要的关键字__attribute__((kernel)),它跟函数放在一起,用于声明这个函数是个RenderScript核心函数,而不是一个可调用的函数。什么意思呢?其实可以这样理解,就是这个函数不是个普通函数,是用于并行计算的函数。我们不能显式调用,它是RenderScript内部调用的函数。这时你可能会想,既然我们不能显式调用,那该怎么调用呢?别急,接下来为你揭晓。


我们继续看到invert函数,这个函数有个uchar4类型,不用想,肯定表示占用4个字节,每个字节表示的取值范围0~255。但是接下来的事情就很奇怪了,uchar4 in中直接可以用in.r、in.g、in.b分别取出rgb颜色。我猜想uchar4是个结构体类型,本来想去官网查看一下,找了很久没找到。找到的童鞋麻烦告诉我一下,我可以重新编辑这篇文章。


但是就算没找到,我们也可以理解的通,其实,如果从本质上来说,它并不复杂,r表示第一个字节,g表示第二个字节,b表示第三个字节。甚至我们可以可以猜得到,还有in.a表示透明度,然后我测试了一下,发现真的编译通过。


另外,从RenderScript Basics Tutorial这篇文章可以知道,还可以通过x、y、z、w分别取出对应的第1、2、3、4个字节。也就是说,in.x与in.r都是一个意思.好了这里不再继续纠结uchar4.


RenderScript的核心我们编写完成了,从上面rs文件的invert函数我们知道,这个函数只对具体一个像素点操作,可是我们的图片有width*height个像素点,我们需要这些像素点并行执行inver函数才能得到我们想要的结果。


我们再看看Java代码如何调用,使之并行计算。


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mSrcImageView = (ImageView) findViewById(R.id.src);
        mDstImageView = (ImageView) findViewById(R.id.dst);
        mInBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test);
        mOutBitmap = Bitmap.createBitmap(mInBitmap.getWidth(),mInBitmap.getHeight(), mInBitmap.getConfig());
        mSrcImageView.setImageBitmap(mInBitmap);
        RenderScript rs = RenderScript.create(this);
        mScript = new ScriptC_hello(rs);
        aIn = Allocation.createFromBitmap(rs, mInBitmap);
        aOut = Allocation.createFromBitmap(rs, mInBitmap);
        mScript.forEach_invert(aIn, aOut);
        aOut.copyTo(mOutBitmap);
        mDstImageView.setImageBitmap(mOutBitmap);
        rs.destroy();
    }


运行出来的结果: 20160528192635733.png


我们继续解释Java代码:先看到第13行,创建的是一个RenderScript对象。接下来是将我们编写的rs文件对应的自动生成的Java类(即ScriptC_hello类)初始化。到目前为止,这些都很好理解。紧接着是创建了两个Allocation对象,这个对象是干嘛用的呢?从名称可以看出,它是用于分配内存的,createFromBitmap根据Bitmap分配内存。为什么需要创建2个Allocation对象呢?


这主要是在执行rs文件里面的并行函数时一个Allocation类型 aIn用于参数传入,一个Allocation类型 aOut用于计算结果输出。这两个Allocation的Element类型必须相同,在函数调用时RenderScript会检查,如果不想同会抛异常。这里提到了Element,Elemtent是指Allocation里的一项。比如我们要处理的是Bitmap,则Element表示的类型是像素。


做并行计算时,aIn对应的一个元素(Element)的计算结果会放入aOut对应的位置上。


定位到代码:mScript.forEach_invert(aIn, aOut);我们的rs文件里面并没有写forEach_invert函数,但是却在ScriptC_hello 类里面生成了这个函数。请注意,我们编写了invert函数,正因为我们的invert函数加了__attribute__((kernel))关键字,所以,会生成forEach_invert函数,这个函数传入的参数aIn和aOut我们都清楚了,RenderScript会自动将aIn里的每个元素(Element)并行的去执行invert函数.得到的结果放入aOut里。最后调用Allocation的copyTo函数把计算的结果转入到Bitmap中。


另外,值得注意的是,__attribute__((kernel))修饰的函数,其形参该怎么写,为啥我们这里是uchar4而不是uchar3或者是uint32之类的呢?我们该怎么确定好这个参数呢?其实,这主要是跟我们的需求有关,你可以根据需求改动。


比如我们的aIn里的元素是像素,而一个像素有RGBA占4个字节,因此我们写成uchar4作为形参。还有就是,后面还可以加形参uint32 x,uint32 y,uint32 z。这些是可选项,可以加也可以不加,不影响函数的调用,但是必须是uint32类型。


还有个可选函数init(),在rs文件里的这个函数会指初始化时调用,并且只会调用一次。

有时候我们希望返回的结果不止一个对象该怎么办?我们可以选择使用全局变量,在rs文件中声明全局变量,在rs文件的函数中把数据写入到rs文件的全局变量中。


再从Java代码中读取rs的全局变量即可!那么在Java代码中该怎么读取和设置rs中的全局变量呢?答案是,rs文件对应生成的Java类会自动生成全局变量的get和set方法。比如,在hello.rs文件中定义了全局变量int myVar.自动生成的ScriptC_hello类中会自动生成函数:set_myVar(int v)和get_myVar().这样就可以访问rs文件中的全局变量了。





最后


回到最开始说的,提升上一篇文章的轮廓提取速度。如果没有看过上一篇文章的请跳过,或者是前去: Android自动手绘,圆你儿时画家梦! 查看。我们去看看Sobel算法,主要分为2步,首先将彩色图转为灰度图,在CommenUtils类的toGrayscale函数中。然后再是调用Sobelsuanf ,在SobelUtils类的Sobel函数。


先看看toGrayscale函数:这个函数是直接调用系统的函数,我们不去管。在Sobel函数中,有两个地方使用了两个for循环,显然可以通过RenderScript进行并行计算,提升速度。篇幅原因,具体的实现这里就不提了。



相关文章
|
存储 编解码 Java
【Android FFMPEG 开发】FFMPEG 音频重采样 ( 初始化音频重采样上下文 SwrContext | 计算音频延迟 | 计算输出样本个数 | 音频重采样 swr_convert )(一)
【Android FFMPEG 开发】FFMPEG 音频重采样 ( 初始化音频重采样上下文 SwrContext | 计算音频延迟 | 计算输出样本个数 | 音频重采样 swr_convert )(一)
756 0
|
编解码 文件存储 Android开发
Android | 如何计算图片占用内存的大小
Android | 如何计算图片占用内存的大小
Android | 如何计算图片占用内存的大小
|
Android开发 开发者
Android系统是如何计算应用启动耗时的?能否更精准定位性能瓶颈呢?
Android系统是如何计算应用启动耗时的?能否更精准定位性能瓶颈呢?
Android系统是如何计算应用启动耗时的?能否更精准定位性能瓶颈呢?
|
API Android开发
【Android 内存优化】自定义组件长图组件 ( 长图滚动区域解码 | 手势识别 GestureDetector | 滑动计算类 Scroller | 代码示例 )
【Android 内存优化】自定义组件长图组件 ( 长图滚动区域解码 | 手势识别 GestureDetector | 滑动计算类 Scroller | 代码示例 )
160 0
【Android 内存优化】自定义组件长图组件 ( 长图滚动区域解码 | 手势识别 GestureDetector | 滑动计算类 Scroller | 代码示例 )
|
API Android开发
【Android 内存优化】自定义组件长图组件 ( 获取图像宽高 | 计算解码区域 | 设置图像解码属性 复用 像素格式 | 图像绘制 )
【Android 内存优化】自定义组件长图组件 ( 获取图像宽高 | 计算解码区域 | 设置图像解码属性 复用 像素格式 | 图像绘制 )
177 0
【Android 内存优化】自定义组件长图组件 ( 获取图像宽高 | 计算解码区域 | 设置图像解码属性 复用 像素格式 | 图像绘制 )
|
Java Shell Android开发
【Android 性能优化】应用启动优化 ( 启动白屏问题 | 应用启动时间测量 | 冷启动 | 热启动 | 应用启动时间计算源码分析 )
【Android 性能优化】应用启动优化 ( 启动白屏问题 | 应用启动时间测量 | 冷启动 | 热启动 | 应用启动时间计算源码分析 )
429 0
【Android 性能优化】应用启动优化 ( 启动白屏问题 | 应用启动时间测量 | 冷启动 | 热启动 | 应用启动时间计算源码分析 )
|
存储 编解码 Android开发
【Android 内存优化】Bitmap 内存占用计算 ( Bitmap 图片内存占用分析 | Bitmap 内存占用计算 | Bitmap 不同像素密度间的转换 )
【Android 内存优化】Bitmap 内存占用计算 ( Bitmap 图片内存占用分析 | Bitmap 内存占用计算 | Bitmap 不同像素密度间的转换 )
238 0
|
Android开发
【Android FFMPEG 开发】FFMPEG 音频重采样 ( 初始化音频重采样上下文 SwrContext | 计算音频延迟 | 计算输出样本个数 | 音频重采样 swr_convert )(二)
【Android FFMPEG 开发】FFMPEG 音频重采样 ( 初始化音频重采样上下文 SwrContext | 计算音频延迟 | 计算输出样本个数 | 音频重采样 swr_convert )(二)
675 0
|
Android开发
Android 笔记:计算当前日期时间前后几天的封装方法
Android 笔记:计算当前日期时间前后几天的封装方法
175 0
|
存储 XML 传感器
[Android电量] 耗电概述 & 计算公式
前言 电池电量耗费的源头实在太多,基本Android 设备上任何一个活动都会引起电池电量的消耗。 目前部分手机有 耗电排行的功能, 能显示出App耗电详情排行。虽然谷歌开放sdk 中并没有公开电量统计的API 或者文档,但因为安全中心->省电优化→耗电排行 中就是通过app 能显示出耗电详情排行,所以虽然未公开API但实则有相关的耗电API。
5143 0