Android Lesson One: Getting Started

RK3572-Android16-记录一次OTA升级失败解决方案 这里记录一次解决RK3576 Android16 OTA 用Recovery 升级失败的一次解决案例,将部分经验作为总结。本次OTA升级耗时了几天,最终是RK给出的解决方案,再加上经验来解决了其它部分问题新平台,高一点的Android版本,可能本身有bug,多请教平台来,不要蒙着干,干不出来的。 阅读详情

原文链接地址:http://www.learnopengles.com/android-lesson-one-getting-started/

 

This is the first tutorial on using OpenGL ES 2 on Android. In this lesson, we’re going to go over the code step-by-step, and look at how to create an OpenGL ES 2 context and draw to the screen. We’ll also take a look at what shaders are and how they work, as well as how matrices are used to transform the scene into the image you see on the screen. Finally, we’ll look at what you need to add to the manifest to let the Android market know that you’re using OpenGL ES 2.

Prerequisites

Before we begin, you’ll want to make sure you have the following tools installed on your machine:

Unfortunately, the Android emulator does not support OpenGL ES 2, so you’ll need access to an actual Android device in order to run the tutorial. Most recent devices should support OpenGL ES 2 these days, so all you need to do is enable developer mode and hook the phone up to your machine.

Assumptions

The reader should be familiar with Android and Java on a basic level. TheAndroid Tutorials are a good place to start.

Getting started

We’ll go over all of the code below and explain what each part does. You can copy the code on a segment by segment basis by creating your own project, or you can download the completed project at the end of the lesson. Once you have the tools installed, go ahead and create a new Android project in Eclipse. The names don’t matter, but for the lesson I will be referring to the main activity asLessonOneActivity.

Let’s take a look at the code:

/** Hold a reference to our GLSurfaceView */
private GLSurfaceView mGLSurfaceView; 

The GLSurfaceView is a special view which manages OpenGL surfaces for us and draws it into the Android view system. It also adds a lot of features which make it easier to use OpenGL, including but not limited to:

  • It provides a dedicated render thread for OpenGL so that the main thread is not stalled.
  • It supports continuous or on-demand rendering.
  • It takes care of the screen setup for you using EGL, the interface between OpenGL and the underlying window system.

The GLSurfaceView makes setting up and using OpenGL from Android relatively painless.

@Override
public void onCreate(Bundle savedInstanceState) 
{ 
    super.onCreate(savedInstanceState);
 
    mGLSurfaceView =new GLSurfaceView(this);
 
    // Check if the system supports OpenGL ES 2.0.
    finalActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
    finalConfigurationInfo configurationInfo = activityManager.getDeviceConfigurationInfo();
    finalboolean supportsEs2 = configurationInfo.reqGlEsVersion >=0x20000; 
 
    if(supportsEs2) 
    {
        // Request an OpenGL ES 2.0 compatible context.
        mGLSurfaceView.setEGLContextClientVersion(2);
 
        // Set the renderer to our demo renderer, defined below.
        mGLSurfaceView.setRenderer(newLessonOneRenderer()); 
    }
    else
    {
        // This is where you could create an OpenGL ES 1.x compatible
        // renderer if you wanted to support both ES 1 and ES 2.
        return;
    }
 
    setContentView(mGLSurfaceView);
} 
 

The onCreate() of our activity is the important part where our OpenGL context gets created and where everything starts happening. In our onCreate(), the first thing we do after calling the superclass is creating our GLSurfaceView. We then need to figure out if the system supports OpenGL ES 2. To do this, we get an ActivityManager instance which lets us interact with the global system state. We can then use this to get the device configuration info, which will tell us if the device supports OpenGL ES 2.

Once we know if the device supports OpenGL ES 2 or not, we tell the surface view we want an OpenGL ES 2 compatible surface and then we pass in a custom renderer. This renderer will be called by the system whenever it’s time to adjust the surface or draw a new frame. We can also support OpenGL Es 1.x by passing in a different renderer, though we would need to write different code as the APIs are different. For this lesson we’ll only look at supporting OpenGL ES 2.

Finally, we set the content view to our GLSurfaceView, which tells Android that the activity’s contents should be filled by our OpenGL surface. To get into OpenGL, it’s as easy as that!

@Override
protectedvoid onResume() 
{ 
    // The activity must call the GL surface view's onResume() on activity onResume().
    super.onResume();
    mGLSurfaceView.onResume();
} 
 
@Override
protectedvoid onPause() 
{ 
    // The activity must call the GL surface view's onPause() on activity onPause().
    super.onPause();
    mGLSurfaceView.onPause();
}

GLSurfaceView requires that we call its onResume() and onPause() methods whenever the parent Activity has its own onResume() and onPaused() called. We add in the calls here to round out our activity.

Visualizing a 3D world

In this section, we’ll start looking at how OpenGL ES 2 works and how we can start drawing stuff onto the screen. In the activity we passed in a customGLSurfaceView.Renderer to the GLSurfaceView, which will be defined here. The renderer has three important methods which will be automatically called by the system whenever events happen:

