主頁 > 移動端開發 > 安卓JNI精細化講解,讓你徹底了解JNI(二):用法決議

安卓JNI精細化講解,讓你徹底了解JNI(二):用法決議

2020-09-15 20:50:22 移動端開發

目錄

用法決議
├── 1、JNI函式
│ ├── 1.1、extern "C"
│ ├── 1.2、JNIEXPORT、JNICALL
│ ├── 1.3、函式名
│ ├── 1.4、JNIEnv
│ ├── 1.5、jobject
├── 2、Java、JNI、C/C++基本型別映射關系
├── 3、JNI描述符(簽名)
├── 4、函式靜態注冊、動態注冊
│ ├── 4.1、動態注冊原理
│ ├── 4.2、靜態注冊原理
│ ├── 4.3、Java呼叫native的流程

當通過AndroidStudio創建了Native C++工程后,首先面對的是*.cpp檔案,對于不熟悉C/C++的開發人員而言,往往是望“類”興嘆,無從下手,為此,咱們系統的梳理一下JNI的用法,為后續Native開發做鋪墊,

1、JNI函式

#include <jni.h>
#include <string>

extern "C" JNIEXPORT jstring JNICALL
Java_com_qxc_testnativec_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

通常,大家看到的JNI方法如上圖所示,方法結構與Java方法類似,同樣包含方法名、引數、回傳型別,只不過多了一些修飾詞、特定引數型別而已,

1.1、extern "C"

作用:避免編繹器按照C++的方式去編繹C函式

該關鍵字可以刪掉嗎?
我們不妨動手測驗一下:去掉extern “C” , 重新生成so,運行app,結果直接閃退了:

咱們反編譯so檔案看一下,原來去掉extern “C” 后,函式名字竟然被修改了:

//保留extern "C"
000000000000ea98 T 
Java_com_qxc_testnativec_MainActivity_stringFromJNI

//去掉extern "C"
000000000000eab8 T 
_Z40Java_com_qxc_testnativec_MainActivity_stringFromJNIP7_JNIEnvP8_jobject

原因是什么呢?
其實這跟C和C++的函式多載差異有關系:

1、C不支持函式的多載,編譯之后函式名不變;
2、C++支持函式的多載(這點與Java一致),編譯之后函式名會改變;

原因:在C++中,存在函式的多載問題,函式的識別方式是通過:函式名,函式的回傳型別,函式引數串列
三者組合來完成的,

所以,如果希望編譯后的函式名不變,應通知編譯器使用C的編譯方式編譯該函式(即:加上關鍵字:extern “C”),

擴展:
如果即想去掉關鍵字 extern “C”,又希望方法能被正常呼叫,真的不能實作嗎?

非也,還是有解決辦法的:“函式的動態注冊”,這個后面再介紹吧!!!
1.2、JNIEXPORT、JNICALL
作用:

JNIEXPORT 用來表示該函式是否可匯出(即:方法的可見性)
JNICALL 用來表示函式的呼叫規范(如:__stdcall)

我們通過JNIEXPORT、JNICALL關鍵字跳轉到jni.h中的定義,如下圖:

通過查看 jni.h 中的原始碼,原來JNIEXPORT、JNICALL是兩個宏定義

對于安卓開發者來說,宏可這樣理解:

├── 宏 JNIEXPORT 代表的就是右側的運算式: __attribute__ ((visibility ("default")))
├── 或者也可以說: JNIEXPORT 是右側運算式的別名

宏可表達的內容很多,如:一個具體的數值、一個規則、一段邏輯代碼等;

attribute___((visibility ("default"))) 描述的是“可見性”屬性 visibility

1、default :表示外部可見,類似于public修飾符 (即:可以被外部呼叫)
2、hidden :表示隱藏,類似于private修飾符 (即:只能被內部呼叫)
3、其他 :略

如果,我們想使用hidden,隱藏我們寫的方法,可這么寫:

#include <jni.h>
#include <string>

extern "C" __attribute__ ((visibility ("hidden"))) jstring JNICALL
Java_com_qxc_testnativec_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

重新編譯、運行,結果閃退了,
原因:函式Java_com_qxc_testnativec_MainActivity_stringFromJNI已被隱藏,而我們在java中呼叫該函式時,找不到該函式,所以拋出了例外,如下圖:

宏JNICALL 右邊是空的,說明只是個空定義,上面講了,宏JNICALL代表的是右邊定義的內容,那么,我們代碼也可直接使用右邊的內容(空)替換調JNICALL(即:去掉JNICALL關鍵字),編譯后運行,呼叫so仍然是正確的:

