SurfaceView允许你在非ui线程中去绘制。

SurfaceView的帧率可以操作60FPS

在要求实时性比较高的游戏开发中,显然,view的ondraw是满足不了你的,这时候只能是用SurfaceView。

SurfaceView在绘图时使用l了双缓冲机制,而View没有。

1. 混淆配置

一般情况下,app module 的 build.gradle 文件默认会有如下结构:

      android {buildTypes {release {minifyEnabled falseproguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'}}}

因为开启混淆会使编译时间变长,所以debug模式下不应该开启。我们需要做的是:

  1. releaseminifyEnabled的值改为true,打开混淆;
  2. 加上shrinkResources true,打开资源压缩。

修改后文件内容如下:

android {buildTypes {release {minifyEnabled trueshrinkResources trueproguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'}}
}

2. 自定义混淆规则

app module默认生成了项目的自定义混淆规则文proguard-rules.pro,多方调研后,一份适用于大部分项目的混淆规则最佳实践如下:

#指定压缩级别
  -optimizationpasses 5
  ​
  #不跳过非公共的库的类成员
  -dontskipnonpubliclibraryclassmembers
  ​
  #混淆时采用的算法
  -optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
  ​
  #把混淆类中的方法名也混淆了
  -useuniqueclassmembernames
  ​
  #优化时允许访问并修改有修饰符的类和类的成员 
  -allowaccessmodification
  ​
  #将文件来源重命名为“SourceFile”字符串
  -renamesourcefileattribute SourceFile
  #保留行号
  -keepattributes SourceFile,LineNumberTable
  ​
  #保持所有实现 Serializable 接口的类成员
  -keepclassmembers class * implements java.io.Serializable {
      static final long serialVersionUID;
      private static final java.io.ObjectStreamField[] serialPersistentFields;
      private void writeObject(java.io.ObjectOutputStream);
      private void readObject(java.io.ObjectInputStream);
      java.lang.Object writeReplace();
      java.lang.Object readResolve();
  }
  ​
  #Fragment不需要在AndroidManifest.xml中注册,需要额外保护下
  -keep public class * extends android.support.v4.app.Fragment
  -keep public class * extends android.app.Fragment
  ​
  # 保持测试相关的代码
  -dontnote junit.framework.**
  -dontnote junit.runner.**
  -dontwarn android.test.**
  -dontwarn android.support.test.**
  -dontwarn org.junit.**

真正通用的、需要添加的就是上面这些,除此之外,需要每个项目根据自身的需求添加一些混淆规则:- 第三方库所需的混淆规则。正规的第三方库一般都会在接入文档中写好所需混淆规则,使用时注意添加。- 在运行时动态改变的代码,例如反射。比较典型的例子就是会与 json 相互转换的实体类。假如项目命名规范要求实体类都要放在model包下的话,可以添加类似这样的代码把所有实体类都保持住-keep public class **.*Model*.** {*;}- JNI中调用的类。- WebViewJavaScript调用的方法- Layout布局使用的View构造函数、android:onClick等。

3. 检查混淆结果

混淆过的包必须进行检查,避免因混淆引入的bug。

一方面,需要从代码层面检查。使用上文的配置进行混淆打包后在 <module-name>/build/outputs/mapping/release/ 目录下会输出以下文件:- dump.txt 描述APK文件中所有类的内部结构- mapping.txt供混淆前后类、方法、类成员等的对照表- seeds.txt列出没有被混淆的类和成员- usage.txt列出被移除的代码

我们可以根据 seeds.txt 文件检查未被混淆的类和成员中是否已包含所有期望保留的,再根据 usage.txt件查看是否有被误移除的代码。

另一方面,需要从测试方面检查。将混淆过的包进行全方面测试,检查是否有 bug 产生。

4. 解出混淆栈

混淆后的类、方法名等等难以阅读,这固然会增加逆向工程的难度,但对追踪线上 crash 也造成了阻碍。我们拿到 crash 的堆栈信息后会发现很难定位,这时需要将混淆反解。

<sdk-root>/tools/proguard/ 路径下有附带的的反解工具(Window 系统为 proguardgui.bat,Mac 或 Linux 系统为 proguardgui.sh)。

这里以 Window 平台为例。双击运行 proguardgui.bat 后,可以看到左侧的一行菜单。点击 ReTrace,选择该混淆包对应的 mapping 文件(混淆后在 <module-name>/build/outputs/mapping/release/ 路径下会生成 mapping.txt 文件,它的作用是提供混淆前后类、方法、类成员等的对照表),再将 crash 的 stack trace 黏贴进输入框中,点击右下角的 ReTrace ,混淆后的堆栈信息就显示出来了。

以上使用 GUI 程序进行操作,另一种方式是利用该路径下的 retrace 工具通过命令行进行反解,命令是

retrace.bat|retrace.sh [-verbose] mapping.txt [<stacktrace_file>]

例如:

retrace.bat -verbose mapping.txt obfuscated_trace.txt

注意事项:

  1. 所有在 AndroidManifest.xml 涉及到的类已经自动被保持,因此不用特意去添加这块混淆规则。(很多老的混淆文件里会加,现在已经没必要)

  2. proguard-android.txt 已经存在一些默认混淆规则,没必要在 proguard-rules.pro 重复添加,该文件具体规则见附录1:

二、混淆简介

Android中的“混淆”可以分为两部分,一部分是 Java 代码的优化与混淆,依靠 proguard (保驾护航)混淆器来实现;另一部分是资源压缩,将移除项目及依赖的库中未被使用的资源(资源压缩严格意义上跟混淆没啥关系,但一般我们都会放一起讲)。

1. 代码压缩

image

代码混淆是包含了代码压缩、优化、混淆等一系列行为的过程。如上图所示,混淆过程会有如下几个功能:

\1. 压缩。移除无效的类、类成员、方法、属性等;

\2. 优化。分析和优化方法的二进制代码;根据proguard-android-optimize.txt中的描述,优化可能会造成一些潜在风险,不能保证在所有版本的Dalvik上都正常运行。

\3. 混淆。把类名、属性名、方法名替换为简短且无意义的名称;

\4. 预校验。添加预校验信息。这个预校验是作用在Java平台上的,Android平台上不需要这项功能,去掉之后还可以加快混淆速度。

这四个流程默认开启。

在 Android 项目中我们可以选择将“优化”和“预校验”关闭,对应命令是-dontoptimize-dontpreverify(当然,默认的 proguard-android.txt 文件已包含这两条混淆命令,不需要开发者额外配置)。

2. 资源压缩

资源压缩将移除项目及依赖的库中未被使用的资源,这在减少 apk 包体积上会有不错的效果,一般建议开启。具体做法是在 build.grade 文件中,将 shrinkResources 属性设置为 true。需要注意的是,只有在用minifyEnabled true开启了代码压缩后,资源压缩才会生效

资源压缩包含了“合并资源”和“移除资源”两个流程。

“合并资源”流程中,名称相同的资源被视为重复资源会被合并。需要注意的是,这一流程不受shrinkResources属性控制,也无法被禁止, gradle 必然会做这项工作,因为假如不同项目中存在相同名称的资源将导致错误。gradle 在四处地方寻找重复资源:- src/main/res/ 路径- 不同的构建类型(debug、release等等)- 不同的构建渠道- 项目依赖的第三方库

合并资源时按照如下优先级顺序:

依赖 -> main -> 渠道 -> 构建类型

举个例子,假如重复资源同时存在于main文件夹和不同渠道中,gradle 会选择保留渠道中的资源。

同时,如果重复资源在同一层次出现,比如src/main/res/src/main/res2/,则 gradle 无法完成资源合并,这时会报资源合并错误。

“移除资源”流程则见名知意,需要注意的是,类似代码,混淆资源移除也可以定义哪些资源需要被保留,这点在下文给出。

三、自定义混淆规则

在上文“混淆配置”中有这样一行代码

proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'

这行代码定义了混淆规则由两部分构成:位于 SDK 的 tools/proguard/ 文件夹中的 proguard-android.txt 的内容以及默认放置于模块根目录的 proguard-rules.pro 的内容。前者是 SDK 提供的默认混淆文件(内容见附录1),后者是开发者自定义混淆规则的地方。

1. 常见混淆命令:

  • optimizationpasses

  • dontoptimize

  • dontusemixedcaseclassnames

  • dontskipnonpubliclibraryclasses

  • dontpreverify

  • dontwarn

  • verbose

  • optimizations

  • keep

  • keepnames

  • keepclassmembers

  • keepclassmembernames

  • keepclasseswithmembers

  • keepclasseswithmembernames

在第一部分 Android 混淆最佳实践中已介绍部分需要使用到的混淆命令,这里不再赘述,详情请查阅官网。需要特别介绍的是与保持相关元素不参与混淆的规则相关的几种命令:

命令 作用
-keep 防止类和成员被移除或者被重命名
-keepnames 防止类和成员被重命名
-keepclassmembers 防止成员被移除或者被重命名
-keepnames 防止成员被重命名
-keepclasseswithmembers 防止拥有该成员的类和成员被移除或者被重命名
-keepclasseswithmembernames 防止拥有该成员的类和成员被重命名

2. 保持元素不参与混淆的规则

形如:

[保持命令] [类] {[成员] }

“类”代表类相关的限定条件,它将最终定位到某些符合该限定条件的类。它的内容可以使用:- 具体的类- 访问修饰符(publicprotectedprivate)- 通配符*,匹配任意长度字符,但不含包名分隔符(.)- 通配符**,匹配任意长度字符,并且包含包名分隔符(.)- extends,即可以指定类的基类- implement,匹配实现了某接口的类- $,内部类

“成员”代表类成员相关的限定条件,它将最终定位到某些符合该限定条件的类成员。它的内容可以使用:- <init> 匹配所有构造器- <fields> 匹配所有域- <methods> 匹配所有方法- 通配符*,匹配任意长度字符,但不含包名分隔符(.)- 通配符**,匹配任意长度字符,并且包含包名分隔符(.)- 通配符***,匹配任意参数类型- ,匹配任意长度的任意类型参数。比如void test(…)就能匹配任意 void test(String a) 或者是 void test(int a, String b)这些方法。- 访问修饰符(publicprotectedprivate

举个例子,假如需要将name.huihui.test包下所有继承Activitypublic类及其构造函数都保持住,可以这样写:

-keep public class name.huihui.test.** extends Android.app.Activity {<init>}

3. 常用的自定义混淆规则

  • 不混淆某个类
-keep public class name.huihui.example.Test { *; }
  • 不混淆某个包所有的类
-keep class name.huihui.test.** { *; }
  • 不混淆某个类的子类
-keep public class * extends name.huihui.example.Test { *; }
  • 不混淆所有类名中包含了“model”的类及其成员
-keep public class **.*model*.** {*;}
  • 不混淆某个接口的实现
-keep class * implements name.huihui.example.TestInterface { *; }
  • 不混淆某个类的构造方法
-keepclassmembers class name.huihui.example.Test { public <init>(); }
  • 不混淆某个类的特定的方法
-keepclassmembers class name.huihui.example.Test { public void test(java.lang.String); }

四、自定义资源保持规则

1. keep.xml

shrinkResources true开启资源压缩后,所有未被使用的资源默认被移除。假如你需要定义哪些资源必须被保留,在 res/raw/路径下创建一个 xml 文件,例如 keep.xml

通过一些属性的设置可以实现定义资源保持的需求,可配置的属性有:- tools:keep 定义哪些资源需要被保留(资源之间用“,”隔开)- tools:discard 定义哪些资源需要被移除(资源之间用“,”隔开)- tools:shrinkMode 开启严格模式

当代码中通过 Resources.getIdentifier() 用动态的字符串来获取并使用资源时,普通的资源引用检查就可能会有问题。例如,如下代码会导致所有以“img_”开头的资源都被标记为已使用。

String name = String.format("img_%1d", angle + 1);
res = getResources().getIdentifier(name, "drawable", getPackageName());

我们可以设置 tools:shrinkModestrict 来开启严格模式,使只有确实被使用的资源被保留。

以上就是自定义资源保持规则相关的配置,举个例子:

<?xml version="1.0" encoding="utf-8"?><resources xmlns:tools="http://schemas.android.com/tools"tools:keep="@layout/l_used*_c,@layout/l_used_a,@layout/l_used_b*"tools:discard="@layout/unused2"tools:shrinkMode="strict"/>

2. 移除替代资源

一些替代资源,例如多语言支持的 strings.xml,多分辨率支持的 layout.xml 等,在我们不需要使用又不想删除掉时,可以使用资源压缩将它们移除。

我们使用 resConfig 属性来指定需要支持的属性,例如

  android {defaultConfig {...resConfigs "en", "fr"}}

其他未显式声明的语言资源将被移除。

附录

  1. proguard-android.txt文件内容
  #包名不混合大小写-dontusemixedcaseclassnames​#不跳过非公共的库的类-dontskipnonpubliclibraryclasses​#混淆时记录日志-verbose​#关闭预校验-dontpreverify​#不优化输入的类文件-dontoptimize​#保护注解-keepattributes *Annotation*​#保持所有拥有本地方法的类名及本地方法名-keepclasseswithmembernames class * {native <methods>;}​#保持自定义View的get和set相关方法-keepclassmembers public class * extends android.view.View {void set*(***);*** get*();}​#保持Activity中View及其子类入参的方法-keepclassmembers class * extends android.app.Activity {public void *(android.view.View);}​#枚举-keepclassmembers enum * {**[] $VALUES;public *;}​#Parcelable-keepclassmembers class * implements android.os.Parcelable {public static final android.os.Parcelable$Creator CREATOR;}​#R文件的静态成员-keepclassmembers class **.R$* {public static <fields>;}​-dontwarn android.support.**​#keep相关注解-keep class android.support.annotation.Keep​-keep @android.support.annotation.Keep class * {*;}​-keepclasseswithmembers class * {@android.support.annotation.Keep <methods>;}​-keepclasseswithmembers class * {@android.support.annotation.Keep <fields>;}​-keepclasseswithmembers class * {@android.support.annotation.Keep <init>(...);}

1. What? openGl是什么?openGl ES又是什么?

相信很多人从事开发的都或多或少听到过有关OpenGl这个东西,但是平时用的少,只知道有这么个东西,而且学起来不简单,所以大多数人都不能讲出个个所以然来。

官方对OpenGl的描述为

OpenGL(Open Graphics Library开发图形接口)是一个跨平台的图形API,用于指定3D图形处理硬件中的标准软件接口。

OpenGl的前身是SGI公司为其图形工作站开发的IRIS GL,后来因为IRIS GL的移植性不好,所以在其基础上,开发出了OpenGl。OpenGl一般用于在图形工作站,PC端使用,由于性能各方面原因,在移动端使用OpenGl基本带不动。为此,Khronos公司就为OpenGl提供了一个子集,OpenGl ES(OpenGl for Embedded(嵌入式) System)

什么是OpenGl ES呢?

OpenGl ES是免费的跨平台的功能完善的2D/3D图形库接口的API,是OpenGL的一个子集。

移动端使用到的基本上都是OpenGl ES,当然Android开发下还专门为OpenGl提供了android.opengl包,并且提供了GlSurfaceView,GLU,GlUtils等工具类。

2. How? Android中的openGL 如何使用?

在了解OpenGl的使用之前,我们需要了解两个基本类别的Android框架:GlSurfaceView和GlSurfaceView.Renderer

3. GlSurfaceView是什么? GLSurfaceView的作用是什么? GLSurfaceView如何使用?

GlSurfaceView从名字就可以看出,它是一个SurfaceView,看源码可知,GlSurfaceView继承自SurfaceView。并增加了Renderer.它的作用就是专门为OpenGl显示渲染使用的。

GLSurfaceView的使用方法:
可以通过创建的实例使用这个类,并增加你的Renderer.

@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);GLSurfaceView glSurfaceView = new GLSurfaceView(this);glSurfaceView.setRenderer(new GLSurfaceView.Renderer() {@Overridepublic void onSurfaceCreated(GL10 gl, EGLConfig config) {}@Overridepublic void onSurfaceChanged(GL10 gl, int width, int height) {}@Overridepublic void onDrawFrame(GL10 gl) {}});setContentView(glSurfaceView);}

4. GlSurfaceView.Renderer是什么?GLSurfaceView.Renderer的作用?GLSurfaceView.Renderer的用法?

该接口定义了用于绘制在图形所需的方法GLSurfaceView。你必须提供这个接口作为一个单独的类的实现,并将其连接到您的GLSurfaceView使用实例 GLSurfaceView.setRenderer()。如上面的代码所示。作用就是提供各种渲染方法,OpenGl的渲染操作均在此接口中实习。下面说下实现该接口的方法含义:

  • onSurfaceCreated():系统调用这个方法一次创建时GLSurfaceView。使用此方法来执行只需要发生一次的操作,比如设置OpenGL的环境参数或初始化的OpenGL图形对象。
  • onDrawFrame():系统调用上的每个重绘此方法GLSurfaceView。使用此方法作为主要执行点用于绘制(和重新绘制)的图形对象。
  • 系统调用此方法时的GLSurfaceView几何形状的变化,包括尺寸变化GLSurfaceView或设备屏幕的取向。例如,当设备从纵向变为横向的系统调用这个方法。使用此方法可以在变化做出反应GLSurfaceView容器。

介绍完了GlSurfaceView和GlSurfaceView.renderer之后,接下来说下如何使用GlSurfaceView;

  1. 创建一个GlSurfaceView
  2. 为这个GlSurfaceView设置渲染
  3. 在GlSurfaceView.renderer中绘制处理显示数据

5. OpenGl的简单使用实例(绘制一个三角形)

  1. 在使用OpenGl之前,需要在AndroidManifest.xml中设置OpenGl的版本:这里我们使用的是OpenGl ES 2.0,所以需要添加如下说明:
<uses-feature android:glEsVersion="0x00020000" android:required="true" />
  1. 使用GLSufaceView(上面有介绍)
  2. 具体在GlSurfaceView.Renderer中的绘制步骤:
    • 设置视图展示窗口(viewport) :在onSurfaceChanged中调用GLES20.glViewport(0, 0, width, height);
    • 创建图形类,确定好顶点位置和图形颜色,将顶点和颜色数据转换为OpenGl使用的数据格式
    • 加载顶点找色器和片段着色器用来修改图形的颜色,纹理,坐标等属性
    • 创建投影和相机视图来显示视图的显示状态,并将投影和相机视图的转换传递给着色器。
    • 创建项目(Program),连接顶点着色器片段着色器。
    • 将坐标数据传入到OpenGl ES程序中:

使用OpenGl修改背景颜色

创建一个GlSurfaceView,并为其设置渲染OneGlRenderer;

public class OneGlSurfaceView extends GLSurfaceView {private final OneGlRenderer mRenderer;public OneGlSurfaceView(Context context) {super(context);// Create an OpenGL ES 2.0 contextsetEGLContextClientVersion(2);mRenderer = new OneGlRenderer();// Set the Renderer for drawing on the GLSurfaceViewsetRenderer(mRenderer);}
}

实现渲染接口

public class OneGlRenderer implements GLSurfaceView.Renderer {public void onSurfaceCreated(GL10 unused, EGLConfig config) {// Set the background frame colorGLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);}public void onDrawFrame(GL10 unused) {// Redraw background colorGLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);}public void onSurfaceChanged(GL10 unused, int width, int height) {GLES20.glViewport(0, 0, width, height);}
}

展示渲染后的GlSurfaceView

public class OneOpenGlActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);OneGlSurfaceView glSurfaceView = new OneGlSurfaceView(this);setContentView(glSurfaceView);}
}

效果如下:就是简单给GlSurfaceView渲染一层黑色。

这里写图片描述

使用OpenGl绘制几何图形

一:图形创建

创建一个几何图形(这里主要列举三角形和正方形),需要注意一点,我们设置图形的顶点坐标后,需要将顶点坐标转为ByteBuffer,这样OpenGl才能进行图形处理。

三角形图形创建:

public class Triangle {private FloatBuffer vertexBuffer;// number of coordinates per vertex in this arraystatic final int COORDS_PER_VERTEX = 3;static float triangleCoords[] = {   // in counterclockwise order:0.0f,  0.5f, 0.0f, // top-0.5f, -0.5f, 0.0f, // bottom left0.5f, -0.5f, 0.0f  // bottom right};// Set color with red, green, blue and alpha (opacity) valuesfloat color[] = { 255, 0, 0, 1.0f };public Triangle() {// 初始化ByteBuffer,长度为arr数组的长度*4,因为一个float占4个字节ByteBuffer bb = ByteBuffer.allocateDirect(triangleCoords.length * 4);// 数组排列用nativeOrderbb.order(ByteOrder.nativeOrder());// 从ByteBuffer创建一个浮点缓冲区vertexBuffer = bb.asFloatBuffer();// 将坐标添加到FloatBuffervertexBuffer.put(triangleCoords);// 设置缓冲区来读取第一个坐标vertexBuffer.position(0);}
}

正方型图:

public class Square {private FloatBuffer vertexBuffer;private ShortBuffer drawListBuffer;// number of coordinates per vertex in this arraystatic final int COORDS_PER_VERTEX = 3;static float squareCoords[] = {-0.5f,  0.5f, 0.0f,   // top left-0.5f, -0.5f, 0.0f,   // bottom left0.5f, -0.5f, 0.0f,   // bottom right0.5f,  0.5f, 0.0f }; // top rightprivate short drawOrder[] = { 0, 1, 2, 0, 2, 3 }; // order to draw verticespublic Square() {// 初始化ByteBuffer,长度为arr数组的长度*4,因为一个float占4个字节ByteBuffer bb = ByteBuffer.allocateDirect(squareCoords.length * 4);bb.order(ByteOrder.nativeOrder());vertexBuffer = bb.asFloatBuffer();vertexBuffer.put(squareCoords);vertexBuffer.position(0);// 初始化ByteBuffer,长度为arr数组的长度*2,因为一个short占2个字节ByteBuffer dlb = ByteBuffer.allocateDirect(drawOrder.length * 2);dlb.order(ByteOrder.nativeOrder());drawListBuffer = dlb.asShortBuffer();drawListBuffer.put(drawOrder);drawListBuffer.position(0);}
}

创建图形基本没什么技巧可言,按部就班就行了,为什么数据需要转换格式呢?主要是因为Java的缓冲区数据存储结构为大端字节序(BigEdian),而OpenGl的数据为小端字节序(LittleEdian),因为数据存储结构的差异,所以,在Android中使用OpenGl的时候必须要进行下转换。当然,一般我们在使用的时候都会做个简单的工具类。这里提供几个简单的封装。(占几个字节就初始化ByteBuffer长度的时候*几)

  1. 将int[]转成IntBuffer
private IntBuffer intBufferUtil(int[] arr){IntBuffer mBuffer;// 初始化ByteBuffer,长度为arr数组的长度*4,因为一个int占4个字节ByteBuffer qbb = ByteBuffer.allocateDirect(arr.length * 4);// 数组排列用nativeOrderqbb.order(ByteOrder.nativeOrder());mBuffer = qbb.asIntBuffer();mBuffer.put(arr);mBuffer.position(0);return mBuffer;}
  1. 将float[]数组转为OpenGl 所需要的FloatBuffer
private FloatBuffer floatBufferUtil(float[] arr){FloatBuffer mBuffer;// 初始化ByteBuffer,长度为arr数组的长度*4,因为一个int占4个字节ByteBuffer qbb = ByteBuffer.allocateDirect(arr.length * 4);// 数组排列用nativeOrderqbb.order(ByteOrder.nativeOrder());mBuffer = qbb.asFloatBuffer();mBuffer.put(arr);mBuffer.position(0);return mBuffer;}
  1. 当然,依葫芦画瓢,如何将short[]转ShortBuffer这个就照着写就ok了
 private ShortBuffer shortBufferUtil(short[] arr){ShortBuffer mBuffer;// 初始化ByteBuffer,长度为arr数组的长度*2,因为一个short占2个字节ByteBuffer dlb = ByteBuffer.allocateDirect(// (# of coordinate values * 2 bytes per short)arr.length * 2);dlb.order(ByteOrder.nativeOrder());mBuffer = dlb.asShortBuffer();mBuffer.put(arr);mBuffer.position(0);return mBuffer;}

创建完形状之后,我们就要进行我们的第二步了,将这些形状渲染到GlSurfaceView中去。主要可分为下面几步:

  1. 首先我们需要在GlSurfaceView.Renderer中初始化需要渲染的几何图形
private Triangle mTriangle;private Square   mSquare;public void onSurfaceCreated(GL10 unused, EGLConfig config) {// 设置背景颜色GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);// 初始化trianglemTriangle = new Triangle();// 初始化 squaremSquare = new Square();}

二.:绘制图形,因为需要提供很多细节的图形渲染管线,所以绘制图形前至少需要一个顶点着色器来绘制形状和一个片段着色器的颜色,形状。这些着色器必须被编译,然后加入到一个OpenGL ES程序,然后将其用于绘制形状。简单介绍下这几个概念:

  • 顶点着色器(Vertex Shader)顶点着色器是GPU上运行的小程序,由名字可以知道,通过它来处理顶点,他用于渲染图形顶点的OpenGL ES图形代码。顶点着色器可用来修改图形的位置,颜色,纹理坐标,不过不能用来创建新的顶点坐标。
  • 片段着色器(Fragment Shader ) 用于呈现与颜色或纹理的形状的面的OpenGL ES代码。
  • 项目(Program) -包含要用于绘制一个或多个形状着色器的OpenGL ES的对象。

下面给Triangle类定义一个基本的着色器代码:

public class Triangle {private final String vertexShaderCode ="attribute vec4 vPosition;" +"void main() {" +"  gl_Position = vPosition;" +"}";private final String fragmentShaderCode ="precision mediump float;" +"uniform vec4 vColor;" +"void main() {" +"  gl_FragColor = vColor;" +"}";...
}

当然,上面我们创建了着色器的编译代码,代码编写完成,需要写个方法来执行这段代码,这里我们在渲染器中写一个如下方法来执行着色器代码:

public static int loadShader(int type, String shaderCode){// 创造顶点着色器类型(GLES20.GL_VERTEX_SHADER)// 或者是片段着色器类型 (GLES20.GL_FRAGMENT_SHADER)int shader = GLES20.glCreateShader(type);// 添加上面编写的着色器代码并编译它GLES20.glShaderSource(shader, shaderCode);GLES20.glCompileShader(shader);return shader;}

这里有一点需要注意,因为着色器的代码执行是很昂贵滴,所以避免多次执行,需要我们一般将执行代码的逻辑写带图形类的构造方法中。比如上面的Triangle,我们就这么写:

private final int mProgram;
public Triangle() {... ...//数据转换int vertexShader = OneGlRenderer.loadShader(GLES20.GL_VERTEX_SHADER,vertexShaderCode);int fragmentShader = OneGlRenderer.loadShader(GLES20.GL_FRAGMENT_SHADER,fragmentShaderCode);// 创建空的OpenGL ES程序mProgram = GLES20.glCreateProgram();// 添加顶点着色器到程序中GLES20.glAttachShader(mProgram, vertexShader);// 添加片段着色器到程序中GLES20.glAttachShader(mProgram, fragmentShader);// 创建OpenGL ES程序可执行文件GLES20.glLinkProgram(mProgram);}

最后,所有绘制的所有基本配置都配置完成之后,我们来写绘制图形的方法,我们在图形类(Triangle)中创建一个绘制的方法onDraw(),可以在onDraw()方法中设置绘制逻辑。

private int mPositionHandle;private int mColorHandle;private final int vertexCount = triangleCoords.length / COORDS_PER_VERTEX;private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertexpublic void draw() {// 将程序添加到OpenGL ES环境GLES20.glUseProgram(mProgram);// 获取顶点着色器的位置的句柄mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");// 启用三角形顶点位置的句柄GLES20.glEnableVertexAttribArray(mPositionHandle);//准备三角形坐标数据GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX,GLES20.GL_FLOAT, false,vertexStride, vertexBuffer);// 获取片段着色器的颜色的句柄mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");// 设置绘制三角形的颜色GLES20.glUniform4fv(mColorHandle, 1, color, 0);// 绘制三角形GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);// 禁用顶点数组GLES20.glDisableVertexAttribArray(mPositionHandle);}

完成上面所有步骤,只需要在GlSurfaceView.Renderer的onDrawFrame()方法中调用图形类的绘制方法即可(上面的onDraw()):

 public void onDrawFrame(GL10 unused) {        mTriangle.draw();    }

最后的呈现效果如下图所示:

这里写图片描述

运用投影和相机视图

通常情况下,OpenGl中展示的视图和在Android上显示的图形会有偏差。借用官方图片:

这里写图片描述

当然我们可以通过矩阵转换来解决这种问题,让OpenGl上的视图在任何android设备上显示的比例都是一样的,这里说下什么是投影和相机视图:

投影的定义

使用OpenGl绘制的3D图形,需要展示在移动端2D设备上,这就是投影。Android OpenGl ES中有两种投影方式:一种是正交投影,一种是透视投影:

正交投影投影物体的带下不会随观察点的远近而发生变化,我们可以使用下面方法来执行正交投影:

Matrix.orthoM (float[] m,           //接收正交投影的变换矩阵int mOffset,        //变换矩阵的起始位置(偏移量)float left,         //相对观察点近面的左边距float right,        //相对观察点近面的右边距float bottom,       //相对观察点近面的下边距float top,          //相对观察点近面的上边距float near,         //相对观察点近面距离float far)          //相对观察点远面距离

透视投影:随观察点的距离变化而变化,观察点越远,视图越小,反之越大,我们可以通过如下方法来设置透视投影:

Matrix.frustumM (float[] m,         //接收透视投影的变换矩阵int mOffset,        //变换矩阵的起始位置(偏移量)float left,         //相对观察点近面的左边距float right,        //相对观察点近面的右边距float bottom,       //相对观察点近面的下边距float top,          //相对观察点近面的上边距float near,         //相对观察点近面距离float far)          //相对观察点远面距离

相机视图

什么是相机视图?简单来说生活中我们拍照,你站的高度,拿相机的位置,姿势不同,拍出来的照片也就不一样,相机视图就是来修改相机位置,观察方式以及相机的倾斜角度等属性。我们可以通过下面方法来修改相机视图属性:

Matrix.setLookAtM (float[] rm,      //接收相机变换矩阵int rmOffset,       //变换矩阵的起始位置(偏移量)float eyeX,float eyeY, float eyeZ,   //相机位置float centerX,float centerY,float centerZ,  //观察点位置float upX,float upY,float upZ)  //up向量在xyz上的分量

转换矩阵(变换矩阵)

转换矩阵用来做什么的呢?是否记得上面我们绘制的图形坐标需要转换为OpenGl中能处理的小端字节序(LittleEdian),没错,转换矩阵就是用来将数据转为OpenGl ES可用的数据字节,我们将相机视图和投影设置的数据相乘,便得到一个转换矩阵,然后我们再讲此矩阵传给顶点着色器,具体使用方法及参数说明如下:

Matrix.multiplyMM (float[] result, //接收相乘结果int resultOffset,  //接收矩阵的起始位置(偏移量)float[] lhs,       //左矩阵int lhsOffset,     //左矩阵的起始位置(偏移量)float[] rhs,       //右矩阵int rhsOffset)     //右矩阵的起始位置(偏移量)

下面简单讲解下如何使用投影和相机视图来实现矩阵变换并传递给顶点着色器;

  1. 定义一个投影:
 // mMVPMatrix is an abbreviation for "Model View Projection Matrix"private final float[] mMVPMatrix = new float[16];private final float[] mProjectionMatrix = new float[16];private final float[] mViewMatrix = new float[16];public void onSurfaceChanged(GL10 unused, int width, int height) {GLES20.glViewport(0, 0, width, height);float ratio = (float) width / height;// 这个投影矩阵被应用于对象坐标在onDrawFrame()方法中Matrix.frustumM(mProjectionMatrix, 0, -ratio, ratio, -1, 1, 3, 7);}
  1. 定义一个相机视图
@Override
public void onDrawFrame(GL10 unused) {...// Set the camera position (View matrix)Matrix.setLookAtM(mViewMatrix, 0, 0, 0, -3, 0f, 0f, 0f, 0f, 1.0f, 0.0f);// Calculate the projection and view transformationMatrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mViewMatrix, 0);// Draw shapemTriangle.draw(mMVPMatrix);
}
  1. 修改图形类执行代码
public class Triangle {    private final String vertexShaderCode =        // This matrix member variable provides a hook to manipulate        // the coordinates of the objects that use this vertex shader        "uniform mat4 uMVPMatrix;" +        "attribute vec4 vPosition;" +        "void main() {" +        // the matrix must be included as a modifier of gl_Position        // Note that the uMVPMatrix factor *must be first* in order        // for the matrix multiplication product to be correct.        "  gl_Position = uMVPMatrix * vPosition;" +        "}";    // Use to access and set the view transformation    private int mMVPMatrixHandle;    ...}
  1. 投影和相机视图代码到图形类的绘制方法中去onDraw()
public class Triangle {private final String vertexShaderCode =// This matrix member variable provides a hook to manipulate// the coordinates of the objects that use this vertex shader"uniform mat4 uMVPMatrix;" +"attribute vec4 vPosition;" +"void main() {" +// the matrix must be included as a modifier of gl_Position// Note that the uMVPMatrix factor *must be first* in order// for the matrix multiplication product to be correct."  gl_Position = uMVPMatrix * vPosition;" +"}";// Use to access and set the view transformationprivate int mMVPMatrixHandle;...
}
  1. 投影和相机视图代码到图形类的绘制方法中去onDraw()
 public void draw(float[] mvpMatrix){... ...// 得到形状的变换矩阵的句柄mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");// 将投影和视图转换传递给着色器GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);// 画三角形GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);// 禁用顶点数组GLES20.glDisableVertexAttribArray(mPositionHandle);}

做完这些,我们就能得到如下图:

这里写图片描述

没错,这才没有变形的视图。到这里,基本的通过OpenGl绘制简单图形就over了,下面我们讲解下如何添加一些交互动作。

添加动作

前面都是简单的动作介绍,使用OpenGl在屏幕上绘制对象是使用openGl的基本功。下面我来说下如何添加旋转形状。使用OpenGl的描绘对象是相对简单的,首先需要在渲染器中创建一组旋转矩阵,然后使用之前提到过的投影和相机视图变换矩阵结合起来使用:

private float[] mRotationMatrix = new float[16];
public void onDrawFrame(GL10 gl) {float[] scratch = new float[16];...// 创建一个旋转矩阵long time = SystemClock.uptimeMillis() % 4000L;float angle = 0.090f * ((int) time);Matrix.setRotateM(mRotationMatrix, 0, angle, 0, 0, -1.0f);// 将旋转矩阵与投影和相机视图组合在一起// Note that the mMVPMatrix factor *must be first* in order// for the matrix multiplication product to be correct.Matrix.multiplyMM(scratch, 0, mMVPMatrix, 0, mRotationMatrix, 0);// Draw trianglemTriangle.draw(scratch);
}

运行效果图如下:

这里写图片描述

153); font-size: 0.7em;">这里写图片描述</figcaption>

修改顶点颜色

一个颜色是不是太单调了?如何让做成多彩的呢?接下来我们来做一个多彩三角形,如何来做一个多彩三角形?我们通过顶点着色器来做。基于上面的代码,我们只需要做一点点改动,下面是基本步骤:

  1. 修改着色器代码
  2. 将颜色值修改为float数组并转为floatBuffer
  3. 将获取的floatBuffer传递给顶点着色器。

修改着色器代码:

 private final String vertexShaderCode ="attribute vec4 vPosition;" +"uniform mat4 uMVPMatrix;"+"varying  vec4 vColor;"+"attribute vec4 aColor;"+"void main() {" +"  gl_Position = uMVPMatrix*vPosition;" +"  vColor=aColor;"+"}";private final String fragmentShaderCode ="precision mediump float;" +"varying vec4 vColor;" +"void main() {" +"  gl_FragColor = vColor;" +"}";

shader的变量类型(uniform,attribute和varying)的区别

关于shader的变量类型(uniform,attribute和varying)的区别及使用,下面做下说明:

  1. uniform:uniform变量在vertex和fragment两者之间声明方式完全一样,则它可以在vertex和fragment共享使用。(相当于一个被vertex和fragment shader共享的全局变量)uniform变量一般用来表示:变换矩阵,材质,光照参数和颜色等信息。在代码中通过GLES20.glGetUniformLocation(int program, String name)来获取属性值。并通过 GLES20.glUniformMatrix4fv(int location, int count, boolean transpose, float[] value, int offset);方法将数据传递给着色器。
  2. attribute:这个变量只能在顶点着色器中使用(vertex Shader),用来表示顶点的数据,比如顶点坐标,顶点颜色,法线,纹理坐标等。在绘制的时候通过GLES20.glGetAttribLocation(int program, String name)来获取变量值,通过 GLES20.glEnableVertexAttribArray(int index)来启动句柄,最后通过 GLES20.glVertexAttribPointer(int indx,int size,int type,boolean normalized,int stride,java.nio.Buffer ptr)来设置图形数据。
  3. varying变量:这个变量只能用来在vertex和fragment shader之间传递数据时使用,不可以通过代码获取其变量值。

接来下我们进行数据转换:

float color[] = {1.0f, 0f, 0f, 1.0f ,0f, 1.0f, 0f, 1.0f ,0f, 0f, 1.0f, 1.0f};public Triangle() {... ...ByteBuffer dd = ByteBuffer.allocateDirect(color.length * 4);dd.order(ByteOrder.nativeOrder());colorBuffer = dd.asFloatBuffer();colorBuffer.put(color);colorBuffer.position(0);}

最后我们需要获取着色器的句柄并设置着色器的颜色:

public void draw(float[] mvpMatrix){... ... /* // 获取片段着色器的vColor成员的句柄mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");// 设置绘制三角形的颜色GLES20.glUniform4fv(mColorHandle, 1, colorBuffer, 0);*///获取片元着色器的vColor成员的句柄mColorHandle = GLES20.glGetAttribLocation(mProgram, "aColor");//设置绘制三角形的颜色GLES20.glEnableVertexAttribArray(mColorHandle);GLES20.glVertexAttribPointer(mColorHandle,4,GLES20.GL_FLOAT,false,0,colorBuffer);... ...}

这里写图片描述

153); font-size: 0.7em;">这里写图片描述</figcaption>

SurfaceView简单理解,Android混淆,Android openGl开发详解简单图形的绘制,相关推荐

  1. Android openGl开发详解(二)——通过SurfaceView,TextureView,GlSurfaceView显示相机预览(附Demo)

    最近公司在做自定义相机这一块,之前使用的是第三方,后来需求变更,第三方不支持添加动态贴纸,所以只能自己扩展.当然网上有很多例子,但是关于添加动态贴纸的例子几乎找不到,反正我是没找到(欲哭无泪).当然, ...

  2. Android openGl开发详解(二)

    https://zhuanlan.zhihu.com/p/35192609 Android openGl开发详解(二)--通过SurfaceView,TextureView,GlSurfaceView ...

  3. Android 系统(252)---Android:BLE智能硬件开发详解

    Android:BLE智能硬件开发详解 目录 前言 BLE是个什么鬼 BLE中的角色分工 主要的关键词和概念  GATT(Generic Attribute Profile ) Characteris ...

  4. iPhone和Android的WEB应用开发详解

    iPhone和Android的WEB应用开发详解 在我们现在的生活中,移动设备的作用日益重要.我们使用它们进行交流.我们使用它们进行导航.我们甚至可以将它们用作方便的手电筒.面向 iPhone 和 A ...

  5. Android个人手机通讯录开发详解

    一.Android 个人手机通讯录开发 数据存储:SQLite 数据库 开发工具:Android Studio 二.Phone Module 简介 界面展示 文件结构简单分析 三.个人手机通讯录代码实 ...

  6. 【OpenCV 4开发详解】图像直方图绘制

    本文首发于"小白学视觉"微信公众号,欢迎关注公众号 本文作者为小白,版权归人民邮电出版社发行所有,禁止转载,侵权必究! 经过几个月的努力,小白终于完成了市面上第一本OpenCV 4 ...

  7. 【OpenCV 4开发详解】图像上绘制几何图形

    本文首发于"小白学视觉"微信公众号,欢迎关注公众号 本文作者为小白,版权归人民邮电出版社发行所有,禁止转载,侵权必究! 经过几个月的努力,小白终于完成了市面上第一本OpenCV 4 ...

  8. Android openGl开发详解(一)——绘制简单图形

    1. What? openGl是什么?openGl ES又是什么? 2. How? Android中的openGL 如何使用? 3. GlSurfaceView是什么? GLSurfaceView的作 ...

  9. Android自定义定时闹钟开发详解

    这篇文章主要为大家详细介绍了Android自定义定时闹钟开发,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下 本文实例为大家分享了Android开发之自定义闹钟实现,供大 ...

最新文章

  1. python装饰器-python中的装饰器常用于哪些应用场景
  2. WebService大讲堂之Axis2(6):跨服务会话(Session)管理
  3. 在一个千万级的数据库查寻中,如何提高查询效率?
  4. Linux日志系统-03:logrotate主配置文件详解
  5. python从入门到实践笔记_Python编程 从入门到实践 #笔记#
  6. 不止代码:迷宫问题(bfs)
  7. 3-4:常见任务和主要工具之文件搜索
  8. 合并excel文件 C语言,如何用VBA函数合并多个文件
  9. CWnd类与Windows窗口的关系-3、CWnd类如何封装Windows窗口
  10. 默纳克系统服务器怎么看抱闸,默纳克抱闸制动力检测怎么关闭
  11. [luoguP2679] 子串(DP)
  12. pytorch项目代码总结
  13. 多窗口下的批量操作脚本 - 利用win32gui和WindowSpy++简单实现目标窗口前置
  14. VB6基础教程与源代码
  15. 机器视觉技术在当前各行各业中的应用
  16. 阿里云(企业云解析DNS)让你的博客飞起来
  17. 为啥淘宝的商品链接无法分享到微信?
  18. 《觉醒》:头脑编程与全息宇宙 大卫·艾克
  19. 中电信启动超大数据中心交换机招标
  20. java程序调用时的调用规则,Java程序调用ILog规则出错

热门文章

  1. tcga数据下载_从GDC下载最新的TCGA临床数据
  2. 节点的nodeType属性
  3. 南瓜先生大冒险游戏如何?
  4. 如何使用Jpcap 包实现网络监听
  5. java tree 树的代码实现
  6. Python smtp发邮件提示错误554, b'DT:SPM 163 smtp1
  7. 1046 划拳 (15 分) (C语言)
  8. ensembl数据库mysql图_Ensembl数据库的使用
  9. 数据挖掘 之 数据可视化与数据分析
  10. 表单事件(城市列表等)