本文主要分为两部分,第一部分采用实验性插件(Gradle Experimental Plugin)建立一个一般性的NDK应用,第二部分采用稳定版插件建立一个简单的灰度处理的OpenCV JNI应用。

单纯为Android Studio配置OpenCV 3.1环境的方法请见《在Android Studio上进行OpenCV 3.1开发》。

更新记录

2016.07.15 – 经@kennedywai的提醒,第二部分的代码只能运行于x86架构,而在armeabi-v7a会产生"libopencv_java3.so" not found的错误。解决办法是把app\src\main\jni\Application.mk文件中的“APP_STL := c++_static”改为“APP_STL := gnustl_shared”或“APP_STL := c++_shared”(gnustl_shared是考虑到APP_CPPFLAGS的-std=c++11标记)。

索引

开发环境

Windows 7 x64 旗舰版

Android Studio 2.1.2

JDK 1.8.0

Android 6.0(API 23)

OpenCV 3.1.0 Android SDK

Android NDK r12b

准备工作

2.打开Android Studio 2.1.2,点击

图标打开SDK Manager。选择SDK Tools,勾选NDK如下图(我已经安装了NDK,所以细节上会有些不同,不过步骤是正确的):

再点击Apply,软件就会自动下载NDK。目前(2016.07.08)最新版本为r12b。下载完成后会自动保存在\ndk-bundle目录下,比如我的是E:\dev-lib\android-sdk\ndk-bundle。也可以到官网手动下载所需的版本(https://developer.android.com/ndk/downloads/index.html),然后解压到一个合适的位置。此外,推荐一个开源的NDK:CrystaX NDK,据说在对C++11的支持方面以及与Boost的配合上更胜一筹(与Android NDK r10e相比,更新版本的比较情况暂时未知)。

在ndk-bundle文件夹的CHANGELOG.md文件中可以查看目前的版本以及对应过去版本所产生的修改。几个月前我在使用Android Studio 1.5.1时,是不能直接通过SDK Manager下载软件包的。所幸,现在网络直连也可以了,而且下载速度不低:)

初级NDK应用

实验性Gradle插件:

Gradle Experimental Plugin基于Gradle的新特性开发,目的在于减少项目配置时间,并提供更好的NDK支持。由于仍然处在开发阶段,所以有些功能暂不支持,而且已有的功能也在频繁地修改。所以尝尝鲜就好,如果要结合OpenCV这种量级的函数库来进行NDK开发,目前我还是推荐采用稳定的Gradle Plugin,详见OpenCV NDK应用一节。

        Gradle的版本可以在File->Project Structure中看到:

创建项目:

1.打开Android Studio,新建一个项目,应用名(Application name)为NDKdemo,包名(Package name)为net.johnhany.ndkdemo。我的项目路径(Project location)为E:\projects\Android\NDKdemo,如下图所示:

2.点击Next,保持默认不变,再点击Next:

        3.选择默认的Empty Activity,再点击Next:

        4.保持默认Activity和Layout不变,点击Finish,建立项目:

        5.项目创建好后,在下图所示的位置选择Project:

        6.在源码目录创建一个jni文件夹。右击main文件夹,选择New->Directory:

        在弹出的窗口中输入“jni”,然后点击OK:

        7.在jni目录创建一个c文件。右击jni文件夹,选择New->C/C++ Source File:

        在弹出的窗口中输入“hello-jni”,并选择.c类型:

此时项目的文件结构如下图所示,比较重要的文件用红色框标记:

       文件说明及修改:

1.app\src\main\java\net\johnhany\ndkdemo\MainActivity.java包含主要的Java代码,内容修改为:

package net.johnhany.ndkdemo;

import android.support.v7.app.AppCompatActivity;

import android.widget.TextView;

import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

TextView tv = (TextView)findViewById(R.id.textview);

tv.setText( stringFromJNI() );

}

public native String stringFromJNI();

static {

System.loadLibrary("hello-jni");

}

}

2.app\src\main\jni\hello-jni.c包含主要的C代码,内容修改为:

#include

jstring

Java_net_johnhany_ndkdemo_MainActivity_stringFromJNI( JNIEnv* env, jobject thiz )

