OpenGL.Shader:2-Android Cpp下读取assets图片资源 / 读取图片加载纹理 / 在线找AndroidNative源码

(AS3.x rebuild出现More than one file was found with OS independent path)

这篇文章主要解决标题上的几个大问题。为啥是说大难题?据我发现,第一个问题(Cpp下读取assets图片资源),全网没有一篇文章能很好的说清楚整个流程;第二个问题(Cpp下读取各种格式图片加载纹理)网上很多都是不实际的方法。为啥说不实际,java层利用系统的bitmap.API方法操作资源对象,然后再通过jni传入?商用App这样做性能效率能不低?第三个问题(Cpp下操作三大矩阵)这个比较好理解,但我还是不见有完整的跨平台封装贡献出来。也通过这个问题简单介绍怎么在线找AndroidNative的源码。

以OpenGL.Shader:1为基础,结合之前OpenGL.ES在Android上的简单实践:11-全景(索引-深度测试)索引知识的内容,在纯Cpp层渲染出一个正方体,并在每个正方体上贴上纹理。

首先贴上GLRender的实现类NativeGLRender的整体代码,有个大概认识:

NativeGLRender::NativeGLRender() {init(); // 初始化非GL-Object
}void NativeGLRender::init() {mEglCore = NULL;mWindowSurface = NULL;res_path = NULL;viewMatrix = new float[16];CELLMath::Matrix::setIdentityM(viewMatrix, 0);projectionMatrix = new float[16];CELLMath::Matrix::setIdentityM(projectionMatrix, 0);// 投影矩阵P x 视图矩阵V = VPviewProjectionMatrix = new float[16];memset(viewProjectionMatrix, 0, sizeof(float) * 16);// (投影矩阵P x 视图矩阵V) x 具体模型的模型矩阵M = MVPmodelViewProjectionMatrix = new float[16];memset(modelViewProjectionMatrix, 0, sizeof(float) * 16);
}NativeGLRender::~NativeGLRender() {// 回收各种 非 GL-Objectif(viewMatrix!=NULL) {delete [] viewMatrix;viewMatrix = NULL;}if(projectionMatrix!=NULL) {delete [] projectionMatrix;projectionMatrix = NULL;}if(viewProjectionMatrix!=NULL) {delete [] viewProjectionMatrix;viewProjectionMatrix = NULL;}if(modelViewProjectionMatrix!=NULL) {delete [] modelViewProjectionMatrix;modelViewProjectionMatrix = NULL;}if(res_path!=NULL) {delete res_path;res_path = NULL;}
}void NativeGLRender::setResPath(char *string) {res_path = new char[250];strcpy(res_path, string);LOGI("setResPath : %s\n", res_path);
}void NativeGLRender::surfaceCreated(ANativeWindow *window)
{if (mEglCore == NULL) {mEglCore = new EglCore(NULL, FLAG_RECORDABLE);}mWindowSurface = new WindowSurface(mEglCore, window, true);assert(mWindowSurface != NULL && mEglCore != NULL);LOGD("render surface create ... ");mWindowSurface->makeCurrent();cube = new CubeIndex();cubeShaderProgram = new CubeShaderProgram();char _res_name[250]={0};sprintf(_res_name, "%s%s", res_path, "test.jpg");// 输入资源文件的资源路径,创建纹理,返回纹理idanimation_texure = TextureHelper::createTextureFromImage(_res_name);
}
void NativeGLRender::surfaceChanged(int width, int height)
{mWindowSurface->makeCurrent();LOGD("render surface change ... update MVP!");glViewport(0,0, width, height);glEnable(GL_DEPTH_TEST);CELLMath::Matrix::perspectiveM(projectionMatrix, 45.0f, (float)width/(float)height, 1.0f, 100.0f);CELLMath::Matrix::setLookAtM(viewMatrix, 0,4.0f, 4.0f, 4.0f,0.0f, 0.0f, 0.0f,0.0f, 1.0f, 0.0f);CELLMath::Matrix::multiplyMM(viewProjectionMatrix,  projectionMatrix, viewMatrix);mWindowSurface->swapBuffers();
}
void NativeGLRender::renderOnDraw()
{if (mEglCore == NULL) {LOGW("Skipping drawFrame after shutdown");return;}mWindowSurface->makeCurrent();glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);cubeShaderProgram->ShaderProgram::userProgram();glActiveTexture(GL_TEXTURE0);glBindTexture(GL_TEXTURE_2D, animation_texure);glUniform1i(cubeShaderProgram->uTextureUnit, 0);CELLMath::Matrix::multiplyMM(modelViewProjectionMatrix, viewProjectionMatrix, cube->modelMatrix);cubeShaderProgram->setUniforms(modelViewProjectionMatrix);cube->bindData(cubeShaderProgram);cube->draw();mWindowSurface->swapBuffers();
}
void NativeGLRender::surfaceDestroyed(void)
{// 清空自定义模型,纹理,各种 GL-Objectif(cubeShaderProgram!=NULL) {delete cubeShaderProgram;cubeShaderProgram = NULL;}if(cube!=NULL) {delete cube;cube = NULL;}if (mWindowSurface) {mWindowSurface->release();delete mWindowSurface;mWindowSurface = NULL;}if (mEglCore) {mEglCore->release();delete mEglCore;mEglCore = NULL;}
}