#include <jni.h>
#include <string>

extern "C" JNIEXPORT jstring
Java_com_qxc_testnativec_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}
JNICALL 知識擴展:

JNICALL的定義,并非所有平臺都像Linux一樣是空的,如windows平臺:
#ifndef _JAVASOFT_JNI_MD_H_  
#define _JAVASOFT_JNI_MD_H_  
#define JNIEXPORT __declspec(dllexport)  
#define JNIIMPORT __declspec(dllimport)  
#define JNICALL __stdcall  
typedef long jint;  
typedef __int64 jlong;  
typedef signed char jbyte;  
#endif
1.3、函式名

看到.cpp中的函式"Java_com_qxc_testnativec_MainActivity_stringFromJNI",大部分開發人員都會有疑問:我們定義的native函式名stringFromJNI,為什么對應到cpp中函式名會變成這么長呢?

public native String stringFromJNI();

這跟JNI native函式的注冊方式有關

JNI Native函式有兩種注冊方式(后面會詳細介紹):
1、靜態注冊:按照JNI介面規范的命名規則注冊;
2、動態注冊:在.cpp的JNI_OnLoad方法里注冊;

JNI介面規范的命名規則:

Java_<PackageName>_<ClassName>_<MethodName> 

當我們在Java中呼叫native方法時,JVM 也會根據這種命名規則來查找、呼叫native方法對應的 C 方法,

1.4、JNIEnv

JNIEnv 代表了Java環境,通過JNIEnv*就可以對Java端的代碼進行操作,如:
├──創建Java物件
├──呼叫Java物件的方法
├──獲取Java物件的屬性等

我們跳轉、查看JNIEnv的原始碼實作,如下圖:

JNIEnv指向_JNIEnv,而_JNIEnv是定義的一個C++結構體,里面包含了很多通過JNI介面(JNINativeInterface)物件呼叫的方法,

那么,我們通過JNIEnv操作Java端的代碼,主要使用哪些方法呢?

函式名稱 作用
NewObject 創建Java類中的物件
NewString 創建Java類中的String物件
NewArray 創建型別為Type的陣列物件
GetField 獲得型別為Type的欄位
SetField 設定型別為Type的欄位
GetStaticField 獲得型別為Type的static的欄位
SetStaticField 設定型別為Type的static的欄位
CallMethod 呼叫回傳值型別為Type的static方法
CallStaticMethod 呼叫回傳值型別為Type的static方法

具體用法,后面案例再進行演示,

1.5、jobject

jobject 代表了定義native函式的Java類 或 Java類的實體:

├── 如果native函式是static,則代表類Class物件
├── 如果native函式非static,則代表類的實體物件

我們可以通過jobject訪問定義該native方法的成員方法、成員變數等,

2、Java、JNI、C/C++基本型別映射關系

上面,已經介紹了.cpp方法的基本結構、主要關鍵字,當我們定義了具體方法,寫C/C++方法實作時,會用到各種引數型別,那么,在JNI開發中,這些型別應該是怎么寫呢?
舉例:定義加、減、乘、除的方法

//加
jint addNumber(JNIEnv *env,jclass clazz,jint a,jint b){
     return a+b;
}
//減
jint subNumber(JNIEnv *env,jclass clazz,jint a,jint b){
     return a-b;
}
//乘
jint mulNumber(JNIEnv *env,jclass clazz,jint a,jint b){
     return a*b;
}
//除
jint divNumber(JNIEnv *env,jclass clazz,jint a,jint b){
     return a/b;
}

通過上面案例可以看到,幾個方法的后兩個引數、回傳值,型別都是 jint

jint 是JNI中定義的型別別名,對應的是Java、C++中的int型別

我們先原始碼跟蹤、看下jint的定義,jint 原來是 jni.h中 定義的 int32_t 的別名,如下圖:

根據 int32_t 查找,發現 int32_t 是 stdint.h中定義的 __int32_t的別名,如下圖:

再根據 __int32_t 查找,發現 __int32_t 是 stdint.h中定義的 int 的別名(這個也就是C/C++中的int型別了),如下圖:

Java 、C/C++都有一些常用的資料型別,分別是如何與JNI型別對應的呢?如下所示:

Java 、C/C++中的常用資料型別的映射關系表(通過原始碼跟蹤查找列出來的)
JNI中定義的別名 Java型別 C/C++型別
jint / jsize int int
jshort short short
jlong long long / long long (__int64)
jbyte byte signed char
jboolean boolean unsigned char
jchar char unsigned short
jfloat float float
jdouble double double
jobject Object _jobject*