{

#if defined(__arm__)

#if defined(__ARM_ARCH_7A__)

#if defined(__ARM_NEON__)

#if defined(__ARM_PCS_VFP)

#define ABI "armeabi-v7a/NEON (hard-float)"

#else

#define ABI "armeabi-v7a/NEON"

#endif

#else

#if defined(__ARM_PCS_VFP)

#define ABI "armeabi-v7a (hard-float)"

#else

#define ABI "armeabi-v7a"

#endif

#endif

#else

#define ABI "armeabi"

#endif

#elif defined(__i386__)

#define ABI "x86"

#elif defined(__x86_64__)

#define ABI "x86_64"

#elif defined(__mips64) /* mips64el-* toolchain defines __mips__ too */

#define ABI "mips64"

#elif defined(__mips__)

#define ABI "mips"

#elif defined(__aarch64__)

#define ABI "arm64-v8a"

#else

#define ABI "unknown"

#endif

return (*env)->NewStringUTF(env, "Hello from JNI! Compiled with ABI " ABI ".");

}

3.app\src\main\res\layout\activity_main.xml用来设置应用的Layout,内容修改如下:

xmlns:tools="http://schemas.android.com/tools"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:paddingBottom="@dimen/activity_vertical_margin"

android:paddingLeft="@dimen/activity_horizontal_margin"

android:paddingRight="@dimen/activity_horizontal_margin"

android:paddingTop="@dimen/activity_vertical_margin"

tools:context="net.johnhany.ndkdemo.MainActivity">

android:layout_width="match_parent"

android:layout_height="match_parent" />

4.app\src\main\res\values\strings.xml规定了应用Layout中会引用的字符常量,比如应用名称、标题栏名称、按钮上的文字等等。可以不作修改。

5.app\src\main\AndroidManifest.xml用来规定Activity之间的关系,以及申请摄像头、内存读写权限等。可以不作修改。

6.app\build.gradle文件确定Android平台、编译工具及选项、依赖库等重要信息,修改如下:

apply plugin: 'com.android.model.application'

model {

android {

compileSdkVersion 23

buildToolsVersion "24.0.0"

defaultConfig {

applicationId "net.johnhany.ndkdemo"

minSdkVersion.apiLevel 15

targetSdkVersion.apiLevel 23

versionCode 1

versionName "1.0"

}

ndk {

moduleName = "hello-jni"

toolchain = "clang"

ldLibs.addAll(['log'])

cppFlags.add("-std=c++11")

cppFlags.add("-fexceptions")

platformVersion = 23

stl = 'gnustl_shared'

}

buildTypes {

release {

minifyEnabled false

proguardFiles.add(file("proguard-rules.pro"))

}

}

}

}

dependencies {

compile fileTree(dir: "libs", include: ["*.jar"])

testCompile 'junit:junit:4.12'

compile "com.android.support:appcompat-v7:23.2.1"

}

由于我在ndk-bundle\CHANGELOG.md中发现有这样一行'ndk-build' will default to using Clang in r13. GCC will be removed in a later release.,所以我这里工具链也采用了Clang。

7.项目根目录下的build.gradle只需要修改一处,把buildscript.dependencies中的classpath改为'com.android.tools.build:gradle-experimental:0.7.0',表示采用实验性插件:

// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {

repositories {

jcenter()

}

dependencies {

classpath 'com.android.tools.build:gradle-experimental:0.7.0'

// NOTE: Do not place your application dependencies here; they belong

// in the individual module build.gradle files

}

}

allprojects {

repositories {

jcenter()

}

}

task clean(type: Delete) {

delete rootProject.buildDir

}

8.gradle.properties文件暂时不需要修改。在某些情况下,Android Studio会提示你在该文件内添加android.useDeprecatedNdk=true,以启用老版本NDK的某些功能,不过我们这里给出的两个项目都不需要。

9.local.properties文件规定了Android SDK和Android NDK的路径,默认不需要修改。如果是手动下载的NDK,则需要在这里设置NDK的路径:

ndk.dir=E\:\\dev-lib\\android-sdk\\ndk-bundle

sdk.dir=E\:\\dev-lib\\android-sdk

编译及运行:

先点击一下

按钮,检查一下Gradle配置是否正确。如果没有错误信息,确认虚拟设备已经在运行或者已经通过USB线连接了Android手机,则点击

按钮,Android Studio就会开始自动编译、打包、安装及运行。运行效果如下图:

OpenCV NDK应用

创建项目:

新建项目的过程与前文类似,这里就不赘述了。应用名称为OpenCV3JNI,包名为net.johnhany.opencv3jni,项目路径为E:\projects\Android\OpenCV3JNI。