public void onSurfaceCreated(GL10 glUnused, EGLConfig config)
This method is called when the surface is first created. It will also be called if we lose our surface context and it is later recreated by the system.
public void onSurfaceChanged(GL10 glUnused, int width, int height)
This is called whenever the surface changes; for example, when switching from portrait to landscape. It is also called after the surface has been created.
public void onDrawFrame(GL10 glUnused)
This is called whenever it’s time to draw a new frame.

You may have noticed that the GL10 instance passed in is referred to as glUnused. We don’t use this when drawing using OpenGL ES 2; instead, we use the static methods of the class GLES20. The GL10 parameter is only there because the same interface is used for OpenGL ES 1.x.

Before our renderer can display anything, we’ll need to have something to display. In OpenGL ES 2, we pass in stuff to display by specifying arrays of numbers. These numbers can represent positions, colors, or anything else we need them to. In this demo, we’ll display three triangles.

// New class members
/** Store our model data in a float buffer. */
private final FloatBuffer mTriangle1Vertices; 
private final FloatBuffer mTriangle2Vertices; 
private final FloatBuffer mTriangle3Vertices; 
 
/** How many bytes per float. */
private final int mBytesPerFloat =4; 
 
/** 
 * Initialize the model data.
 */
public LessonOneRenderer() 
{ 
    // This triangle is red, green, and blue.
    finalfloat[] triangle1VerticesData = {
            // X, Y, Z,
            // R, G, B, A
            -0.5f, -0.25f,0.0f, 
            1.0f,0.0f, 0.0f,1.0f, 
 
            0.5f, -0.25f,0.0f, 
            0.0f,0.0f, 1.0f,1.0f, 
 
            0.0f,0.559016994f, 0.0f, 
            0.0f,1.0f, 0.0f,1.0f}; 
 
    ...
 
    // Initialize the buffers.
    mTriangle1Vertices = ByteBuffer.allocateDirect(triangle1VerticesData.length * mBytesPerFloat)
    .order(ByteOrder.nativeOrder()).asFloatBuffer();
 
    ...
 
    mTriangle1Vertices.put(triangle1VerticesData).position(0);
 
    ...
}

So, what’s all this about? If you’ve ever used OpenGL 1, you might be used to doing things this way:

glBegin(GL_TRIANGLES);
glVertex3f(-0.5f, -0.25f, 0.0f);
glColor3f(1.0f, 0.0f, 0.0f);
... 
glEnd(); 

Things don’t work that way in OpenGL ES 2. Instead of defining points via a bunch of method calls, we define an array instead. Let’s take a look at our array again:

final float[] triangle1VerticesData = { 
                // X, Y, Z,
                // R, G, B, A
                -0.5f, -0.25f,0.0f,
                1.0f,0.0f,0.0f,1.0f,
                ... 

This represents one point of the triangle. We’ve set things up so that the first three numbers represent the position (X, Y, and Z), and the last four numbers represent the color (red, green, blue, and alpha (transparency)). You don’t need to worry too much about how this array is defined; just remember that when we want to draw stuff in OpenGL ES 2, we need to pass it data in chunks instead of passing it in one at a time.

Understanding buffers
// Initialize the buffers.
        mTriangle1Vertices = ByteBuffer.allocateDirect(triangle1VerticesData.length * mBytesPerFloat)
        .order(ByteOrder.nativeOrder()).asFloatBuffer();
        ...
        mTriangle1Vertices.put(triangle1VerticesData).position(0);

We do our coding in Java on Android, but the underlying implementation of OpenGL ES 2 is actually written in C. Before we pass our data to OpenGL, we need to convert it into a form that it’s going to understand. Java and the native system might not store their bytes in the same order, so we use a special set of buffer classes and create a ByteBuffer large enough to hold our data, and tell it to store its data using the native byte order. We then convert it into a FloatBuffer so that we can use it to hold floating-point data. Finally, we copy our array into the buffer.

This buffer stuff might seem confusing (it was to me when I first came across it!), but just remember that it’s an extra step we need to do before passing our data to OpenGL. Our buffers are now ready to be used to pass data into OpenGL.

As a side note, float buffers are slow on Froyo and moderately faster on Gingerbread, so you probably don’t want to be changing them too often.

 

Understanding matrices
// New class definitions
 /**
 * Store the view matrix. This can be thought of as our camera. This matrix transforms world space to eye space;
 * it positions things relative to our eye.
 */
private float[] mViewMatrix = newfloat[16];
 
@Override
public void onSurfaceCreated(GL10 glUnused, EGLConfig config)
{ 
    // Set the background clear color to gray.
    GLES20.glClearColor(0.5f,0.5f,0.5f,0.5f);
 
    // Position the eye behind the origin.
    finalfloateyeX = 0.0f;
    finalfloateyeY = 0.0f;
    finalfloateyeZ = 1.5f;
 
    // We are looking toward the distance
    finalfloatlookX = 0.0f;
    finalfloatlookY = 0.0f;
    finalfloatlookZ = -5.0f;
 
    // Set our up vector. This is where our head would be pointing were we holding the camera.
    finalfloatupX = 0.0f;
    finalfloatupY = 1.0f;
    finalfloatupZ = 0.0f;
 
    // Set the view matrix. This matrix can be said to represent the camera position.
    // NOTE: In OpenGL 1, a ModelView matrix is used, which is a combination of a model and
    // view matrix. In OpenGL 2, we can keep track of these matrices separately if we choose.
    Matrix.setLookAtM(mViewMatrix,0, eyeX, eyeY, eyeZ, lookX, lookY, lookZ, upX, upY, upZ);
 
    ...

Another ‘fun’ topic is matrices! These will become your best friends whenever you do 3D programming, so you’ll want to get to know them well.

When our surface is created, the first thing we do is set our clear color to gray. The alpha part has also been set to gray, but we’re not doing alpha blending in this lesson so this value is unused. We only need to set the clear color once since we will not be changing it later.

The second thing we do is setup our view matrix. There are several different kinds of matrices we use and they all do something important:

  1. The model matrix. This matrix is used to place a model somewhere in the “world”. For example, if you have a model of a car and you want it located 1000 meters to the east, you will use the model matrix to do this.
  2. The view matrix. This matrix represents the camera. If we want to view our car which is 1000 meters to the east, we’ll have to move ourselves 1000 meters to the east as well (another way of thinking about it is that we remain stationary, and the rest of the world moves 1000 meters to the west). We use the view matrix to do this.
  3. The projection matrix. Since our screens are flat, we need to do a final transformation to “project” our view onto our screen and get that nice 3D perspective. This is what the projection matrix is used for.

A good explanation of this can be found over at SongHo’s OpenGL Tutorials. I recommend reading it a few times until you grasp the idea well; don’t worry, it took me a few reads as well!

In OpenGL 1, the model and view matrices are combined and the camera is assumed to be at (0, 0, 0) and facing the -Z direction.

We don’t need to construct these matrices by hand. Android has a Matrix helper class which can do the heavy lifting for us. Here, I create a view matrix for a camera which is positioned behind the origin and looking toward the distance.

Defining vertex and fragment shaders
final String vertexShader = 
    "uniform mat4 u_MVPMatrix;      \n"    // A constant representing the combined model/view/projection matrix.
 
  +"attribute vec4 a_Position;     \n"    // Per-vertex position information we will pass in. 
  +"attribute vec4 a_Color;        \n"    // Per-vertex color information we will pass in. 
 
  +"varying vec4 v_Color;          \n"    // This will be passed into the fragment shader. 
 
  +"void main()                    \n"    // The entry point for our vertex shader. 
  +"{                              \n"
  +"   v_Color = a_Color;          \n"    // Pass the color through to the fragment shader. 
                                            // It will be interpolated across the triangle.
  +"   gl_Position = u_MVPMatrix   \n"    // gl_Position is a special variable used to store the final position.
  +"               * a_Position;   \n"    // Multiply the vertex by the matrix to get the final point in
  +"}                              \n";   // normalized screen coordinates.

In OpenGL ES 2, anything we want to display on the screen is first going to have to go through a vertex and fragment shader. The good thing is that these shaders are really not as complicated as they appear. Vertex shaders perform operations on each vertex, and the results of these operations are used in the fragment shaders which do additional calculations per pixel.

Each shader basically consists of input, output, and a program. First we define a uniform, which is a combined matrix containing all of our transformations. This is a constant across all vertices and is used to project them onto the screen. Then we define two attributes for position and color. These attributes will be read in from the buffer we defined earlier on, and specify the position and color of each vertex. We then define a varying, which interpolates values across the triangle and passes it on to the fragment shader. When it gets to the fragment shader, it will hold an interpolated value for each pixel.

Let’s say we defined a triangle with each point being red, green, and blue, and we sized it so that it will take up 10 pixels on the screen. When the fragment shader runs, it will contain a different varying color for each pixel. At one point, that varying will be red, but halfway between red and blue it may be a more purplish color.

Aside from setting the color, we also tell OpenGL what the final position of the vertex should be on the screen. Then we define the fragment shader:

final String fragmentShader = 
    "precision mediump float;       \n"    // Set the default precision to medium. We don't need as high of a
                                            // precision in the fragment shader.
  +"varying vec4 v_Color;          \n"    // This is the color from the vertex shader interpolated across the
                                            // triangle per fragment.
  +"void main()                    \n"    // The entry point for our fragment shader. 
  +"{                              \n"
  +"   gl_FragColor = v_Color;     \n"    // Pass the color directly through the pipeline. 
  +"}                              \n"; 


This is the fragment shader which will actually put stuff on the screen. In this shader, we grab the varying color from the vertex shader, and just pass it straight through to OpenGL. The point is already interpolated per pixel since the fragment shader runs for each pixel that will be drawn.

More information can be found on the OpenGL ES 2 quick reference card.

Loading shaders into OpenGL
// Load in the vertex shader.
int vertexShaderHandle = GLES20.glCreateShader(GLES20.GL_VERTEX_SHADER); 
 
if (vertexShaderHandle != 0)
{ 
    // Pass in the shader source.
    GLES20.glShaderSource(vertexShaderHandle, vertexShader);
 
    // Compile the shader.
    GLES20.glCompileShader(vertexShaderHandle);
 
    // Get the compilation status.
    finalint[] compileStatus =new int[1];
    GLES20.glGetShaderiv(vertexShaderHandle, GLES20.GL_COMPILE_STATUS, compileStatus,0);
 
    // If the compilation failed, delete the shader.
    if(compileStatus[0] ==0)
    {
        GLES20.glDeleteShader(vertexShaderHandle);
        vertexShaderHandle =0;
    }
} 
 
if (vertexShaderHandle == 0)
{ 
    thrownewRuntimeException("Error creating vertex shader.");
}

First, we create the shader object. If this succeeded, we’ll get a reference to the object. Then we use this reference to pass in the shader source code, and then we compile it. We can obtain the status from OpenGL and see if it compiled successfully. If there were errors, we can use GLES20.glGetShaderInfoLog(shader) to find out why. We follow the same steps to load the fragment shader.

Linking a vertex and fragment shader together into a program
// Create a program object and store the handle to it.
int programHandle = GLES20.glCreateProgram(); 
 
if (programHandle != 0)
{ 
    // Bind the vertex shader to the program.
    GLES20.glAttachShader(programHandle, vertexShaderHandle);
 
    // Bind the fragment shader to the program.
    GLES20.glAttachShader(programHandle, fragmentShaderHandle);
 
    // Bind attributes
    GLES20.glBindAttribLocation(programHandle,0,"a_Position");
    GLES20.glBindAttribLocation(programHandle,1,"a_Color");
 
    // Link the two shaders together into a program.
    GLES20.glLinkProgram(programHandle);
 
    // Get the link status.
    finalint[] linkStatus =new int[1];
    GLES20.glGetProgramiv(programHandle, GLES20.GL_LINK_STATUS, linkStatus,0);
 
    // If the link failed, delete the program.
    if(linkStatus[0] ==0)
    {
        GLES20.glDeleteProgram(programHandle);
        programHandle =0;
    }
} 
 
if (programHandle == 0)
{ 
    thrownewRuntimeException("Error creating program.");
}

Before we can use our vertex and fragment shader, we need to bind them together into a program. This is what connects the output of the vertex shader with the input of the fragment shader. It’s also what lets us pass in input from our program and use the shader to draw our shapes.

We create a new program object, and if that succeeded, we then attach our shaders. We want to pass in the position and color as attributes, so we need to bind these attributes. We then link the shaders together.

//New class members
/** This will be used to pass in the transformation matrix. */
private int mMVPMatrixHandle; 
 
/** This will be used to pass in model position information. */
private int mPositionHandle; 
 
/** This will be used to pass in model color information. */
private int mColorHandle; 
 
@Override
public void onSurfaceCreated(GL10 glUnused, EGLConfig config)
{ 
    ...
 
    // Set program handles. These will later be used to pass in values to the program.
    mMVPMatrixHandle = GLES20.glGetUniformLocation(programHandle,"u_MVPMatrix");
    mPositionHandle = GLES20.glGetAttribLocation(programHandle,"a_Position");
    mColorHandle = GLES20.glGetAttribLocation(programHandle,"a_Color");
 
    // Tell OpenGL to use this program when rendering.
    GLES20.glUseProgram(programHandle);
}

After we successfully linked our program, we finish up with a couple more tasks so we can actually use it. The first task is obtaining references so we can pass data into the program. Then we tell OpenGL to use this program when drawing. Since we only use one program in this lesson, we can put this in the onSurfaceCreated() instead of the onDrawFrame().

Setting the perspective projection
// New class members
/** Store the projection matrix. This is used to project the scene onto a 2D viewport. */
private float[] mProjectionMatrix = newfloat[16];
 
@Override
public void onSurfaceChanged(GL10 glUnused, intwidth,int height) 
{ 
    // Set the OpenGL viewport to the same size as the surface.
    GLES20.glViewport(0,0, width, height);
 
    // Create a new perspective projection matrix. The height will stay the same
    // while the width will vary as per aspect ratio.
    finalfloatratio = (float) width / height;
    finalfloatleft = -ratio; 
    finalfloatright = ratio; 
    finalfloatbottom = -1.0f;
    finalfloattop = 1.0f;
    finalfloatnear = 1.0f;
    finalfloatfar = 10.0f;
 
    Matrix.frustumM(mProjectionMatrix,0, left, right, bottom, top, near, far);
}

Our onSurfaceChanged() is called at least once and also whenever our surface is changed. Since we only need to reset our projection matrix whenever the screen we’re projecting onto has changed, onSurfaceChanged() is an ideal place to do it.

Drawing stuff to the screen!
    // New class members
    /**
     * Store the model matrix. This matrix is used to move models from object space (where each model can be thought
     * of being located at the center of the universe) to world space.
     */
    privatefloat[] mModelMatrix =newfloat[16];
  
    @Override
    publicvoidonDrawFrame(GL10 glUnused)
    {
        GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
  
        // Do a complete rotation every 10 seconds.
        longtime = SystemClock.uptimeMillis() % 10000L;
        floatangleInDegrees = (360.0f /10000.0f) * ((int) time);
  
        // Draw the triangle facing straight on.
        Matrix.setIdentityM(mModelMatrix,0);
        Matrix.rotateM(mModelMatrix,0, angleInDegrees,0.0f,0.0f,1.0f);
        drawTriangle(mTriangle1Vertices);
  
        ...
}

This is where stuff actually get displayed on the screen. We clear the screen so we don’t get any weirdhall of mirror effects, and we want our triangles to animate smoothly so we rotate them using time. Whenever you animate something on the screen, it’s usually better to use time instead of framerate.

The actual drawing is done in drawTriangle:

// New class members
/** Allocate storage for the final combined matrix. This will be passed into the shader program. */
private float[] mMVPMatrix = newfloat[16];
 
/** How many elements per vertex. */
private final int mStrideBytes =7* mBytesPerFloat;
 
/** Offset of the position data. */
private final int mPositionOffset =0;
 
/** Size of the position data in elements. */
privatefinalint mPositionDataSize =3;
 
/** Offset of the color data. */
privatefinalint mColorOffset = 3;
 
/** Size of the color data in elements. */
privatefinalint mColorDataSize = 4;
 
/** 
 * Draws a triangle from the given vertex data.
 *
 * @param aTriangleBuffer The buffer containing the vertex data.
 */
privatevoiddrawTriangle(finalFloatBuffer aTriangleBuffer)
{ 
    // Pass in the position information
    aTriangleBuffer.position(mPositionOffset);
    GLES20.glVertexAttribPointer(mPositionHandle, mPositionDataSize, GLES20.GL_FLOAT,false,
            mStrideBytes, aTriangleBuffer);
 
    GLES20.glEnableVertexAttribArray(mPositionHandle);
 
    // Pass in the color information
    aTriangleBuffer.position(mColorOffset);
    GLES20.glVertexAttribPointer(mColorHandle, mColorDataSize, GLES20.GL_FLOAT,false,
            mStrideBytes, aTriangleBuffer);
 
    GLES20.glEnableVertexAttribArray(mColorHandle);
 
    // This multiplies the view matrix by the model matrix, and stores the result in the MVP matrix
    // (which currently contains model * view).
    Matrix.multiplyMM(mMVPMatrix,0, mViewMatrix,0, mModelMatrix, 0); 
 
    // This multiplies the modelview matrix by the projection matrix, and stores the result in the MVP matrix
    // (which now contains model * view * projection).
    Matrix.multiplyMM(mMVPMatrix,0, mProjectionMatrix,0, mMVPMatrix, 0); 
 
    GLES20.glUniformMatrix4fv(mMVPMatrixHandle,1,false, mMVPMatrix,0);
    GLES20.glDrawArrays(GLES20.GL_TRIANGLES,0,3);
}

Do you remember those buffers we defined when we originally created our renderer? We’re finally going to be able to use them. We need to tell OpenGL how to use this data using GLES20.glVertexAttribPointer(). Let’s look at the first call.

// Pass in the position information
aTriangleBuffer.position(mPositionOffset);
GLES20.glVertexAttribPointer(mPositionHandle, mPositionDataSize, GLES20.GL_FLOAT,false,
        mStrideBytes, aTriangleBuffer);
GLES20.glEnableVertexAttribArray(mPositionHandle);

We set our buffer position to the position offset, which is at the beginning of the buffer. We then tell OpenGL to use this data and feed it into the vertex shader and apply it to our position attribute. We also need to tell OpenGL how many elements there are between each vertex, or the stride.

Note: The stride needs to be defined in bytes. Although we have 7 elements (3 for the position, 4 for the color) between vertices, we actually have 28 bytes, since each floating point number takes up 4 bytes. Forgetting this step may not cause any errors, but you will be wondering why you don’t see anything on the screen.

Finally, we enable the vertex attribute and move on to the next attribute. A little bit further down we build a combined matrix to project points onto the screen. We could do this in the vertex shader, too, but since it only needs to be done once we may as well just cache the result. We pass in the final matrix to the vertex shader using GLES20.glUniformMatrix4fv() and GLES20.glDrawArrays() converts our points into a triangle and draws it on the screen.

Recap

Visual output of Lesson one.Whew! This was a big lesson, and kudos to you if you made it all the way through. We learned how to create our OpenGL context, pass shape data, load in a vertex and pixel shader, set up our transformation matrices, and finally bring it all together. If everything went well, you should see something similar to the screenshot on the right.

This lesson was a lot to digest and you may need to go over the steps a few times to understand it well. OpenGL ES 2 takes more setup work to get going, but once you’ve been through the process a few times you’ll remember the flow by the back of your hand.

Publishing on Android Market

When developing apps, we wouldn’t want people unable to run those apps to see them in the market, otherwise we could end up with a lot of bad reviews and ratings when the app crashes on their device. To prevent an OpenGL ES 2 app from appearing on a device which doesn’t support it, you can add this to your manifest:

<uses-feature
android:glEsVersion="0x00020000"
android:required="true" />


This tells the Market that your app requires OpenGL ES 2, and it will hide your app from devices which don’t support it.

ST7789驱动实战:从SPI时序到RGB565显示的完整配置解析 本文详细解析了ST7789液晶驱动芯片的SPI时序配置与RGB565显示实现,从数据手册解读到代码实战,涵盖SPI接口配置、关键寄存器设置及完整初始化流程。通过优化DMA传输和常见问题排查,帮助开发者高效驱动ST7789显示屏,适用于嵌入式显示项目开发。 阅读详情

相关推荐

手把手教你用scATAC-seq分析拟南芥根尖染色质可及性(附代码实战)

本文详细介绍了使用scATAC-seq技术分析拟南芥根尖染色质可及性的完整流程,涵盖从实验设计、数据质控、细胞分群到差异分析和多组学整合的实战步骤。通过具体的代码示例,帮助植物研究者掌握单细胞表观组学分析的核心方法,揭示细胞异质性和基因调控动态。

weixin_29291185的博客 265

spark in eclipse---Spark学习笔记3

想要调试源代码,还是要放到eclipse里面去。先生成eclipse项目,下载依赖包 victor@victor-ubuntu:~/software/incubator-spark-0.8.1-incubating$ mvn eclipse:eclipse [INFO] Scanning for projects... [INFO] ---------------------------

OopsOutOfMemory盛利的博客 5729

涨姿势|小众建模软件Make human,MakeClothes插件使用详解教程(2)

衣服可以从头开始建模,或者通过改变人体网格或由MakeClothes工具提供的“服装助手”(见下文)来建模。在设计和建模一件衣物时,应该记住两个限制。首先,将服装网格映射到人体网格的算法要求服装网格完全由四面体组成。其次,知道MakeClothes每件衣服只支持一种材料是很重要的。 完成MakeClothes工作流程后,您的HOME / makehuman / v1 / data / clothes中的工具将创建一个新文件夹。该文件夹将采用用户为服装项目提供的唯一名称。该文件夹将填充必要的资源,以使该项目在

Crazy_in_3Dmax的博客 1874

具身智能 2026:启元人形机器人开发者生态的“安卓时刻”来了吗

一个贯穿全文的问题:人形机器人开发者生态,是否正从「封闭垂直」走向「开源平台化」?本文以开源底座的公开数据为标尺,盘点 SDK、URDF、仿真与技能分发的开放程度,并落回启元机器人的开源路径。所有关键数据均标注来源,文末附社区与学堂链接。

在北风吹起的时候加入我们的队伍 在秋雨到来的时候成为彼此的温暖 757

Rclone替代RaiDrive?零广告免费挂载Alist WebDAV到Windows资源管理器(含自启脚本)

本文详细介绍了如何使用开源工具Rclone替代RaiDrive,实现零广告、全免费的Alist WebDAV挂载方案。通过深入解析配置参数、提供完整的自启动解决方案和解决常见问题,帮助用户在Windows资源管理器中高效管理云存储,提升使用体验。

weixin_33709364的博客 636

8.5/Q1,Charls最新高分文章解读!

8 257名受试者中,672人(8.14%)在9年内发生过卒中。在调整的多因素cox回归模型中,TyG和ABSI仍然是卒中的预测因素,TyG的HR为1.25(95%CI:1.06~1.47),ABSI的HR为1.33(95%CI:1.06~1.68)。TyG和ABSI联合诊断效能最高(AUC:0.579,95%CI:0.553~0.597),而TyG(AUC:0.566,95%CI:0.544~0.587)和ABSI(AUC:0.533,95%CI:0.524~0.597)的诊断效能最低。

gsky_sci的博客 1015

android-ad-cleaner:android免 Root 批量关闭系统开屏与营销推送

橘子,计算机视觉方向工程师,深耕 AIGC 与多媒体处理领域,专注技术实用化落地。本文为原创内容,转载请注明来源与作者。优快云 首发 | 版权所有 © 2026。

下载资源或查看教程前,请务必阅读置顶的《免责声明》 536

淘宝滑块验证码破解:Python模拟鼠标轨迹过关详解

本文全面解析了淘宝滑块验证码的破解流程,从机制分析、图像处理、轨迹模拟到自动化实现,提供了完整的Python解决方案。随着2026年风控技术的升级,单纯依靠轨迹模拟已经难以达到高通过率,未来趋势是结合强化学习生成更自然的轨迹,并利用深度神经网络进行端到端的验证码绕过。此外,各大平台正逐步推广"无感验证"和"行为式验证码",将验证融入到用户正常操作中,这对爬虫技术提出了更高的挑战。技术本身是中立的,关键在于使用者的目的。希望本文能帮助开发者更好地理解验证码对抗技术,从而设计出更安全的防御系统。

2201_76125261的博客 96

Android 14 SELinux策略深度解析

init 的全局公开约束(如 neverallow init execute_no_trans,放 public 才能约束所有分区的规则)这就是"哪些可见"的强制机制。alps_s0(MTK 工程)的 device/mediatek/sepolicy/basic/ 提供。所有分区(system/system_ext/product/vendor)都可引用。init的公开约束、跨分区共用的 type/attribute/neverallow。——vendor 的 .cil 只能引用 plat 的。

新时代农民工 105

vue2/3 - [超详细] 实现企业微信扫码登录完整流程及代码,vue项目接入企业微信扫码登录功能教程,支持新跳页显示二维码或div容器里内嵌登录二维码(详细代码注释,一键复制开箱即用)

vue企业微信扫码登录,vue网页网站企业微信扫码登录教程,vue如何实现企业微信扫码登录功能,vue详细企业微信扫码登录功能实现,vue2实现企业微信扫码登录,前端vue企业微信扫码登录教程,vue pc电脑端企业微信扫码登录详细教程,vue项目中怎么做企业微信扫码注册登录,vue实现网站接入企业微信扫码登录,vue2做企业微信扫码登录的优质文章教程,vue网站对接企业微信扫码登录,vue企业微信扫码登录如何新开一页全屏显示登录二维码,vue企业微信登录二维码 内嵌在div容器里/跳页显示登录二维码,vu

✅ 精选推荐文章 5702

C#里使用NModbus来创建一个TCP从站服务

摘要:本文介绍了一个用C#实现的Modbus TCP从站服务器,基于.NET Framework 4.6.1开发。代码使用NModbus库创建TcpListener和ModbusSlaveNetwork,支持设置IP地址、端口号和从站ID。服务器实现了数据存储初始化功能(包括线圈和保持寄存器),并提供了启动/停止操作的UI界面和日志记录功能。该解决方案适用于需要调试Modbus TCP协议的场景,特别适合无法升级到更高版本.NET Framework的环境。

大坡3D软件开发 478

Android 开发入门实践(二):Kotlin 空安全,从可空类型到安全访问

延续 NewsApp 项目中的新闻标题练习,记录 Kotlin 安全调用。

Easyhood的博客 281

北斗导航 | 接收机自主完好性监测(RAIM)算法近三年研究现状总结(2022-2025)

近三年RAIM算法已从传统的单故障检测演进为多星座协同、智能融合的复杂系统,在航空、自动驾驶等安全关键领域实现规模化应用。未来需重点突破城市复杂环境适应性和标准化体系建设,推动技术从"可用"向"可信"跨越。

尘世冰封的专栏 439

Android 模块化收口:能力组合框架、手写 LifecycleOwner 与热修复共存

二十多个模块业务高度相似却各有差异,核心矛盾在于“统一与灵活”的平衡。通过角色接口组合(如IProjectAware、IVoiceAware)实现能力按需注入,新增功能零侵入;用模板方法锁定生命周期流程,确保顺序一致;结合@CallSuper与工厂方法,既约束关键逻辑,又保留子类自由。最终实现:骨架统一、填空自由,管得住业务,又不侵入业务。

mmsx990的博客 396

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

Android Message源码阅读

本文梳理了Android Handler机制中Message的核心设计:链表结构、消息复用池(sPool)及回收机制,重点解析了异步消息与同步屏障(SyncBarrier)的协同机制。当主线程需优先处理UI绘制等高优先级任务时,系统通过postSyncBarrier插入屏障,使Looper跳过普通同步消息,优先执行标记为异步的UI渲染任务,确保帧率流畅。该机制是保障Android界面响应性的关键技术之一。

u012116089的博客 195

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

安卓部署Linux性能实测:一颗移动处理器,能否胜任日常 Linux 开发?

本文通过在一加Ace5上部署NimoteCode的ARM64 Linux环境,测试了其在CoreMark、SQLite编译及并行构建中的表现。结果显示,尽管单核性能约2.34GHz,多核扩展比达6.15×,但优化编译耗时仍高达35秒,反映移动处理器虽具备一定开发能力,却受限于文件系统开销与运行环境(如PRoot)。实际开发中,非优化构建效率较高,而并行编译可显著提速。结论:安卓手机可作为轻量级开发辅助工具,但无法替代桌面环境,真正价值在于便携性与即时响应,而非全功能替代。

一个不知名码农的专栏 300

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

Android 开发入门实践(九):把新闻数据与页面状态交给 ViewModel

本文记录第110—117课学习成果:将页面标题、新闻列表及选中编号逐步迁移至NewsViewModel,通过ViewModelProvider获取实例并读取数据。页面调用ViewModel方法处理选择与返回,实现状态管理解耦。过程中纠正了对Activity重建机制的误解,并厘清mutableStateOf的可观察性与ViewModel对象生命周期的区别,深化了对Compose状态更新机制的理解。

Easyhood的博客 219

CTF 命令执行题学习笔记:终端特殊字符 $IFS 与 %09 的用法(HelloCTF RCE-labs)

空格被过滤时:用%09(TAB)或$IFS代替空格关键词被过滤时:用通配符?或字符类[f]拼出被禁的词,让 WAF 在原始字符串里看不到它,shell 在执行时再展开这类技巧在后续的命令执行题里会反复用到,建议亲手在靶机上敲一遍感受一下。

YY888888_的博客 541

云手机私有化部署完整方案:基于ARM服务器搭建私有安卓云集群

ARM 服务器是私有化安卓云集群最优硬件底座,原生架构匹配 Android,高密度、低损耗,非常适合企业内网场景。整套私有化方案的核心,从来不是 “跑起来安卓”,而是隔离可控、可审计、可运维、可扩容。两条路线各有取舍:开源自建灵活、门槛高;商用私有化交付开箱即用、安全能力完备,是当前企业生产环境主流方案。六边云手机私有化方案,补齐了自建方案在驱动适配、管控平台、审计合规、信创适配上的短板,适合对数据主权、内网隔离有强需求的政企、自研 App、垂直行业客户。

a598652的博客 314

Android 17 全栈性能分析关键字与源码手册

FrameTimeline 找异常帧,ATrace 找阶段,thread_state 找等待类型,ftrace 找内核原因,Simpleperf 找 CPU 热点,驱动源码找最终触发条件;任何根因结论都必须回到同一对象、同一时间窗口和同一条依赖链。

weixin_43228946的博客 368

Android 顶点上传:Heap 到 Direct 的零拷贝,省的是 JNI 次数

一帧数十万次putFloat的性能瓶颈源于频繁跨JNI边界,每次开销远超传输字节数。通过将写入与上传解耦:用堆内ByteBuffer高速写入,最后一次性put(writeBuf)批量传至堆外DirectByteBuffer,将数百万次边界调用压缩为一次,实现近10倍性能提升。关键在于复用堆外缓冲区、统一端序,并结合“更新-绘制”分离,仅在数据变更时上传顶点,平移缩放时零开销重绘,彻底释放CPU瓶颈。

mmsx990的博客 476

Android 系统启动与调试中的日志抓取方式全解析

│ 用户操作/系统事件 ││ │▼ ▼│ (驱动/硬件/内存管理) │ │ (App/SystemServer) ││ │ │ ││ │▼ ▼│ dmesg 命令 │ │ logd daemon ││ cat /proc/kmsg │ │ (分类存储到分区) ││ │▼ ▼│ 串口 UART │ │ logcat 命令 ││ (硬件直连,最早输出) │ │ adb logcat ││ │▼│ (汇总所有日志+系统状态) │工具最佳用途关键优势主要局限dmesg。

zhaoshuzhaoshu的专栏 263

go消息推送之极光推送 (JPush REST API + SDK,覆盖安卓与iOS)

使用 gorush有个绕不开的问题:国内安卓设备上 FCM 基本不可达,华为、小米、OPPO、vivo 各自为政——此时可用极光推送(JPush)作解决方案。它的思路和前面几家都不一样:App 集成极光 SDK 后拿到统一的 RegistrationID,服务端发一条 REST 请求,极光云端自动把消息路由到 APNs、各家厂商通道或极光自有长连接。这里给出了:一个用纯标准库直接调用极光的;另一个用社区维护的,类型化参数,接口注释与官方文档逐条对应。两个功能相同,可放在一起对比学习。

qq_40467670的博客 172

Android 开发入门实践(十):用 Kotlin 协程完成模拟加载、取消与异常处理

本文记录第118—125课对NewsApp的协程改造:从viewModelScope.launch启动协程,逐步加入delay模拟等待、suspend函数封装、withContext(Dispatchers.IO)切换线程并观察上下文恢复机制。通过日志验证withContext完成后恢复原执行上下文(非自动回主线程),纠正了初始理解偏差。引入Job管理任务,实现加载取消与重复点击防护,结合异常处理与状态管理,完成可观察、可中断的异步流程。

Easyhood的博客 358

【Android】听写播放器改版:中英文书写速度拆项与「正在读」高亮

现象:把播放页主体抽成后,里面给LazyColumn加想让它占满剩余高度,编译报错(weight 不可用)。原因weight不是通用修饰符,是RowScope的扩展函数。是个普通函数,函数体里没有接收者,自然调不到。解决private fun ColumnScope.PlaybackBody(...) { /* 这里就能用 Modifier.weight(1f) */ }调用处仍在原来的Column { }里,无需改动。预防:Compose 的布局修饰符都是作用域绑定。

木头大左的博客 237

android 动画掉帧

动画

yeshen.org 239
下一篇: Android Lesson Two: Ambient and Diffuse Lighting
incanus
博客等级 码龄15年 15粉丝 12原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值