Cube和CubeShaderProgram在之前的OpenGL.ES在Android上的简单实践:11-全景(索引-深度测试)有具体的介绍,这里就不再论述。大致的流程也不难懂,在NativeGLRender构造函数初始化非GL-Object,在三大回调处理GL的相关逻辑。

1、借助libzip解压apk,释放assets资源文件

资源文件放在assets目录下,在工程打包成apk的时候,就会保持原来格式,不会被进行压缩。在Java层我们可以通过Context.getAssets()获取AssetManager进行操作。NDK也有对应的头文件#include <android/asset_manager.h>和<android/asset_manager_jni.h>!但是!But!However,借助Assets读取的资源文件,是拿不到文件的路径。而且如果想对文件进行一些操作,都需要读取文件内容到运行的内存当中。这样的操作缺点太明显了,也不够自由度。那怎么办呢?

那么为什么Assets的系统API都能读取文件内容了,何为就不提供文件的路径和其他信息呢?其实是因为Assets系统API是直接读取apk包里面的assets文件,并没有解压缩,所以自然就没有具体的路径啦。那么能不能骚操作一把,我们自己解压apk获取assets资源文件并释放到磁盘空间? 借助 libzip.so 可以实现这一步。

首先在NaitveEGL.java 新增两个方法:

public class NativeEGL {static {System.loadLibrary("zip");System.loadLibrary("native-egl");}private Context ctx;public NativeEGL(Context context) {ctx = context;initBeforeEGL(ctx);}public String getPackageResourceAPK() {return ctx.getPackageResourcePath();// /data/app/org.zzrblog.nativecpp-zzIu0MPPws9Df9SN-U1BRA==/base.apk// 类似这种以根目录开头的,apk后缀的压缩文件,然后再在JNI层用zip库解压缩。// 解压出 assets/mipmap/filename到getResourceCacheDir()相应的目录下}public String getResourceCacheDir() {// /storage/emulated/0/Android/data/org.zzrblog.nativecpp/cacheFile externalCacheDir = ctx.getExternalCacheDir();assert externalCacheDir != null;return externalCacheDir.getAbsolutePath();}public native void initBeforeEGL(Context context);// ... ...
}

其中getPackageResourcePath()方法比较关键,这个就是返回资源压缩包base.apk的所在路径。准备这两个方法是提供native void initBeforeEGL使用的。现在我们就进入initBeforeEGL方法。

extern "C"
JNIEXPORT void JNICALL
Java_org_zzrblog_nativecpp_NativeEGL_initBeforeEGL(JNIEnv *env, jobject instance, jobject context)
{jclass j_native_egl_class = env->GetObjectClass(instance);jmethodID getResAbsolutePath_mid = env->GetMethodID(j_native_egl_class, "getPackageResourceAPK","()Ljava/lang/String;");jstring compressed_apk_path_jstr = static_cast<jstring>(env->CallObjectMethod(instance, getResAbsolutePath_mid));const char *compressed_apk_path_cstr = env->GetStringUTFChars(compressed_apk_path_jstr, GL_FALSE);LOGI("资源压缩apk文件:%s", compressed_apk_path_cstr);jmethodID getResourceCacheDir_mid = env->GetMethodID(j_native_egl_class, "getResourceCacheDir","()Ljava/lang/String;");jstring release_res_path_jstr = static_cast<jstring>(env->CallObjectMethod(instance, getResourceCacheDir_mid));const char *release_res_path_cstr = env->GetStringUTFChars(release_res_path_jstr, GL_FALSE);LOGI("本地解压缩路径:%s", release_res_path_cstr);// 解压纹理资源文件unzipAssetsResFile(compressed_apk_path_cstr, release_res_path_cstr);renderer = new NativeGLRender();char res_path[250]={0};strcat (res_path, release_res_path_cstr);strcat (res_path, "/");strcat (res_path, "assets/mipmap/");renderer->setResPath(res_path);env->ReleaseStringUTFChars(compressed_apk_path_jstr, compressed_apk_path_cstr);env->ReleaseStringUTFChars(release_res_path_jstr, release_res_path_cstr);
}
void unzipAssetsResFile(const char *compressed_apk_path_cstr, const char *release_res_path_cstr)
{int iErr = 0;struct zip* apkArchive = zip_open(compressed_apk_path_cstr, ZIP_CHECKCONS, &iErr);if(!apkArchive) {LOGE("zip open failed:%d\n", iErr);return;}struct zip_stat fstat;zip_stat_init(&fstat);int numFiles = zip_get_num_files(apkArchive);// 指定要解压出"assets/mipmap/"这类前缀的文件const char *prefix = "assets/mipmap/";for (int i=0; i<numFiles; i++) {const char* name = zip_get_name(apkArchive, i, 0);if (name == NULL) {LOGE("Error reading zip file name at index %i : %s", i, zip_strerror(apkArchive));return;}zip_stat(apkArchive,name,0,&fstat);//LOGD("Index %i:%s,Uncompressed Size:%d,Compressed Size:%d", i, fstat.name, fstat.size, fstat.comp_size);// 查找指定前缀的资源文件const char * ret = strstr(fstat.name, prefix);if( ret ) {LOGI("find it! : %s",ret);char dest_path[250]={0};strcat (dest_path, release_res_path_cstr);strcat (dest_path, "/");strcat (dest_path, prefix);// create all level pathint iPathLength=strlen(dest_path);int iLeaveLength=0;int iCreatedLength=0;char szPathTemp[250]={0};for (int j=0; (NULL!=strchr(dest_path+iCreatedLength,'/')); j++){iLeaveLength = strlen(strchr(dest_path+iCreatedLength,'/'))-1;iCreatedLength = iPathLength - iLeaveLength;strncpy(szPathTemp, dest_path, iCreatedLength);if (access(szPathTemp, F_OK) != 0){mkdir(szPathTemp, 0777);LOGI("mkdir : %s\n", szPathTemp);}}if (access(dest_path, F_OK) != 0){mkdir(dest_path, 0777);LOGI("mkdir : %s\n", dest_path);}// unzip filechar dest_file[250]={0};strcat (dest_file, release_res_path_cstr);strcat (dest_file, "/");strcat (dest_file, fstat.name);//LOGI("uncompressed file : %s\n", dest_file);if(access(dest_file, F_OK) == 0) {LOGI("unzip %s has exist.\n", dest_file);continue;}FILE *fp = fopen(dest_file, "w+");if (!fp) {LOGE("Create unzip file failed.");break;}ssize_t iRead = 0;size_t iLen = 0;char buf[1024];struct zip_file* file = zip_fopen(apkArchive, fstat.name, 0);while(iLen < fstat.size){iRead = zip_fread(file, buf, 1024);if (iRead < 0) {LOGE("zip_fread file failed");break;}fwrite(buf, 1, static_cast<size_t>(iRead), fp);iLen += iRead;}fclose(fp);LOGI("Create unzip file : %s done!", dest_file);zip_fclose(file);}}zip_close(apkArchive);
}

unzipAssetsResFile方法主要流程是:

1、zip_open打开压缩文件apkArchive,zip_stat_init初始化状态结构体zip_stat 。
2、zip_get_num_files获取当前压缩文件包含的所有文件索引数目,并进行for循环遍历。
3、for循环 -> zip_get_name获取当前索引的文件名,zip_stat(apkArchive,name,0,&zip_stat); 获取当前索引文件的状态信息,通过状态结构体zip_stat,可以获取到当前索引文件的详尽信息。(文件名,索引号,crc,压缩前后的大小,压缩方式、加密方式)
4、找出符合prefix = "assets/mipmap/"的文件子项,并指定解压路径是getResourceCacheDir下的assets/mipmap/,检测是否存在当前路径,没有则需要创建目录路径。
5、zip_fopen(apkArchive, fstat.name, 0)打开索引文件,读取文件内容;fopen打开解压的目录文件路径,把读取到的文件内容写日目标文件。解压完成。
6、zip_fclose 关闭当前操作的索引文件,zip_close关闭当前压缩文件。

经过unzipAssetsRefFile方法之后,我们就把工程目录的assets/mipmap,解压到/storage/emulated/0/Android/data/org.zzrblog.nativecpp/cache/assets/mipmap/ filename,此后我们就可以自由的操作文件资源了。(按实际需要解压这个base.apk里面的文件,也可以打开调试日志看看里面实际有多少个文件)

2、读取图片文件(开源,跨平台)

现在我们已经能自由的操作图片资源了,但是图片的格式种类繁多,需要解格式之后获取位图数据,这个是一个贼麻烦的操作。而且网上太多可用库了,又分不同的平台,真的太繁琐了。所以这里贡献一下歪果人整理的读取图片库,以源码的形式开放出来,这样就不用东拼一个libpng,西凑一个libjpeg,最后还来个libwebp。源码兼容Windows/Linux/Android/iOS。(有需要的同学自己到github上load下来)

使用方法也很简单,我们在解压base.apk的assets下的资源文件之后,通过setResPath方法标记 /storage/emulated/0/Android/data/org.zzrblog.nativecpp/cache/assets/mipmap/ 为当前应用的资源文件路径。然后通过拼接完整的资源文件名进行操作。相关代码如下:

    char _res_name[250]={0};sprintf(_res_name, "%s%s", res_path, "test.jpg");// 输入资源文件的绝对路径,创建纹理GLuint _texure_id = TextureHelper::createTextureFromImage(_res_name);
unsigned    createTexture(int w, int h, const void* data, GLenum type)
{unsigned    texId;glGenTextures(1, &texId);glBindTexture(GL_TEXTURE_2D, texId);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);glTexImage2D(GL_TEXTURE_2D, 0, type, w, h, 0, type, GL_UNSIGNED_BYTE, data);return  texId;
}GLuint TextureHelper::createTextureFromImage(const char *fileName)
{int     width;int     height;int     chanel;GLuint  texId;stbi_uc*    pixels  =   stbi_load(fileName, &width, &height, &chanel, 0);if (chanel == 3){texId   =   createTexture(width, height, pixels, GL_RGB);} else {texId   =   createTexture(width, height, pixels, GL_RGBA);}free(pixels);return texId;
}

其中stbi_load就是整理封装的各格式图片读取的函数,暂且支持jpg/jpeg,png,bmp,psd四种类别的文件后缀。源码提供在工程目录下的 cpp/common/stb_image.h/cpp,有需要的同学到这里找https://github.com/MrZhaozhirong/NativeCppApp