只不过要在jni文件夹中创建3个文件:Android.mk,Application.mk以及gray-process.cpp:

       还要在app\src\main\res\drawable中准备一幅名为pic.png的图片,我使用的图片如下:

此外,需要注意的是,如果你的Java代码中没有出现任何OpenCV相关的变量和函数,则不需要向项目中导入OpenCV库,也不需要在设备上安装OpenCV Manager,在后续步骤中也不需要把OpenCV Android SDK中的opencv_java3.so手动拷贝到项目目录下。如果在Java代码中调用了OpenCV的Java接口,则请按照《在Android Studio上进行OpenCV 3.1开发》为项目配置OpenCV的完整过程将OpenCV库导入到项目中。

文件的修改:

1.app\src\main\java\net\johnhany\opencv3jni\MainActivity.java内容修改为:

package net.johnhany.opencv3jni;

import android.support.v7.app.AppCompatActivity;

import android.os.Bundle;

import android.graphics.Bitmap;

import android.graphics.BitmapFactory;

import android.graphics.Bitmap.Config;

import android.view.View;

import android.view.View.OnClickListener;

import android.widget.Button;

import android.widget.ImageView;

public class MainActivity extends AppCompatActivity implements OnClickListener {

private Button btnProc;

private ImageView imageView;

private Bitmap bmp;

public static native int[] grayProc(int[] pixels, int w, int h);

static {

System.loadLibrary("gray-process");

}

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

btnProc = (Button) findViewById(R.id.btn_gray_process);

imageView = (ImageView) findViewById(R.id.image_view);

bmp = BitmapFactory.decodeResource(getResources(), R.drawable.pic);

imageView.setImageBitmap(bmp);

btnProc.setOnClickListener(this);

}

@Override

public void onClick(View v) {

int w = bmp.getWidth();

int h = bmp.getHeight();

int[] pixels = new int[w*h];

bmp.getPixels(pixels, 0, w, 0, 0, w, h);

int[] resultInt = grayProc(pixels, w, h);

Bitmap resultImg = Bitmap.createBitmap(w, h, Config.ARGB_8888);

resultImg.setPixels(resultInt, 0, w, 0, 0, w, h);

imageView.setImageBitmap(resultImg);

}

@Override

public void onResume(){

super.onResume();

}

}

2.app\src\main\jni\Android.mk文件的内容为:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

#opencv

include E:\\dev-lib\\OpenCV-android-sdk\\sdk\\native\\jni\\OpenCV.mk

OPENCV_CAMERA_MODULES:=on

OPENCV_INSTALL_MODULES:=on

OPENCV_LIB_TYPE:=SHARED

LOCAL_SRC_FILES := gray-process.cpp

LOCAL_LDLIBS += -llog

LOCAL_MODULE := gray-process

include $(BUILD_SHARED_LIBRARY)

其中,E:\\dev-lib\\OpenCV-android-sdk\\sdk\\native\\jni\\OpenCV.mk要指向你的OpenCV Android SDK中对应文件的位置。

OPENCV_CAMERA_MODULES:=on说明要使用摄像头功能,我们的demo中虽然没有用到,但是平时写程序时经常会忘记在这里添加,所以一并写上了。

OPENCV_INSTALL_MODULES:=on可以理解为,编译器自动把OpenCV-android-sdk\sdk\native\libs\x86\libopencv_java3.so拷贝并打包在最终的apk中(目标架构x86因目标设备而定),这也是不需要我们手动向项目内添加任何OpenCV相关库文件的原因。

LOCAL_SRC_FILES是需要编译的C/C++源码文件,如果有多个文件,只需要用空格将文件名分隔,如果文件名太多需要换行,则在前一行行末添加一个“\”符号即可。

LOCAL_MODULE是编译产生的.so库的名称,应该与MainActivity.java中通过System.loadLibrary("gray-process");调用的库名称一致。

3.app\src\main\jni\Application.mk文件的内容为:

APP_STL := gnustl_shared

APP_CPPFLAGS := -frtti -fexceptions -std=c++11

APP_ABI := armeabi-v7a x86

APP_PLATFORM := android-15

其中,APP_CPPFLAGS中的-std=c++11表示开启C++11支持。

APP_ABI规定了NDK编译的目标平台,由于我只想测试这两个架构,所以只写了两个,不过当然是越齐全越好:)