3、JNI描述符 (簽名)

JNI開發時,我們除了寫本地C/C++實作,還可以通過 JNIEnv *env 呼叫Java層代碼,如獲得某個欄位、獲取某個函式、執行某個函式等:

//獲得某類中定義的欄位id
jfieldID GetFieldID(jclass clazz, const char* name, const char* sig)
    { return functions->GetFieldID(this, clazz, name, sig); }

//獲得某類中定義的函式id
jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)
    { return functions->GetMethodID(this, clazz, name, sig); }

上面的函式與Java的反射比較類似,引數:

clazz : 類的class物件
name : 欄位名、函式名
sig : 欄位描述符(簽名)、函式描述符(簽名)

寫過反射的開發人員對clazz、name這兩個引數應該比較熟悉,對sig稍微陌生一些,

sig 此處是指的:

1、如果是欄位,表示欄位型別的描述符
2、如果是函式,表示函式結構的描述符,即:每個引數型別描述符 + 回傳值型別描述符

舉例( int 型別的描述符是 大寫的 I ):

Java代碼:

public class Hello{
     public int property;
     public int fun(int param, int[] arr){
          return 100;
     }
}
JNI C/C++代碼:

JNIEXPORT void Java_Hello_test(JNIEnv* env, jobject obj){
    jclass myClazz = env->GetObjectClass(obj);
    jfieldId fieldId_prop = env -> GetFieldId(myClazz, "property", "I");
    jmethodId methodId_fun = env -> GetMethodId(myClazz, "fun", "(I[I)I");
}

由上面的示例可以看到,Java類中的欄位型別、函式定義分別對應的描述符:

int  型別 對應的是  I
fun  函式 對應的是  (I[I)I

其他型別的描述符(簽名)如下表:

Java型別 欄位描述符(簽名) 備注
int I int的首字母、大寫
float F float的首字母、大寫
double D double的首字母、大寫
short S short的首字母、大寫
long L long的首字母、大寫
char C char的首字母、大寫
byte B byte的首字母、大寫
boolean Z 因B已被byte使用,所以JNI規定使用Z
object L + /分隔完整類名 String 如: Ljava/lang/String
array [ + 型別描述符 int[] 如:[I
Java函式 函式描述符(簽名) 備注
void V 無回傳值型別
Method (引數欄位描述符...)回傳值欄位描述符 int add(int a,int b) 如:(II)I

4、函式靜態注冊、動態注冊

JNI開發中,我們一般定義了Java native方法,又寫了對應的C方法實作,
那么,當我們在Java代碼中呼叫Java native方法時,虛擬機是怎么知道并呼叫SO庫的對應的C方法的呢?

Java native方法與C方法的對應關系,其實是通過注冊實作的,Java native方法的注冊形式有兩種,一種是靜態注冊,另一種是動態注冊:

靜態注冊:按照JNI規范書寫函式名:java_類路徑_方法名(路徑用下劃線分隔)
動態注冊:JNI_OnLoad中指定Java Native函式與C函式的對應關系

兩種注冊方式的使用對比:

靜態注冊:
1、優缺點:
系統默認方式,使用簡單;
靈活性差(如果修改了java native函式所在類的包名或類名,需手動修改C函式名稱(頭檔案、源檔案));

2、實作方式:
1)函式名可以根據規則手寫
2)也可使用javah命令自動生成

3、示例:
extern "C" JNIEXPORT jstring
Java_com_qxc_testnativec_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}
動態注冊:
1、優缺點:
函式名看著舒服一些,但是需要在C代碼中維護Java Native函式與C函式的對應關系;
靈活性稍高(如果修改了java native函式所在類的包名或類名,僅調整Java native函式的簽名資訊)

2、實作方式
env->RegisterNatives(clazz, gMethods, numMethods)

3、示例:
Java類定義Native函式:

package com.qxc.testpage;
public class JNITools {
    static {
        System.loadLibrary("jnidemo");
    }

    //加法
    public static native int  add(int a,int b);

    //減法
    public static native int sub(int a,int b);

    //乘法
    public static native int mul(int a,int b);

    //除法
    public static native int div(int a,int b);
}

.cpp中動態注冊:

JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved){
    //列印日志
    __android_log_print(ANDROID_LOG_DEBUG,"JNITag","enter jni_onload");
    JNIEnv* env = NULL;
    jint result = -1;
    // 判斷是否正確
    if((*vm)->GetEnv(vm,(void**)&env,JNI_VERSION_1_6)!= JNI_OK){
        return result;
    }
    // 定義函式映射關系(引數1:java native函式,引數2:函式描述符,引數3:C函式)
    const JNINativeMethod method[]={
            {"add","(II)I",(void*)addNumber},
            {"sub","(II)I",(void*)subNumber},
            {"mul","(II)I",(void*)mulNumber},
            {"div","(II)I",(void*)divNumber}
    };
    //找到對應的JNITools類
    jclass jClassName=(*env)->FindClass(env,"com/qxc/testpage/JNITools");
    //開始注冊
    jint ret = (*env)->RegisterNatives(env,jClassName,method, 4);
     //如果注冊失敗,列印日志
    if (ret != JNI_OK) {
        __android_log_print(ANDROID_LOG_DEBUG, "JNITag", "jni_register Error");
        return -1;
    }
    return JNI_VERSION_1_6;
}

//加
jint addNumber(JNIEnv *env,jclass clazz,jint a,jint b){
     return a+b;
}
//減
jint subNumber(JNIEnv *env,jclass clazz,jint a,jint b){
     return a-b;
}
//乘
jint mulNumber(JNIEnv *env,jclass clazz,jint a,jint b){
     return a*b;
}
//除
jint divNumber(JNIEnv *env,jclass clazz,jint a,jint b){
     return a/b;
}

上面,帶著大家了解了兩種注冊方式的基本知識,接下來,咱們再深入了解一下動態注冊和靜態注冊的底層差異、以及實作原理,

4.1、動態注冊原理

動態注冊是Java代碼呼叫中System.loadLibray()時完成的

那么,我們先了解一下System.loadLibray加載動態庫時,底層究竟做了哪些操作:

System.loadLibray的流程圖(為了便于大家理解,此圖省略了部分流程)

底層原始碼:/dalvik/vm/Native.cpp

dvmLoadNativeCode() -> JNI_OnLoad()
//省略的代碼......
//將pNewEntry保存到gDvm全域變數nativeLibs中,下次可以直接通過快取獲取
SharedLib* pActualEntry = addSharedLibEntry(pNewEntry);
//省略的代碼......
//第一次加載so時,呼叫so中的JNI_OnLoad方法
vonLoad = dlsym(handle, "JNI_OnLoad");

通過System.loadLibray的流程圖,不難看出,Java中加載.so動態庫時,最侄訓呼叫so中的JNI_OnLoad方法,這也是為什么我們要在C的JNIEXPORT jint JNI_OnLoad(JavaVM vm, void* reserved)方法中注冊的原因,

接下來,咱們再深入了解一下動態注冊的具體流程:

動態注冊的具體流程圖(為了便于大家理解,此圖省略了部分流程)

如上圖所示:

流程1:是指執行 System.loadLibray函式;
流程2:是指底層默認呼叫so中的JNI_OnLoad函式;
流程3:是指開發人員在JNI_OnLoad中寫的注冊方法,例如: (*env)->RegisterNatives(env,.....)
流程4:需要重點講解一下:
├── 在Android中,不管是Java函式還是Java Native函式,它在虛擬機中對應的都是一個Method*物件
├── 如果是Java Native函式,那么Method*物件的nativeFunc會指向一個bridge函式dvmCallJNIMethod
├── 當呼叫Java Native函式時,就會執行該bridge函式,bridge函式的作用是呼叫該Java Native方法對應的
JNI方法,即: method.insns

流程4的主要作用,如圖所示,為Java Native函式對應的Method*物件,系結屬性,建立對應關系:
├── nativeFunc 指向函式 dvmCallJNIMethod(通常情況下)
├── insns 指向native層的C函式指標 (我們寫的C函式)

我們再從原始碼層面,重點分析一下動態注冊的流程3和流程4吧,

流程3:開發人員在JNI_OnLoad中寫的注冊方法,注冊對應的C函式

JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved){
    //列印日志
    __android_log_print(ANDROID_LOG_DEBUG,"JNITag","enter jni_onload");
    JNIEnv* env = NULL;
    jint result = -1;
    // 判斷是否正確
    if((*vm)->GetEnv(vm,(void**)&env,JNI_VERSION_1_6)!= JNI_OK){
        return result;
    }
    // 定義函式映射關系(引數1:java native函式,引數2:函式描述符,引數3:C函式)
    const JNINativeMethod method[]={
            {"add","(II)I",(void*)addNumber},
            {"sub","(II)I",(void*)subNumber},
            {"mul","(II)I",(void*)mulNumber},
            {"div","(II)I",(void*)divNumber}
    };
    //找到對應的JNITools類
    jclass jClassName=(*env)->FindClass(env,"com/qxc/testpage/JNITools");
    //開始注冊
    jint ret = (*env)->RegisterNatives(env,jClassName,method, 4);
     //如果注冊失敗,列印日志
    if (ret != JNI_OK) {
        __android_log_print(ANDROID_LOG_DEBUG, "JNITag", "jni_register Error");
        return -1;
    }
    return JNI_VERSION_1_6;
}

//加
jint addNumber(JNIEnv *env,jclass clazz,jint a,jint b){
     return a+b;
}
//減
jint subNumber(JNIEnv *env,jclass clazz,jint a,jint b){
     return a-b;
}
//乘
jint mulNumber(JNIEnv *env,jclass clazz,jint a,jint b){
     return a*b;
}
//除
jint divNumber(JNIEnv *env,jclass clazz,jint a,jint b){
     return a/b;
}

C函式的定義比較簡單,共加減乘除4個函式,當動態注冊時,需呼叫函式 RegisterNatives(env,jClassName,method, 4)(該方法有不同引數的多個方法多載),我們主要關注的引數:jclass clazz、JNINativeMethod* methods、jint nMethods

clazz 表示:定義Java Native方法的Java類;
methods 表示:Java Native方法與C方法的對應關系;
nMethods 表示:methods注冊方法的數量,一般設定成methods陣列的長度;

JNINativeMethod如何表示Java Native方法與C方法的對應關系的呢?查看其原始碼定義:

jni.h

//結構體
typedef struct {
    const char* name;   //Java 方法名稱
    const char* signature;  //Java 方法描述符(簽名)
    void*       fnPtr;  //C/C++方法實作
} JNINativeMethod;

了解了JNINativeMethod結構,那么,JNINativeMethod物件是如何與虛擬機中的Method*物件對應的呢?這個有點復雜了,咱們通過流程圖簡單描述一下吧:

動態注冊的原始碼流程圖(為了便于大家理解,此圖省略了部分流程)

dvmSetNativeFunc原始碼分析
如果還希望更清晰的了解底層原始碼的實作邏輯,可下載Android原始碼,自行分析一下吧,

4.2、靜態注冊原理

靜態注冊是在首次呼叫Java Native函式時完成的

靜態注冊的具體流程圖(為了便于大家理解,此圖省略了部分流程)
如上圖所示:

流程1:Java代碼中呼叫Java Native函式;
流程2:獲得Method*物件,默認為該函式的Method*設定nativeFunc(dvmResolveNativeMethod);
流程3:dvmResolveNativeMethod函式中按照特定名稱查找對應的C方法;
流程4:如果找到了對應的C方法,重新為該方法設定Method*屬性;

注意:當Java代碼中第二次再呼叫Java Native函式時,Method*的nativeFunc已經有值了
(即:dvmCallJNIMethod,可參考動態注冊流程內容),會直接執行Method*的nativeFunc的函式,不會在
重新執行特定名稱查找了,

靜態注冊流程2 原始碼分析

靜態注冊流程3、4 原始碼分析

4.3、Java呼叫native的流程

Java代碼中呼叫Java native的流程圖(為了便于大家理解,此圖省略了部分流程)
經過對動態注冊、靜態注冊的實作原理的梳理之后,再看Java代碼中呼叫Java native方法的流程圖,就比較簡單了:

1、如果是動態注冊的Java native函式,System.loadLibray時就已經設定好了Java native函式與C函式的對應關系,當Java代碼中呼叫Java native方法時,直接執行dvmCallJNIMethod橋函式即可(該函式中執行C函式),

2、如果是靜態注冊的Java native函式,當Java代碼中呼叫Java native方法時,默認為Method.nativeFunc賦值為dvmResolveNativeMethod,并按特定名稱查找C方法,重新賦值Method*,最終仍然是執行dvmCallJNIMethod橋函式(只不過Java代碼中第二次再呼叫靜態注冊的Java native函式時,不會再執行黃色部分的流程圖了)

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/50059.html

標籤:Android

上一篇:安卓JNI精細化講解,讓你徹底了解JNI(一):環境搭建與HelloWord

下一篇:安卓AlertDialog四種對話框的最科學撰寫用法

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more