3、数学矩阵操作(在线找AndroidNative源码)

最后一个问题,其实也不算什么大问题。习惯了系统的android/opengl/Matrix.java提供的各种矩阵方法,到了cpp居然没发现与之相应的矩阵操作库?那么能不能搞一个和Java.Matrix的库一样,使用方式一致的Cpp库?

// Java
Matrix.setIdentityM(modelViewProjectionMatrix,0);
Matrix.setLookAtM(viewMatrix, 0, ... );
Matrix.multiplyMM(viewProjectionMatrix,0,  projectionMatrix,0, viewMatrix,0);
// Cpp
NameSpace::Matrix::setIdentityM(viewMatrix, 0);
NameSpace::Matrix::setLookAtM(viewMatrix, 0, ... );
NameSpace::Matrix::multiplyMM(viewProjectionMatrix,0,  projectionMatrix,0, viewMatrix,0);

这个愿望其实不是什么问题,AndroidSDK的源码是能直接查看到的,我们自己对照着实现就可以了。在愉快的搬砖期间,不愉快的事发送了。

public static native void multiplyMM(float[] result, int resultOffset, float[] lhs, int lhsOffset, float[] rhs, int rhsOffset);

native方法实现去哪找? 找Android系统源码!去哪找?到 http://androidxref.com/ 找! 就以multiplyMM为例,这里简单介绍一下这个网站的使用方法。

首先进入页面,就显示了各Android系统版本的,我们选择较新的AndroidO-8.1.0。

然后就到搜索页面,Full Search输入你想要搜索的方法的关键字(multiplyMM)左边是要搜索的范围。然后点击search,下方就显示出搜索的结果。

从搜索的结果我们可以看到,/frameworks/base/opengl/java/android/opengl/Matrix.java 是java层api的入口点,再往下看/frameworks/base/core/jni/android/opengl/util.cpp,这里就是multiplyMM的native方法实现。我们还可以在左侧看到关键字代码的引用,util.cpp的1033行就是静态注册native方法的签名了,事不宜迟,赶紧点进去。(直接点1033行引用就可以了!)

  