APP_PLATFORM指定了NDK编译针对的平台,其取值应该尽量与app\build.gradle中的minSdkVersion相一致。参考这里:http://stackoverflow.com/a/21982908/3829845。

4.app\src\main\jni\gray-process.cpp文件内容如下:

#include

#include

extern "C" {

using namespace cv;

using namespace std;

JNIEXPORT jintArray JNICALL Java_net_johnhany_opencv3jni_MainActivity_grayProc(JNIEnv *env, jclass obj, jintArray buf, jint w, jint h){

jboolean ptfalse = false;

jint* srcBuf = env->GetIntArrayElements(buf, &ptfalse);

if(srcBuf == NULL){

return 0;

}

int size=w * h;

Mat srcImage(h, w, CV_8UC4, (unsigned char*)srcBuf);

Mat grayImage;

cvtColor(srcImage, grayImage, COLOR_BGRA2GRAY);

cvtColor(grayImage, srcImage, COLOR_GRAY2BGRA);

jintArray result = env->NewIntArray(size);

env->SetIntArrayRegion(result, 0, size, srcBuf);

env->ReleaseIntArrayElements(buf, srcBuf, 0);

return result;

}

}

5.app\src\main\res\layout\activity_main.xml的内容修改为:

xmlns:tools="http://schemas.android.com/tools"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:paddingBottom="@dimen/activity_vertical_margin"

android:paddingLeft="@dimen/activity_horizontal_margin"

android:paddingRight="@dimen/activity_horizontal_margin"

android:paddingTop="@dimen/activity_vertical_margin"

tools:context="net.johnhany.opencv3jni.MainActivity">

android:id="@+id/btn_gray_process"

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:text="@string/str_proc"/>

android:id="@+id/image_view"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:contentDescription="@string/str_proc"

android:layout_marginTop="60dp"

android:layout_centerHorizontal="true" />

6.app\src\main\res\values\strings.xml的内容为:

Gray Process - OpenCV3 JNI

gray process

image description

7.app\build.gradle文件的内容改为:

import org.apache.tools.ant.taskdefs.condition.Os

apply plugin: 'com.android.application'

android {

compileSdkVersion 23

buildToolsVersion "24.0.0"

defaultConfig {

applicationId "net.johnhany.opencv3jni"

minSdkVersion 15

targetSdkVersion 23

versionCode 1

versionName "1.0"

}

sourceSets.main {

jniLibs.srcDirs = ['src/main/libs', 'src/main/jniLibs']

jni.srcDirs = [] //disable automatic ndk-build call

}

project.ext.versionCodes = ['armeabi':1, 'armeabi-v7a':2, 'arm64-v8a':3, 'mips':5, 'mips64':6, 'x86':8, 'x86_64':9]

//versionCode digit for each supported ABI, with 64bit>32bit and x86>armeabi-*

android.applicationVariants.all { variant ->

// assign different version code for each output

variant.outputs.each { output ->

output.versionCodeOverride =

project.ext.versionCodes.get(output.getFilter(com.android.build.OutputFile.ABI), 0) * 1000000 + defaultConfig.versionCode

}

}

// call regular ndk-build(.cmd) script from app directory

task ndkBuild(type: Exec) {

Properties properties = new Properties()

properties.load(project.rootProject.file('local.properties').newDataInputStream())

def ndkDir = properties.getProperty('ndk.dir')

if (Os.isFamily(Os.FAMILY_WINDOWS)) {

commandLine "$ndkDir/ndk-build.cmd", '-C', file('src/main/jni').absolutePath

} else {

commandLine "$ndkDir/ndk-build", '-C', file('src/main/jni').absolutePath

}

}

tasks.withType(JavaCompile) {

compileTask -> compileTask.dependsOn ndkBuild

}

buildTypes {

release {

minifyEnabled false

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

}

}

}

dependencies {

compile fileTree(dir: "libs", include: ["*.jar"])

testCompile 'junit:junit:4.12'

compile 'com.android.support:appcompat-v7:23.2.1'

// compile project(':openCVLibrary310')

}

有些文章中在android.defaultConfig中添加了一个ndk{},用来声明本地库的名称,其实它的作用与Android.mk中的LOCAL_MODULE是相同的,这样不免有些重复,而且在修改文件名时容易出错,所以我把它去掉了。调用ndk-build.cmd的部分参考了NDK的官方样例。

请注意文件末尾被注释掉的compile project(':openCVLibrary310'),这一点前面提到过,如果在Java中调用了OpenCV,这里就要取消掉注释,以编译项目中所导入的OpenCV Java部分。

