Android Studio中通過(guò)CMake使用NDK并編譯自定義庫(kù)和添加預(yù)編譯庫(kù)
Note:這篇文章是基于Android Studio 3.01版本的,NDK是R16。
step1:創(chuàng)建一個(gè)包含C++的項(xiàng)目
其他默認(rèn)就可以了。
C++ Standard
指定編譯庫(kù)的環(huán)境,其中Toolchain Default使用的是默認(rèn)的CMake環(huán)境;C++ 11也就是C++環(huán)境。兩種環(huán)境都可以編庫(kù),至于區(qū)別,后續(xù)會(huì)跟進(jìn),當(dāng)前博文使用的是CMake環(huán)境。
Exceptions Support
如果選中復(fù)選框,則表示當(dāng)前項(xiàng)目支持C++異常處理,如果支持,在項(xiàng)目Module級(jí)別的build.gradle文件中會(huì)增加一個(gè)標(biāo)識(shí) -fexceptions到cppFlags屬性中,并且在so庫(kù)構(gòu)建時(shí),gradle會(huì)把該屬性值傳遞給CMake進(jìn)行構(gòu)建。
Runtime Type Information Support
同理,選中復(fù)選框,項(xiàng)目支持RTTI,屬性cppFlags增加標(biāo)識(shí)-frtti
切換到project 模式,生成的目錄的結(jié)構(gòu)如下:
3、認(rèn)識(shí)CMakeLists.txt構(gòu)建腳本文件
CMakeLists.txt文件用于配置JNI項(xiàng)目屬性,主要用于聲明CMake使用版本、so庫(kù)名稱、C/CPP文件路徑等信息,下面是該文件內(nèi)容:
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html
# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.4.1)
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
add_library( # Sets the name of the library.
native-lib
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
src/main/cpp/native-lib.cpp )
# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log )
# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
target_link_libraries( # Specifies the target library.
native-lib
# Links the target library to the log library
# included in the NDK.
${log-lib} )
cmake_minimum_required(VERSION 3.4.1)
CMake最小版本使用的是3.4.1。
配置so庫(kù)信息(為當(dāng)前當(dāng)前腳本文件添加庫(kù))
native-lib
這個(gè)是聲明引用so庫(kù)的名稱,在項(xiàng)目中,如果需要使用這個(gè)so文件,引用的名稱就是這個(gè)。值得注意的是,實(shí)際上生成的so文件名稱是libnative-lib。當(dāng)Run項(xiàng)目或者build項(xiàng)目是,在Module級(jí)別的build文件下的intermediates\transforms\mergeJniLibs\debug\folders\2000\1f\main下會(huì)生成相應(yīng)的so庫(kù)文件。
SHARED
這個(gè)參數(shù)表示共享so庫(kù)文件,也就是在Run項(xiàng)目或者build項(xiàng)目時(shí)會(huì)在目錄intermediates\transforms\mergeJniLibs\debug\folders\2000\1f\main下生成so庫(kù)文。此外,so庫(kù)文件都會(huì)在打包到.apk里面,可以通過(guò)選擇菜單欄的Build->Analyze Apk...*查看apk中是否存在so庫(kù)文件,一般它會(huì)存放在lib目錄下。
src/main/cpp/native-lib.cpp
構(gòu)建so庫(kù)的源文件。
STATIC:靜態(tài)庫(kù),是目標(biāo)文件的歸檔文件,在鏈接其它目標(biāo)的時(shí)候使用。
SHARED:動(dòng)態(tài)庫(kù),會(huì)被動(dòng)態(tài)鏈接,在運(yùn)行時(shí)被加載。
MODULE:模塊庫(kù),是不會(huì)被鏈接到其它目標(biāo)中的插件,但是可能會(huì)在運(yùn)行時(shí)使用dlopen-系列的函數(shù)動(dòng)態(tài)鏈接。
頭文件
也可以配置頭文件路徑,方法是(注意這里指定的是目錄而非文件):
include_directories([AFTER|BEFORE] [SYSTEM] dir1 [dir2 ...])
下面的配置實(shí)際上與自定義的JNI項(xiàng)目(自定義的so庫(kù))沒(méi)有太大關(guān)系。
find_library()
這個(gè)方法與我們要?jiǎng)?chuàng)建的so庫(kù)無(wú)關(guān)而是使用NDK的Apis或者庫(kù),默認(rèn)情況下Android平臺(tái)集成了很多NDK庫(kù)文件,所以這些文件是沒(méi)有必要打包到apk里面去的。直接聲明想要使用的庫(kù)名稱即可(猜測(cè):貌似是在Sytem/libs目錄下)。在這里不需要指定庫(kù)的路徑,因?yàn)檫@個(gè)路徑已經(jīng)是CMake路徑搜索的一部分。如示例中使用的是log相關(guān)的so庫(kù)。
log-lib
這個(gè)指定的是在NDK庫(kù)中每個(gè)類型的庫(kù)會(huì)存放一個(gè)特定的位置,而log庫(kù)存放在log-lib中
log
指定使用log庫(kù)
target_link_libraries()
如果你本地的庫(kù)(native-lib)想要調(diào)用log庫(kù)的方法,那么就需要配置這個(gè)屬性,意思是把NDK庫(kù)關(guān)聯(lián)到本地庫(kù)。
native-lib
要被關(guān)聯(lián)的庫(kù)名稱
${log-lib}
要關(guān)聯(lián)的庫(kù)名稱,要用大括號(hào)包裹,前面還要有$符號(hào)去引用。
實(shí)際上,我們可以自己創(chuàng)建CMakeLists.txt文件,而且路徑不受限制,只要在build.gradle中配置externalNativeBuild.cmake.path來(lái)指定該文件路徑即可。
add_subdirectory 可以執(zhí)行子路徑的CMakeLists.txt
添加自定義的C++庫(kù)mathlib
創(chuàng)建源文件
我的項(xiàng)目名稱為OpenCVTest,所以右鍵這個(gè)項(xiàng)目點(diǎn)擊New->Module,然后選Android Library,輸入庫(kù)的名稱MathLib,然后Finish,系統(tǒng)就會(huì)生成對(duì)應(yīng)的模塊,并構(gòu)建好初始的目錄樹(shù)。系統(tǒng)將庫(kù)命名為MathLib,但是目錄樹(shù)中還是小寫(xiě)的mathlib。這個(gè)時(shí)候系統(tǒng)會(huì)自動(dòng)在頂級(jí)settings.gradle添加對(duì)于這個(gè)新模塊的include語(yǔ)句。并且在模塊目錄下構(gòu)建好了初始的build.gradle。
現(xiàn)在我們開(kāi)始創(chuàng)建自己的C++庫(kù),首先右鍵mathlib目錄下的src/main,然后選擇New->Directory,輸入cpp并確定。這個(gè)目錄就是我們要?jiǎng)?chuàng)建的庫(kù)的源文件的位置。
右鍵add,點(diǎn)擊New->C/C++ Source File,輸入add.cpp,并選中Create an associated header。
在.cpp文件中定義好一個(gè)簡(jiǎn)單的加法函數(shù),并在.h文件中添加好對(duì)應(yīng)聲明。
add.cpp
#include "add.h"
int add(int a,int b) {
return a + b;
}
add.h
#ifndef OPENCVTEST_ADD_H
#define OPENCVTEST_ADD_H
int add(int a,int b);
#endif //OPENCVTEST_ADD_H
將源文件關(guān)聯(lián)到構(gòu)建系統(tǒng)中
我們用CMake來(lái)構(gòu)建C++庫(kù),然后CMake又要和gradle結(jié)合,在Android Studio里面協(xié)作管理C++和Java的代碼。
我們?cè)谀Kmathlib的根目錄下創(chuàng)建一個(gè)名為CMakeLists.txt的文件,寫(xiě)入
cmake_minimum_required(VERSION 3.4.1)
add_library(add SHARED
src/main/cpp/add.cpp)
set(distribution_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../distribution)
set_target_properties(add PROPERTIES
LIBRARY_OUTPUT_DIRECTORY
${distribution_DIR}/libs/${ANDROID_ABI})
target_include_directories(add
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src/main/cpp)
add_custom_command(TARGET add POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E
copy "${CMAKE_CURRENT_SOURCE_DIR}/src/main/cpp/add.h"
"${distribution_DIR}/include/mathlib/add.h"
# **** the following 2 lines are for potential future debug purpose ****
# COMMAND "${CMAKE_COMMAND}" -E
# remove_directory "${CMAKE_CURRENT_BINARY_DIR}"
COMMENT "Copying gmath to output directory")
set可以自定義變量。這里定義生成so文件的目錄
set_target_properties 命令的意思是設(shè)置目標(biāo)的一些屬性來(lái)改變它們構(gòu)建的方式。這個(gè)命令中設(shè)置了 add的ARCHIVE_OUTPUT_DIRECTORY 屬性。也就是改變了輸出路徑。
add_custom_command 命令是自定義命令。命令中把頭文件也復(fù)制到了 distribution_DIR 中。
target_include_directories,它對(duì)創(chuàng)建的庫(kù)設(shè)置include路徑,針對(duì)目標(biāo)來(lái)設(shè)置,可以避免與其他庫(kù)的沖突,并且此時(shí)對(duì)自定義的庫(kù)設(shè)置好了此路徑后,后續(xù)導(dǎo)入這個(gè)庫(kù)就不需要再次設(shè)置了。但對(duì)于預(yù)構(gòu)建的庫(kù),就需要設(shè)置,稍后會(huì)有詳細(xì)講解。
接下來(lái)我們?cè)谀Kmathlib的build.gradle中的defaultConfig{}中添加如下語(yǔ)句:
externalNativeBuild {
cmake {
arguments '-DANDROID_PLATFORM=android-19',
'-DANDROID_TOOLCHAIN=clang', '-DANDROID_STL=gnustl_static'
targets 'add'
}
}
這里arguments是編譯參數(shù),而targets則是相比于add_subdirectory更高權(quán)限的方法。一般來(lái)說(shuō)可以把它刪去,即默認(rèn)構(gòu)建所有目標(biāo)。
然后在android{}最后添加如下語(yǔ)句,將CMakeLists.txt關(guān)聯(lián)起來(lái)。
externalNativeBuild {
cmake {
path 'CMakeLists.txt'
}
}
C++庫(kù)已經(jīng)創(chuàng)建好了,接下來(lái)就要在主模塊中使用它了。
為了使用自定義C++庫(kù),我們需要一個(gè)中間人,它從Android本身的Java程序中獲取請(qǐng)求,然后使用我們的C++庫(kù)中的函數(shù)計(jì)算得到結(jié)果,并將數(shù)據(jù)傳回Android本身的Java程序中。
創(chuàng)建一個(gè)中間文件native-math.cpp
#include <jni.h>
#include <string>
#include "mathlib/add.h"
extern "C"
JNIEXPORT jstring
JNICALL
Java_com_example_bill_opencvtest_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++ From Android openCVTest";
return env->NewStringUTF(hello.c_str());
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_example_bill_opencvtest_MainActivity_addFromCpp(JNIEnv *env, jobject instance, jint a,
jint b) {
// TODO
return add(a,b);
}
在app/CMakeLists.txt 加上這個(gè)自定義庫(kù)的引用
set(distribution_DIR ${CMAKE_SOURCE_DIR}/../distribution)
include_directories(${distribution_DIR}/include)
add_library(lib_add SHARED IMPORTED)
set_target_properties(lib_add PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/libs/${ANDROID_ABI}/libadd.so)
add_library( # Sets the name of the library.
native-math
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
src/main/cpp/native-math.cpp )
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log )
set_target_properties(native-math PROPERTIES
LIBRARY_OUTPUT_DIRECTORY
${distribution_DIR}/libs/${ANDROID_ABI})
target_link_libraries( # Specifies the target library.
native-math
android
log
lib_add
# Links the target library to the log library
# included in the NDK.
${log-lib} )
在模塊app的局部build.gradle中,像之前一樣添加好對(duì)應(yīng)的語(yǔ)句:
defaultConfig{}中:
externalNativeBuild {
cmake {
arguments '-DANDROID_PLATFORM=android-19',
'-DANDROID_TOOLCHAIN=clang', '-DANDROID_STL=gnustl_static'
}
}
ndk {
//abiFilters 'armeabi-v7a','x86_64'
}
其中ndk指定abi平臺(tái)
ABI(Application binary interface)應(yīng)用程序二進(jìn)制接口。不同的CPU 與指令集的每種組合都有定義的 ABI (應(yīng)用程序二進(jìn)制接口),一段程序只有遵循這個(gè)接口規(guī)范才能在該 CPU 上運(yùn)行,所以同樣的程序代碼為了兼容多個(gè)不同的CPU,需要為不同的 ABI 構(gòu)建不同的庫(kù)文件。當(dāng)然對(duì)于CPU來(lái)說(shuō),不同的架構(gòu)并不意味著一定互不兼容。
armeabi設(shè)備只兼容armeabi;
armeabi-v7a設(shè)備兼容armeabi-v7a、armeabi;
arm64-v8a設(shè)備兼容arm64-v8a、armeabi-v7a、armeabi;
X86設(shè)備兼容X86、armeabi;
X86_64設(shè)備兼容X86_64、X86、armeabi;
mips64設(shè)備兼容mips64、mips;
mips只兼容mips;
接著在src/main/java/*/MainActivity.java中的MainActivity類下面,加載庫(kù),以及設(shè)置好對(duì)應(yīng)的方法聲明:
static {
System.loadLibrary("native-math");
}
/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();
public native int addFromCpp(int a, int b);
然后就可以在onCreate方法中使用這個(gè)C++庫(kù)定義的函數(shù),在Java中對(duì)應(yīng)的函數(shù)了
super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Example of a call to a native method TextView tv = (TextView) findViewById(R.id.sample_text); tv.setText(stringFromJNI() + "____" + addFromCpp(2,88));
最后別忘了在項(xiàng)目中添加模塊的依賴關(guān)系才可以正常運(yùn)行這個(gè)Android App。右鍵項(xiàng)目OpenCVTest,選擇Open Module Settings。選擇app->Dependencies,添加Module dependency,選擇mathlib,確定即可
添加OpenCV庫(kù)的支持
導(dǎo)入OpenCV進(jìn)項(xiàng)目
從OpenCV的官網(wǎng)將OpenCV4Android 3.4下載下來(lái),解壓到某個(gè)目錄。
點(diǎn)擊Android Studio的File->New->Import Module,然后選擇路徑為OpenCV-android-sdk/sdk/java,確定。并在導(dǎo)入之后,修改build.gradle中的SDK版本。
在Open Module Settings中添加模塊的依賴關(guān)系,使app依賴openCVLibrary340。
現(xiàn)在已經(jīng)可以在.java文件中看得到OpenCV的自動(dòng)補(bǔ)全了。
配置OpenCV的C++預(yù)構(gòu)建庫(kù)
把包含文件夾OpenCV-android-sdk/sdk/native/jni/include和預(yù)構(gòu)建庫(kù)文件夾OpenCV-android-sdk/sdk/native/libs也復(fù)制到項(xiàng)目的distribution中。
由于之前已經(jīng)在添加C++庫(kù)時(shí)修改了app的build.gradle,所以這個(gè)步驟現(xiàn)在不需要再執(zhí)行了。
由于OpenCV是預(yù)構(gòu)建庫(kù),所以沒(méi)有編譯的過(guò)程,因此模塊openCVLibrary320中不需要添加CMakeLists.txt等。我們直接在app模塊中根目錄下的CMakeLists.txt導(dǎo)入OpenCV的庫(kù)即可。
set(libs "${CMAKE_SOURCE_DIR}/src/main/jniLibs")
include_directories(${distribution_DIR}/include)
# set add lib
add_library(libopencv_java3 SHARED IMPORTED )
set_target_properties(libopencv_java3 PROPERTIES
IMPORTED_LOCATION "${libs}/${ANDROID_ABI}/libopencv_java3.so")
add_library( # Sets the name of the library.
native-opencv
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
src/main/cpp/native-opencv.cpp )
set_target_properties(native-opencv PROPERTIES
LIBRARY_OUTPUT_DIRECTORY
${distribution_DIR}/libs/${ANDROID_ABI})
target_link_libraries( # Specifies the target library.
native-opencv
android
log
libopencv_java3
# Links the target library to the log library
# included in the NDK.
${log-lib} )
需要注意的是.so使用SHARED,.a使用STATIC。
注意:預(yù)構(gòu)建庫(kù):so文件和.a文件必須copy在src/main/jniLibs這個(gè)目錄,才可以自動(dòng)被打包。其他路徑都不可以,連source這個(gè)命令也不起作用
現(xiàn)在可以使用openCV庫(kù)了,新建一個(gè)文件native-opencv.cpp
//
// Created by bill on 2018/1/13.
//
#include <jni.h>
#include <opencv2/opencv.hpp>
#include <vector>
using namespace cv;
using namespace std;
extern "C"
JNIEXPORT void JNICALL
Java_com_example_bill_opencvtest_MainActivity_nativeProcessFrame(JNIEnv *env, jobject instance,
jlong addrGray, jlong addrRGBA) {
// TODO
Mat& gray = *(Mat *) addrGray;
Mat& rgba = *(Mat *) addrRGBA;
vector<KeyPoint> v;
Ptr<ORB> orb = ORB::create();
orb->detect(gray, v, cv::Mat());
for (int i = 0; i < v.size(); ++i) {
const KeyPoint& kp = v[i];
circle(rgba, Point(kp.pt.x, kp.pt.y), 10, Scalar(255,0,0,255));
}
}
現(xiàn)在就可以在src/main/java/*/MainActivity.java中按照同樣的方法,載入庫(kù),寫(xiě)上方法聲明。最后,如下所示。
static {
System.loadLibrary("native-opencv");
System.loadLibrary("native-math");
}
/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();
public native int addFromCpp(int a, int b);
private native void nativeProcessFrame(long addrGray, long addrRGBA);
完整的MainActivity
package com.example.bill.opencvtest;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.WindowManager;
import android.widget.TextView;
import org.opencv.android.CameraBridgeViewBase;
import org.opencv.android.OpenCVLoader;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
public class MainActivity extends Activity implements CameraBridgeViewBase.CvCameraViewListener2{
static {
System.loadLibrary("native-opencv");
System.loadLibrary("native-math");
}
/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();
public native int addFromCpp(int a, int b);
private native void nativeProcessFrame(long addrGray, long addrRGBA);
private static final String TAG = "MainActivity";
private Mat rgba;
private Mat gray;
private CameraBridgeViewBase mOpenCvCameraView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Example of a call to a native method
TextView tv = (TextView) findViewById(R.id.sample_text);
tv.setText(stringFromJNI() + "____" + addFromCpp(2,88));
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
mOpenCvCameraView = (CameraBridgeViewBase) findViewById(R.id.activity_camera_view);
mOpenCvCameraView.setVisibility(CameraBridgeViewBase.VISIBLE);
mOpenCvCameraView.setCvCameraViewListener(this);
}
@Override
public void onPause() {
super.onPause();
if (mOpenCvCameraView != null){
mOpenCvCameraView.disableView();
}
}
@Override
public void onResume()
{
super.onResume();
if (!OpenCVLoader.initDebug()) {
Log.d(TAG, "Internal OpenCV library not found. Using OpenCV Manager for initialization");
} else {
Log.d(TAG, "OpenCV library found inside package. Using it!");
mOpenCvCameraView.enableView();
}
}
public void onDestroy() {
super.onDestroy();
if (mOpenCvCameraView != null){
mOpenCvCameraView.disableView();
}
}
public void onCameraViewStarted(int width, int height){
rgba = new Mat(height, width, CvType.CV_8UC4);
gray = new Mat(height, width, CvType.CV_8UC1);
}
public void onCameraViewStopped() {
rgba.release();
gray.release();
}
public Mat onCameraFrame(CameraBridgeViewBase.CvCameraViewFrame inputFrame){
rgba = inputFrame.rgba();
gray = inputFrame.gray();
nativeProcessFrame(gray.getNativeObjAddr(), rgba.getNativeObjAddr());
return rgba;
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" xmlns:opencv="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.example.bill.opencvtest.MainActivity"> <TextView android:id="@+id/sample_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> <org.opencv.android.JavaCameraView android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/activity_camera_view" opencv:show_fps="true" opencv:camera_id="any"/> </android.support.constraint.ConstraintLayout>
為了愉快的使用OpenCV Library,可以直接在AndroidManifest.xml里面加入如下權(quán)限
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.bill.opencvtest">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA"/>
<uses-feature android:name="android.hardware.camera"/>
<uses-feature android:name="android.hardware.camera.autofocus"/>
<uses-feature android:name="android.hardware.camera.front"/>
<uses-feature android:name="android.hardware.camera.front.autofocus"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity"
android:screenOrientation="landscape"
android:configChanges="keyboardHidden|orientation">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
總結(jié)
以上所述是小編給大家介紹的Android Studio中通過(guò)CMake使用NDK并編譯自定義庫(kù)和添加預(yù)編譯庫(kù),希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
相關(guān)文章
Android手勢(shì)滑動(dòng)實(shí)現(xiàn)兩點(diǎn)觸摸縮放圖片
這篇文章主要介紹了Android手勢(shì)滑動(dòng)實(shí)現(xiàn)兩點(diǎn)觸摸縮放圖片的相關(guān)資料,需要的朋友可以參考下2016-02-02
Android NDK開(kāi)發(fā)(C語(yǔ)言--動(dòng)態(tài)內(nèi)存分配)
這篇文章主要介紹了Android NDK開(kāi)發(fā) C語(yǔ)言--動(dòng)態(tài)內(nèi)存分配2021-12-12
android設(shè)置adb自帶screenrecord錄屏命令
這篇文章主要介紹了android設(shè)置adb自帶screenrecord錄屏命令,需要的朋友可以參考下2018-11-11
Android開(kāi)發(fā)中的ViewModel使用實(shí)戰(zhàn)案例
在Android應(yīng)用開(kāi)發(fā)中,ViewModel作為架構(gòu)組件,重要的功能是管理UI數(shù)據(jù)與生命周期,文章深入分析ViewModel如何感知View的生命周期,以及其核心優(yōu)勢(shì),包括生命周期感知、數(shù)據(jù)持久化和與UI層解耦,幫助開(kāi)發(fā)者利用ViewModel優(yōu)化應(yīng)用架構(gòu),需要的朋友可以參考下2024-10-10
Android入門教程之Vibrator(振動(dòng)器)
本節(jié)我們介紹的是Vibrator(振動(dòng)器),是手機(jī)自帶的振動(dòng)器,其實(shí)就是Android給我們提供的用于機(jī)身震動(dòng)的一個(gè)服務(wù)!當(dāng)收到推送消息的時(shí)候我們可以設(shè)置震動(dòng)提醒。2016-07-07
Android 10 啟動(dòng)之servicemanager源碼解析
這篇文章主要為大家介紹了Android 10 啟動(dòng)之servicemanager源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10
Android Studio編寫(xiě)微信頁(yè)面提交功能
這篇文章主要介紹了基于Android Studio編寫(xiě)微信頁(yè)面提交功能,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-03-03
Android自定義實(shí)現(xiàn)循環(huán)滾輪控件WheelView
滾輪布局WheelView大家經(jīng)常使用,比如在選擇生日的時(shí)候,風(fēng)格類似系統(tǒng)提供的DatePickerDialog,這篇文章主要為大家詳細(xì)介紹了Android自定義實(shí)現(xiàn)循環(huán)滾輪控件WheelView,感興趣的小伙伴們可以參考一下2016-07-07