到此 Java.Matrix.multiplyMM的native方法实现就找到了!真的非常方便。

通过Androidxref.com的帮助,整理了一套OpenGL常用的数学矩阵静态库,在项目的cpp/common/CELLMath.hpp当中,以后还会不断的扩充。(妈妈再也不怕我不会线性代数啦!)

Note:AndroidStudio 3.x版本CMake的一些问题。

1、More than one file was found with OS independent path 'lib/armeabi-v7a/libxxxxx.so'

解决方法:删除CMakeLists.txt中的 # 设置生成的so动态库最后输出的路径
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI})

这个问题主要是因为AS3.x的CMake编译器,默认已经是路径了,如果再设置,那么就多生产一份so在指定的目录上,所以就会出现more than one file了

2、Cpp引用第三方so找不到存在文件

以本篇为例,我们需要使用libzip.so。以前在AS2.x的CMake环境上,我们可以随意设置其so的存放目录
add_library(yuv SHARED IMPORTED )
set_target_properties(yuv PROPERTIES IMPORTED_LOCATION
            ${PROJECT_SOURCE_DIR}/src/main/cpp/libyuv/libyuv.so)
set_target_properties(yuv PROPERTIES LINKER_LANGUAGE CXX)
但是在AS3.x的CMake环境上,我发现不能随意指向存放路径了,问题点还是和上方问题的原因一致,系统只默认so的存放路径,就是项目工程下的 ${PROJECT_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libxxx.so,所以我们必须把so存放在这个目录下,并指向这个目录。

3、cpp源文件太多,编译脚本的add_library怎么简化?

之前我们源文件不多的时候,可以这样一一的把源文件全称写上。
add_library( # 生成动态库的名称
             sync-player
             # 指定是动态库SO
             SHARED
             # 编译库的源代码文件
             src/main/cpp/ffmpeg/sync_player.c
             src/main/cpp/ffmpeg/AVPacket_buffer.c
             src/main/cpp/common/zzr_common.c)

现在源文件太多,可以利用aux_source_directory。具体语法如下:
# 将当前 "./src/main/cpp" 等具体目录下的所有源文件保存到 "SELF_DEFINE" 中,然后在 add_library 方法调用。
aux_source_directory(./src/main/cpp/common   COMMON_SRC )
aux_source_directory(./src/main/cpp/egl            EGL_SRC )
aux_source_directory(./src/main/cpp/objects     OBJECTS_SRC )
aux_source_directory(./src/main/cpp/program   PROGRAM_SRC )
aux_source_directory(./src/main/cpp/utils          UTILS_SRC )
aux_source_directory(./src/main/cpp/nativegl    PROJECT_SRC )
add_library(
        native-egl
        SHARED
        ${COMMON_SRC}
        ${EGL_SRC}
        ${OBJECTS_SRC}
        ${PROGRAM_SRC}
        ${UTILS_SRC}
        ${PROJECT_SRC})

最后放一张工程效果照。

项目工程:https://github.com/MrZhaozhirong/NativeCppApp

OpenGL.Shader:2-Android Cpp下加载assets图片资源 / 各种格式加载纹理 / 在线找AndroidNative源码相关推荐

  1. 在线直播系统源码,多图加载成动画的形式如何实现

    在线直播系统源码,多图加载成动画的形式如何实现 1.设置 animationDrawable.addFrame(drawable, 1000);//添加图片生成的drawable,时间为1000ms ...

  2. ik mysql热加载分词_Elasticsearch 之(25)重写IK分词器源码来基于mysql热更新词库...

    热更新在上一节< IK分词器配置文件讲解以及自定义词库>自定义词库,每次都是在es的扩展词典中,手动添加新词语,很坑 (1)每次添加完,都要重启es才能生效,非常麻烦 (2)es是分布式的 ...

  3. cesium 3dtiles 加载本地数据_cesium结合geoserver实现地图空间查询(附源码下载)

    前言 cesium 官网的api文档介绍地址cesium官网api,里面详细的介绍 cesium 各个类的介绍,还有就是在线例子:cesium 官网在线例子,这个也是学习 cesium 的好素材. 内 ...

  4. android新闻项目、饮食助手、下拉刷新、自定义View进度条、ReactNative阅读器等源码...

    Android精选源码 Android仿照36Kr官方新闻项目课程源码 一个优雅美观的下拉刷新布局,众多样式可选 安卓版本的VegaScroll滚动布局 android物流详情的弹框 健身饮食记录助手 ...

  5. 【Android App】实战项目之虚拟现实(VR)的全景相册(附源码和演示视频 可用于学习和大作业)

    需要源码请点赞关注收藏后评论区留言私信~~~ 不管是绘画还是摄影,都是把三维的物体投影到平面上,其实仍旧呈现二维的模拟画面. 随着科技的发展,传统的成像手段越来越凸显出局限性,缘由在于人们需要一种更逼 ...

  6. Android中的单例模式(java单例模式详解,Glide,EventBus,LayoutInfalter的源码单例模式详解)

    一.单例模式 (1)单例模式介绍和定义 ​ 大概意思是保证一个类在任何时候都只有一个实例在内存里,以static形式提供整个项目的访问.在Android系统中常用的地方有:创建一个SQLiteOpen ...

  7. Android 开发人脸识别之自动识别验证码功能讲解及实现(超详细 附源码)

    需要源码和图片集请点赞关注收藏后评论区留下QQ或者私信~~~ 一.自动识别验证码 验证码图片中最简单的是数字验证码,一张再普通不过的验证码拿到之后要进行以下步骤的处理 1:首先对图片适当裁剪,先去掉外 ...

  8. html代码在线解析,VIP在线解析HTML源码(修改论坛的源码、加搜索功能)

    本帖最后由 闷骚小贱男 于 2017-3-28 00:08 编辑 今天有朋友问我要TX的VIP看视频来着,就给了他一个在线的网站,心想着自己也弄一个解析的吧.在论坛搜到一个源码 传送门:看到有需要直接 ...

  9. Android Studio App开发中高级控件下拉列表Spinner的讲解及实战(附源码 超详细必看)

    运行有问题或需要源码请点赞关注收藏后评论区留言~~~ 一.下拉框Spinner Spinner是下拉框控件,它用于从一串列表中选择某项,其功能类似于单选按钮的组合,下拉列表的展示方式有两种,一种是在当 ...

最新文章

  1. [项目实施失败讨论Case] “凭心而论,在这家公司很敬业的工作了3年多,老板最后给我下的评语,大家都看看吧,千万别和我走同一条路!”(摘自csdn)...
  2. python源代码-python源码
  3. Ubuntu安装搜狗输入法
  4. ❀❀ selenium 学习网站 ★★★★★
  5. 操作系统P,V(wait,signal原语)操作讲解,以及两个例题(答案仅供参考)
  6. 质量三维论如何持续推进腾讯视频播放体验提升
  7. Dynamic Web Module 3.0 requires Java 1.6 or newer报错
  8. 2020-12-19通信电子线路第一章
  9. Flowable 数据库表结构 ACT_RU_VARIABLE
  10. Android程序员重头学Synchronized
  11. Spring Cloud 服务消费者 Feign (三)
  12. python微信开发入门_python tornado微信开发入门代码
  13. 【BZOJ2164】采矿 树链剖分+线段树维护DP
  14. 【JAVA秒会技术之ActiveMQ】ActiveMQ的快速入门
  15. 使用计算机键盘的基本步骤,键盘指法练习方法
  16. Bluedroid 函数分析:bta_dm_gattc_register
  17. 一道好玩的逻辑题之蓝眼睛红眼睛
  18. (附源码)计算机毕业设计SSM家具商城系统
  19. 广州大学 数据结构实验报告
  20. 哪些产品需要做3C认证,费用是多少

热门文章

  1. SpringBoot系列:9. 分布式系统,Dubbo,Zookeeper服务注册与发现
  2. VC++2005相关问题解决方案
  3. 财务工作人员的实在助手,属于你的RPA神器
  4. 腾讯云首席架构师黄希彤:云时代的编程模式
  5. 如何识别图片中的表格数据(opencv 和pyteressact)
  6. moran指数 r语言_新版白话空间统计(19)空间关系对莫兰指数的影响
  7. java类加载机制为什么双亲委派_[五]类加载机制双亲委派机制 底层代码实现原理 源码分析 java类加载双亲委派机制是如何实现的...
  8. 隐形矫正计算机软件,隐形矫治器治疗流程和原理
  9. python鼠标碰撞_selenium + python 鼠标事件
  10. html5抓鱼游戏,捉鱼幼儿园小班的游戏方案