8.根目录的build.gradle文件中也只有一行需要注意,就是classpath那一行,如下:

dependencies {

classpath 'com.android.tools.build:gradle:2.1.2'

}

9.与初级NDK应用中相同地,gradle.properties,local.properties与app\src\main\AndroidManifest.xml文件保持默认不变。

编译及运行:

与前一节相同,点击

按钮,开始编译和运行。运行效果如下图所示:

Instant Run

Instant Run的优势在于,对已经编译的项目,如果项目源码改动不大,重编译和部署到设备的时间就会显著缩短。只要在File->Project Structure中把Android Plugin Version设为2.1.2,Instant Run就会自动开启。不过这也意味着Instant Run与Gradle Experimental Plugin是不可兼得的。

对于NDK开发,Instant Run的限制在于,NDK编译产生的.so库属于resource文件,而当resource文件或者Layout发生改变时,项目是需要强制重新编译的。所以对C++代码的任何改动都不属于Instant Run的支持范围,需要重新编译。

小结

通过比较前面两个项目的配置文件你会发现app\build.gradle文件的区别:

1.采用gradle-experimental:0.7.0时,启用实验性Gradle插件,此时:

(1)apply plugin为'com.android.model.application';

(2)android{}被包含在model{}内;

(3)ndk{}与defaultConfig{}呈平等关系;

(4)API版本需要用.apiLevel来指定;

(5)属性和标记需要用.add()或.addAll()来添加;

(6)可以直接指定工具链(toolchain);

(7)可以直接指定ldLibs,cppFlags,stl等flag。

2.采用gradle:2.1.2时,可以使用Instant Run,此时:

(1)apply plugin为'com.android.application';

(2)没有model{},只有android{};

(3)ndk{}被包含在defaultConfig{}内;

(4)API版本用targetSdkVersion 23这样的语句来指定,不需要用.apiLevel;

(5)不能指定工具链,ldLibs,cppFlags,stl等,而且不能使用.add()或.addAll()等语句。

关于NDK,OpenCV Java API,OpenCV JNI之间的关系可以这样理解:

(1)Android项目的Java代码中调用了OpenCV,就需要使用OpenCV Java API,需要向项目中导入OpenCV-Android-SDK\sdk\java库,需要在设备上安装OpenCV Manager,需要在app\build.gradle的dependencies中添加compile project(':openCVLibrary310')来明确编译OpenCV的Java源码,在Java代码中需要用OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_3_1_0, this, mOpenCVCallBack);来明确调用OpenCV库;如果项目的Java代码中没有调用OpenCV,以上所有工作都不是必需的。

(2)Android项目的C/C++代码中调用了OpenCV,就需要使用NDK对OpenCV的JNI进行编译,需要在build.gradle和app\build.gradle(根据需要,添加Android.mk以及Application.mk)中提供编译工具(ndk-build.cmd),工具链(Clang或GCC),依赖库(OpenCV.mk中定义),源码文件,库名称等信息。

如何减小项目文件夹的大小

有时为了传阅及上传代码的方便,我们需要手动删除一些缓存文件,及减小项目所占用的空间。具体步骤如下:

1.Build->Clean Project。但默认Clean之后会自动重新编译,所以这样做前后文件夹大小几乎没有区别。

2.File->Invalidate caches。有时项目文件经过大量修改,缓存经过积累可能会产生一些意想不到的错误,这个操作可以强制重置缓存,当然对文件夹的大小不会产生太大变化。

3.打开项目的文件夹,手动删除非必要的缓存文件,包括:

(1).gradle文件夹内的所有文件夹及文件;

(2)整个build文件夹及其子文件内的所有文件;

(3)app\build文件夹内的所有文件夹及文件;

(4)app\src\main\libs文件夹内的所有文件夹及文件;

(5)整个app\src\main\obj文件夹及其子文件内的所有文件。

这样,原本100多MB的文件夹就被精简到了几百KB。并且在下一次编译时,这些文件又会自动生成,不会对编译的应用产生影响。

参考资料

[6].What is the relation between APP_PLATFORM, android:minSdkVersion and android:targetSdkVersion? – http://stackoverflow.com/q/21888052/3829845

把这篇文章分享给你的朋友:

android ndk 博客,Android Studio 2上利用NDK进行OpenCV 3.1开发相关推荐

  1. android传感器博客,Android实现接近传感器

    本文实例为大家分享了Android实现接近传感器的具体代码,供大家参考,具体内容如下 1.接近传感器检测物体与听筒(手机)的距离,单位是厘米. 一些接近传感器只能返回远和近两个状态,如我的手机魅族E2 ...

  2. android博客排行榜,新浪博文排行_新浪博客 新浪博客Android客户端

    新浪博客 新浪博客Android客户端 JPG,330x294,231KB,281_250 醉 是春花烂漫时 JPG,480x800,191KB,250_417 新浪博客 JPG,472x216,23 ...

  3. Android 大牛 国内、国外Android开发者博客

    下面这些内容是 @Trinea大神汇总的国内.国外Android开发者博客. 国内开发者汇总:android-cn/android-dev-cn · GitHub 邓凡平 Innost的专栏 魏祝林 ...

  4. csdn博客markdown 如何输入上下标(如平方指数等)

    csdn博客markdown 如何输入上下标(如平方指数等) 输入上标,如:x2,则输入x^2^ 输入下标,如:x0,则输入 x~0~

  5. github 公钥 私钥_搭建自己的技术博客系列 2:把 Hexo 博客部署到 GitHub 上

    1.在GitHub上建一个新仓库 2.配置Git的SSH KEY 生成SSH添加到GitHub 回到你的git bash中, git config --global user.name "y ...

  6. github ssh 配置_搭建自己的技术博客系列 2:把 Hexo 博客部署到 GitHub 上

    1.在GitHub上建一个新仓库 2.配置Git的SSH KEY 生成SSH添加到GitHub 回到你的git bash中, git config --global user.name "y ...

  7. 手把手教你写高质量Android技术博客,画图工具,录像工具,Markdown写法

    前言 作为程序员,写博客是一件很有意义的事情,可以加深自己对技术的理解,可以结交更多的朋友,记录自己的技术轨迹,而且分享可以让更多的人从中受益,独乐乐不如众乐乐嘛. 但是要写好博客也不是件容易的事,一 ...

  8. Android学习博客和文章存档

    [Android基础]Android总结篇 http://blog.csdn.net/codeemperor/article/details/51004189 Android最佳性能实践(一)--合理 ...

  9. Android应用博客目录

    应用有很多,开个博客都放进来方便查找,也方便修改 1 语言类: 1.1 JAVA 基础语言知识JAVA Collection与Collections,Array与Arrays的区别 JAVA练手--S ...

最新文章

  1. ireport如何给static text加边框_html amp;amp; css 解决li浮动边框为2的问题
  2. Spring Cloud(三)服务提供者 Eureka + 服务消费者(rest + Ribbon)
  3. Oracle 11g新特性之--虚拟列(Virtual Column)
  4. Vue v-if与v-show的区别
  5. 你还傻傻的分不清“和服和浴衣吗?
  6. php 创建 cookie文件,PHP创建Cookie数组
  7. Android数据存储五种方式总结
  8. IDEA 2021.1.2中scala生成变量自动勾选specify type
  9. Vue中去掉表单对象上前后空格
  10. php伪协议实现命令执行的七种姿势
  11. 教你如何在Python中读,写和解析CSV文
  12. 【Java】什么是多态?多态的实现机制是什么?
  13. Angular 项目打包之后,部署到服务器,刷新访问404解决方法
  14. java中int算法的有趣现象
  15. 关于配置了数据库方言为MySQLInnoDBDialect后Hibernate不能自动建表的问题
  16. 如果需要一个图形学算法
  17. 微服务设计笔记——几种远程过程调用方法
  18. 新华三杯考前突击---Day2---IPV6技术篇
  19. 联想教育应用使用说明(7.6版本)——第4章 网络控制工具的使用
  20. CSS3 3D旋转魔方

热门文章

  1. Kafka安全(以SASL+ACL为例)
  2. 浅谈贝叶斯判别(Bayes)
  3. 计算机三级嵌入式学习笔记一
  4. 个人博客系统开发总结之 quartz定时器
  5. VCC、 VDD、VEE、VSS区别
  6. 网络管理员必备工具_EventLog Analyzer一款强大的日志管理工具
  7. 高频模拟混频器模块,功能为一个AD835乘法器+后级一个10KHz的低通滤波器
  8. 分享一个不错的Windows软件
  9. Java:实训五 常用实用类应用
  10. CSS3字号11/10/9/8px实现方法