打印堆棧是調(diào)試的常用方法,一般在系統(tǒng)異常時,我們可以將異常情況下的堆棧打印出來,這樣十分方便錯誤查找。實際上還有另外一個非常有用的功能:分析代碼的行為。android代碼太過龐大復(fù)雜了,完全的靜態(tài)分析經(jīng)常是無從下手,因此通過打印堆棧的動態(tài)分析也十分必要。

成都創(chuàng)新互聯(lián)公司是工信部頒發(fā)資質(zhì)IDC服務(wù)器商,為用戶提供優(yōu)質(zhì)的德陽服務(wù)器托管服務(wù)
Android打印堆棧的方法,簡單歸類一下
1. zygote的堆棧dump
實際上這個可以同時dump java線程及native線程的堆棧,對于java線程,java堆棧和native堆棧都可以得到。
使用方法很簡單,直接在adb shell或串口中輸入:
[plain] view plaincopy
kill -3 pid
輸出的trace會保存在 /data/anr/traces.txt文件中。這個需要注意,如果沒有 /data/anr/這個目錄或/data/anr/traces.txt這個文件,需要手工創(chuàng)建一下,并設(shè)置好讀寫權(quán)限。
如果需要在代碼中,更容易控制堆棧的輸出時機,可以用以下命令獲取zygote的core dump:
[java] view plaincopy
Process.sendSignal(pid, Process.SIGNAL_QUIT);
原理和命令行是一樣的。
不過需要注意兩點:
adb shell可能會沒有權(quán)限,需要root。
android 4.2中關(guān)閉了native thread的堆棧打印,詳見 dalvik/vm/Thread.cpp的dumpNativeThread方法:
[cpp] view plaincopy
dvmPrintDebugMessage(target,
"\"%s\" sysTid=%d nice=%d sched=%d/%d cgrp=%s\n",
name, tid, getpriority(PRIO_PROCESS, tid),
schedStats.policy, schedStats.priority, schedStats.group);
dumpSchedStat(target, tid);
// Temporarily disabled collecting native stacks from non-Dalvik
// threads because sometimes they misbehave.
//dvmDumpNativeStack(target, tid);
Native堆棧的打印被關(guān)掉了!不過對于大多數(shù)情況,可以直接將這個注釋打開。
2. debuggerd的堆棧dump
debuggerd是android的一個daemon進程,負責在進程異常出錯時,將進程的運行時信息dump出來供分析。debuggerd生 成的coredump數(shù)據(jù)是以文本形式呈現(xiàn),被保存在 /data/tombstone/ 目錄下(名字取的也很形象,tombstone是墓碑的意思),共可保存10個文件,當超過10個時,會覆蓋重寫最早生成的文件。從4.2版本開 始,debuggerd同時也是一個實用工具:可以在不中斷進程執(zhí)行的情況下打印當前進程的native堆棧。使用方法是:
[plain] view plaincopy
debuggerd -b pid
這可以協(xié)助我們分析進程執(zhí)行行為,但最最有用的地方是:它可以非常簡單的定位到native進程中鎖死或錯誤邏輯引起的死循環(huán)的代碼位置。
3. java代碼中打印堆棧
Java代碼打印堆棧比較簡單, 堆棧信息獲取和輸出,都可以通過Throwable類的方法實現(xiàn)。目前通用的做法是在java進程出現(xiàn)需要注意的異常時,打印堆棧,然后再決定退出或挽救。通常的方法是使用exception的printStackTrace()方法:
[java] view plaincopy
try {
...
} catch (RemoteException e) {
e.printStackTrace();
...
}
當然也可以只打印堆棧不退出,這樣就比較方便分析代碼的動態(tài)運行情況。Java代碼中插入堆棧打印的方法如下:
[java] view plaincopy
Log.d(TAG,Log.getStackTraceString(new Throwable()));
4. C++代碼中打印堆棧
C++也是支持異常處理的,異常處理庫中,已經(jīng)包含了獲取backtrace的接口,Android也是利用這個接口來打印堆棧信息的。在Android的C++中,已經(jīng)集成了一個工具類CallStack,在libutils.so中。使用方法:
[cpp] view plaincopy
#include utils/CallStack.h
...
CallStack stack;
stack.update();
stack.dump();
使用方式比較簡單。目前Andoid4.2版本已經(jīng)將相關(guān)信息解析的很到位,符號表查找,demangle,偏移位置校正都做好了。
[plain] view plaincopy
5. C代碼中打印堆棧
C代碼,尤其是底層C庫,想要看到調(diào)用的堆棧信息,還是比較麻煩的。 CallStack肯定是不能用,一是因為其實C++寫的,需要重新封裝才能在C中使用,二是底層庫反調(diào)上層庫的函數(shù),會造成鏈接器循環(huán)依賴而無法鏈接。 不過也不是沒有辦法,可以通過android工具類CallStack實現(xiàn)中使用的unwind調(diào)用及符號解析函數(shù)來處理。
這里需要注意的是,為解決鏈接問題,最好使用dlopen方式,查找需要用到的接口再直接調(diào)用,這樣會比較簡單。如下為相關(guān)的實現(xiàn)代碼,只需要在要 打印的文件中插入此部分代碼,然后調(diào)用getCallStack()即可,無需包含太多的頭文件和修改Android.mk文件:
[cpp] view plaincopy
#define MAX_DEPTH 31
#define MAX_BACKTRACE_LINE_LENGTH 800
#define PATH "/system/lib/libcorkscrew.so"
typedef ssize_t (*unwindFn)(backtrace_frame_t*, size_t, size_t);
typedef void (*unwindSymbFn)(const backtrace_frame_t*, size_t, backtrace_symbol_t*);
typedef void (*unwindSymbFreeFn)(backtrace_symbol_t*, size_t);
static void *gHandle = NULL;
static int getCallStack(void){
ssize_t i = 0;
ssize_t result = 0;
ssize_t count;
backtrace_frame_t mStack[MAX_DEPTH];
backtrace_symbol_t symbols[MAX_DEPTH];
unwindFn unwind_backtrace = NULL;
unwindSymbFn get_backtrace_symbols = NULL;
unwindSymbFreeFn free_backtrace_symbols = NULL;
// open the so.
if(gHandle == NULL) gHandle = dlopen(PATH, RTLD_NOW);
// get the interface for unwind and symbol analyse
if(gHandle != NULL) unwind_backtrace = (unwindFn)dlsym(gHandle, "unwind_backtrace");
if(gHandle != NULL) get_backtrace_symbols = (unwindSymbFn)dlsym(gHandle, "get_backtrace_symbols");
if(gHandle != NULL) free_backtrace_symbols = (unwindSymbFreeFn)dlsym(gHandle, "free_backtrace_symbols");
if(!gHandle ||!unwind_backtrace ||!get_backtrace_symbols || !free_backtrace_symbols ){
ALOGE("Error! cannot get unwind info: handle:%p %p %p %p",
gHandle, unwind_backtrace, get_backtrace_symbols, free_backtrace_symbols );
return result;
}
count= unwind_backtrace(mStack, 1, MAX_DEPTH);
get_backtrace_symbols(mStack, count, symbols);
for (i = 0; i count; i++) {
char line[MAX_BACKTRACE_LINE_LENGTH];
const char* mapName = symbols[i].map_name ? symbols[i].map_name : "unknown";
const char* symbolName =symbols[i].demangled_name ? symbols[i].demangled_name : symbols[i].symbol_name;
size_t fieldWidth = (MAX_BACKTRACE_LINE_LENGTH - 80) / 2;
if (symbolName) {
uint32_t pc_offset = symbols[i].relative_pc - symbols[i].relative_symbol_addr;
if (pc_offset) {
snprintf(line, MAX_BACKTRACE_LINE_LENGTH, "#%02d pc %08x %.*s (%.*s+%u)",
i, symbols[i].relative_pc, fieldWidth, mapName,
fieldWidth, symbolName, pc_offset);
} else {
snprintf(line, MAX_BACKTRACE_LINE_LENGTH, "#%02d pc %08x %.*s (%.*s)",
i, symbols[i].relative_pc, fieldWidth, mapName,
fieldWidth, symbolName);
}
} else {
snprintf(line, MAX_BACKTRACE_LINE_LENGTH, "#%02d pc %08x %.*s",
i, symbols[i].relative_pc, fieldWidth, mapName);
}
ALOGD("%s", line);
}
free_backtrace_symbols(symbols, count);
return result;
}
對sched_policy.c的堆棧調(diào)用分析如下,注意具體是否要打印,在哪里打印,還可以通過pid、uid、property等來控制一下,這樣就不會被淹死在trace的汪洋大海中。
[plain] view plaincopy
D/SchedPolicy( 1350): #00 pc 0000676c /system/lib/libcutils.so
D/SchedPolicy( 1350): #01 pc 00006b3a /system/lib/libcutils.so (set_sched_policy+49)
D/SchedPolicy( 1350): #02 pc 00010e82 /system/lib/libutils.so (androidSetThreadPriority+61)
D/SchedPolicy( 1350): #03 pc 00068104 /system/lib/libandroid_runtime.so (android_os_Process_setThreadPriority(_JNIEnv*, _jobject*, int, int)+7)
D/SchedPolicy( 1350): #04 pc 0001e510 /system/lib/libdvm.so (dvmPlatformInvoke+112)
D/SchedPolicy( 1350): #05 pc 0004d6aa /system/lib/libdvm.so (dvmCallJNIMethod(unsigned int const*, JValue*, Method const*, Thread*)+417)
D/SchedPolicy( 1350): #06 pc 00027920 /system/lib/libdvm.so
D/SchedPolicy( 1350): #07 pc 0002b7fc /system/lib/libdvm.so (dvmInterpret(Thread*, Method const*, JValue*)+184)
D/SchedPolicy( 1350): #08 pc 00060c30 /system/lib/libdvm.so (dvmCallMethodV(Thread*, Method const*, Object*, bool, JValue*, std::__va_list)+271)
D/SchedPolicy( 1350): #09 pc 0004cd34 /system/lib/libdvm.so
D/SchedPolicy( 1350): #10 pc 00049382 /system/lib/libandroid_runtime.so
D/SchedPolicy( 1350): #11 pc 00065e52 /system/lib/libandroid_runtime.so
D/SchedPolicy( 1350): #12 pc 0001435e /system/lib/libbinder.so (android::BBinder::transact(unsigned int, android::Parcel const, android::Parcel*, unsigned int)+57)
D/SchedPolicy( 1350): #13 pc 00016f5a /system/lib/libbinder.so (android::IPCThreadState::executeCommand(int)+513)
D/SchedPolicy( 1350): #14 pc 00017380 /system/lib/libbinder.so (android::IPCThreadState::joinThreadPool(bool)+183)
D/SchedPolicy( 1350): #15 pc 0001b160 /system/lib/libbinder.so
D/SchedPolicy( 1350): #16 pc 00011264 /system/lib/libutils.so (android::Thread::_threadLoop(void*)+111)
D/SchedPolicy( 1350): #17 pc 000469bc /system/lib/libandroid_runtime.so (android::AndroidRuntime::javaThreadShell(void*)+63)
D/SchedPolicy( 1350): #18 pc 00010dca /system/lib/libutils.so
D/SchedPolicy( 1350): #19 pc 0000e3d8 /system/lib/libc.so (__thread_entry+72)
D/SchedPolicy( 1350): #20 pc 0000dac4 /system/lib/libc.so (pthread_create+160)
D/SchedPolicy( 1350): #00 pc 0000676c /system/lib/libcutils.so
D/SchedPolicy( 1350): #01 pc 00006b3a /system/lib/libcutils.so (set_sched_policy+49)
D/SchedPolicy( 1350): #02 pc 00016f26 /system/lib/libbinder.so (android::IPCThreadState::executeCommand(int)+461)
D/SchedPolicy( 1350): #03 pc 00017380 /system/lib/libbinder.so (android::IPCThreadState::joinThreadPool(bool)+183)
D/SchedPolicy( 1350): #04 pc 0001b160 /system/lib/libbinder.so
D/SchedPolicy( 1350): #05 pc 00011264 /system/lib/libutils.so (android::Thread::_threadLoop(void*)+111)
D/SchedPolicy( 1350): #06 pc 000469bc /system/lib/libandroid_runtime.so (android::AndroidRuntime::javaThreadShell(void*)+63)
D/SchedPolicy( 1350): #07 pc 00010dca /system/lib/libutils.so
D/SchedPolicy( 1350): #08 pc 0000e3d8 /system/lib/libc.so (__thread_entry+72)
D/SchedPolicy( 1350): #09 pc 0000dac4 /system/lib/libc.so (pthread_create+160)
6. 其它堆棧信息查詢
之前很早就想寫寫Android 的動畫,最近剛好有時間,大概聊一聊安卓動畫。
個人習(xí)慣將動畫分為:補間動畫(透明度、旋轉(zhuǎn)、位移、縮放)、幀動畫、和屬性動畫,這一篇,我們先說說補間動畫。
補間動畫這個詞出于flash,在兩個關(guān)鍵幀( 可以理解成動畫開始和結(jié)束 )中間需要做“補間動畫”,才能實現(xiàn)圖畫的運動;插入補間動畫后兩個關(guān)鍵幀之間的插補幀是由計算機自動運算而得到的。
實際上,Android 的補間動畫也是由我們指定動畫開始、動畫結(jié)束2個關(guān)鍵點,中間部分的動畫由系統(tǒng)完成
在正式開始之前,我們先說下Android 系統(tǒng)的坐標系,屏幕左上角為坐標原點,假如屏幕為1080*1980,那么左上角為(0,0),右上角為(1080,0),左下角為(0,1980),右下角為(1080,1980)
所有動畫有以下公共屬性,注釋比較詳細,這里就不在詳述了
ScaleAnimation有3種構(gòu)造方法
我們先看第一種,其起始比例為0,縮放比例為1.4,即放大到1.4倍
效果如下:
第二種,pivotx,pivotY分別代表起始位置的x、y方向的坐標,我們設(shè)置為(100,100)
效果如下:
第三種,pivotXType和pivotYType有2種模式,RELATIVE_TO_SELF(相對于自身)和RELATIVE_TO_PARENT(相對于父布局),如果設(shè)置這個,pivotx,pivotY的值就應(yīng)該是0-1的浮點數(shù),這里分別對應(yīng)xml中的%(自身)和%p(父布局)
TranslateAnimation有2種構(gòu)造方法,和ScaleAnimation類似
效果如下:
效果如下:
RELATIVE_TO_PARENT
效果如下:
RotateAnimation有3種構(gòu)造方法
順時針720度
效果如下:
逆時針720度
效果如下:
效果如下:
再來RELATIVE_TO_PARENT
效果如下:
這是什么鬼???怎么跑到屏幕外面去了?
原來設(shè)置為RELATIVE_TO_PARENT時,旋轉(zhuǎn)中心x方向應(yīng)該為該空間離左邊的邊距+父布局寬度/2,y方向同理,而此時,我們布局中紅色的Textview為居中狀態(tài),所以旋轉(zhuǎn)中心為屏幕右下角。讓我們來看個例子
修改布局如下:
效果如下:
這時,我們看到旋轉(zhuǎn)中心x方向為離左邊100dp處
AlphaAnimation只有1種構(gòu)造方法
其中fromAlpha為動畫開始的透明度;toAlpha為動畫結(jié)束的透明度
效果如下:
效果如下:
AnimationSet是一個動畫的集合,可以按照添加的順序播放動畫,讓我們來看個例子,通過組合動畫,實現(xiàn)旋轉(zhuǎn)漸入動畫
效果如下:
到這里,補間動畫就介紹完了
參考資料: 自定義控件三部曲之動畫篇
硬件抽象層模塊編寫規(guī)范
硬件抽象層最終都會生成.so文件,放到系統(tǒng)對應(yīng)的目錄中。在系統(tǒng)使用的時候,系統(tǒng)會去對應(yīng)目錄下加載so文件,實現(xiàn)硬件抽象層的功能。因此硬件抽象層的加載過程就是我們使用so的一個接口。先了解加載過程從源頭了解抽象層模塊兒的編寫規(guī)范。
1、硬件抽象層加載過程
系統(tǒng)在加載so的過程中,會去兩個目錄下查找對應(yīng)id的so文件。這兩個目錄分別是/system/lib/hw和/vendor/lib/hw。
so文件的名字分為兩個部分例如id.prop.so,第一部分是模塊id。第二部分是系統(tǒng)prop的值,獲取順序為“ro.hardware”、“ro.producat.board”、“ro.board.platform”、“ro.arch”,如果prop都找不到的話,就用default。(不是找不到prop的值,是找不到prop值對應(yīng)的so文件)。
負責加載硬件抽象層模塊的函數(shù)是hw_get_module,所在的文件是/hardware/libhardware/hardware.c如下:
/** Base path of the hal modules */
#if defined(__LP64__)
#define HAL_LIBRARY_PATH1 "/system/lib64/hw"
#define HAL_LIBRARY_PATH2 "/vendor/lib64/hw"
#else
#define HAL_LIBRARY_PATH1 "/system/lib/hw"
#define HAL_LIBRARY_PATH2 "/vendor/lib/hw"
#endif
/**
* There are a set of variant filename for modules. The form of the filename
* is ".variant.so" so for the led module the Dream variants?
* of base "ro.product.board", "ro.board.platform" and "ro.arch" would be:
*
* led.trout.so
* led.msm7k.so
* led.ARMV6.so
* led.default.so
*/
static const char *variant_keys[] = {
"ro.hardware", ?/* This goes first so that it can pick up a different
file on the emulator. */
"ro.product.board",
"ro.board.platform",
"ro.arch"
};
static const int HAL_VARIANT_KEYS_COUNT =
(sizeof(variant_keys)/sizeof(variant_keys[0]));
/**
* Load the file defined by the variant and if successful
* return the dlopen handle and the hmi.
* @return 0 = success, !0 = failure.
*/
static int load(const char *id,
const char *path,
const struct hw_module_t **pHmi)
{
int status;
void *handle;
struct hw_module_t *hmi;
/*
* load the symbols resolving undefined symbols before
* dlopen returns. Since RTLD_GLOBAL is not or'd in with
* RTLD_NOW the external symbols will not be global
*/
handle = dlopen(path, RTLD_NOW);
if (handle == NULL) {
char const *err_str = dlerror();
ALOGE("load: module=%s\n%s", path, err_str?err_str:"unknown");
status = -EINVAL;
goto done;
}
/* Get the address of the struct hal_module_info. */
const char *sym = HAL_MODULE_INFO_SYM_AS_STR;
hmi = (struct hw_module_t *)dlsym(handle, sym);
if (hmi == NULL) {
ALOGE("load: couldn't find symbol %s", sym);
status = -EINVAL;
goto done;
}
/* Check that the id matches */
if (strcmp(id, hmi-id) != 0) {
ALOGE("load: id=%s != hmi-id=%s", id, hmi-id);
status = -EINVAL;
goto done;
}
hmi-dso = handle;
/* success */
status = 0;
done:
if (status != 0) {
hmi = NULL;
if (handle != NULL) {
dlclose(handle);
handle = NULL;
}
} else {
ALOGV("loaded HAL id=%s path=%s hmi=%p handle=%p",
id, path, *pHmi, handle);
}
*pHmi = hmi;
return status;
}
/*
* Check if a HAL with given name and subname exists, if so return 0, otherwise
* otherwise return negative. ?On success path will contain the path to the HAL.
*/
static int hw_module_exists(char *path, size_t path_len, const char *name,
const char *subname)
{
snprintf(path, path_len, "%s/%s.%s.so",
HAL_LIBRARY_PATH2, name, subname);
if (access(path, R_OK) == 0)
return 0;
snprintf(path, path_len, "%s/%s.%s.so",
HAL_LIBRARY_PATH1, name, subname);
if (access(path, R_OK) == 0)
return 0;
return -ENOENT;
}
int hw_get_module_by_class(const char *class_id, const char *inst,
const struct hw_module_t **module)
{
int i;
char prop[PATH_MAX];
char path[PATH_MAX];
char name[PATH_MAX];
char prop_name[PATH_MAX];
if (inst)
snprintf(name, PATH_MAX, "%s.%s", class_id, inst);
else
strlcpy(name, class_id, PATH_MAX);
/*
* Here we rely on the fact that calling dlopen multiple times on
* the same .so will simply increment a refcount (and not load
* a new copy of the library).
* We also assume that dlopen() is thread-safe.
*/
/* First try a property specific to the class and possibly instance */
snprintf(prop_name, sizeof(prop_name), "ro.hardware.%s", name);
if (property_get(prop_name, prop, NULL) 0) {
if (hw_module_exists(path, sizeof(path), name, prop) == 0) {
goto found;
}
}
/* Loop through the configuration variants looking for a module */
for (i=0 ; iHAL_VARIANT_KEYS_COUNT; i++) {
if (property_get(variant_keys[i], prop, NULL) == 0) {
continue;
}
if (hw_module_exists(path, sizeof(path), name, prop) == 0) {
goto found;
}
}
/* Nothing found, try the default */
if (hw_module_exists(path, sizeof(path), name, "default") == 0) {
goto found;
}
return -ENOENT;
found:
/* load the module, if this fails, we're doomed, and we should not try
* to load a different variant. */
return load(class_id, path, module);
}
int hw_get_module(const char *id, const struct hw_module_t **module)
{
return hw_get_module_by_class(id, NULL, module);
}
找到so文件之后,調(diào)用方法load方法去加載對應(yīng)的so文件,并返回hw_module_t結(jié)構(gòu)體。load方法源碼在上面程序中。
load方法首先調(diào)用dlopen加載對應(yīng)的so文件到內(nèi)存中。然后用dlsym方法找到變量HAL_MODULE_INFO_SYM_AS_STR符號對應(yīng)的地址,這個地址也就是一個hw_module_t結(jié)構(gòu)體,然后從這個結(jié)構(gòu)體中拿出id比對load方法出入的id是否一致,如果是的話表示打開成功。加載過程完成。
HAL_MODULE_INFO_SYM_AS_STR這個符號值為HMI,也就是必須要保證這個符號之后是一個hw_module_t。接下來的規(guī)范中有這個要求。
到此,模塊加載完成
2、硬件抽象層模塊編寫規(guī)范
硬件抽象層有兩個結(jié)構(gòu)體,一個是hw_module_t和hw_device_t,定義在hardware.h中。
首先說一下hw_module_t的編寫規(guī)范。
1、必須要有一個“自定義硬件抽象層結(jié)構(gòu)體”,且結(jié)構(gòu)體第一個變量類型要為hw_module_t。
2、必須存在一個HARDWARE_MODULE_INFO_TAG的符號,且指向“自定義硬件抽象層結(jié)構(gòu)體”。在加載的時候根據(jù)這個符號找到地址,并把地址的轉(zhuǎn)變?yōu)閔w_module_t,這也是為什么第一條中hw_module_t必須要在第一個的原因。
3、hw_module_t的tag必須為HARDWARE_MODULE_TAG
4、結(jié)構(gòu)體中要有一個方法列表,其中要有一個open方法。用open方法獲得hw_device_t
接下來說一下hw_device_t的編寫規(guī)范
1、必須要有一個“自定義硬件設(shè)備結(jié)構(gòu)體”,且結(jié)構(gòu)體第一個變量類型要為hw_device_t。
2、hw_device_t的tag必須為HARDWARE_DEVICE_TAG
3、要有一個close函數(shù)指針,來關(guān)閉設(shè)備
按照上面規(guī)范編寫的硬件抽象層就可以由系統(tǒng)加載并正確獲取到device。具體的應(yīng)用層邏輯在device中實現(xiàn)。
android:fromYDelta="48" 從起始Y坐標,偏移48個坐標
android:fromYDelta="80%p" 從80%p的位置移動
80%p---父組件的80%
Android自定義鍵盤的使用
1、新建一個xml文件夾放在res目錄下面,然后新建xml文件:money_keyboard.xml
2、然后在XML文件中添加按鈕布局,這個布局就是鍵盤的樣子了
3 屬性介紹:
Keyboard:
存儲鍵盤以及按鍵相關(guān)信息。
android:horizontalGap
按鍵之間默認的水平間距。
android:verticalGap
按鍵之間默認的垂直間距。
android:keyHeight
按鍵的默認高度,以像素或顯示高度的百分比表示。
android:keyWidth:
按鍵的默認寬度,以像素或顯示寬度的百分比表示。
Row:
為包含按鍵的容器。
Key:
用于描述鍵盤中單個鍵的位置和特性。
android:codes
該鍵輸出的unicode值。
android:codes 官網(wǎng)介紹是說這個是該鍵的unicode 值或者逗號分隔值,當然我們也可以設(shè)置成我們想要的值,在源碼中提供了幾個特定的值
對照表:
android:isRepeatable
這個屬性如果設(shè)置為true,那么當長按該鍵時就會重復(fù)接受到該鍵上的動作,在 刪除鍵鍵 和 空格鍵 上通常設(shè)為true。
android:keyLabel
顯示在按鍵上的文字。
android:keyIcon 與 keyLabel
是二選一關(guān)系,它會代替文字以圖標的形式顯示在鍵上。
android:keyWidth="33.33333%p"
每一個按鈕的寬度,可以設(shè)置百分比
android:keyHeight="10%p"
每一個按鈕高度,可以設(shè)置百分比
KeyboardView是一個渲染虛擬鍵盤的View。 它處理鍵的渲染和檢測按鍵和觸摸動作。
顯然我們需要KeyboardView來對Keyboard里的數(shù)據(jù)進行渲染并呈現(xiàn)給我們以及相關(guān)的點擊事件做處理。 1)//設(shè)置keyboard與KeyboardView相關(guān)聯(lián)的方法。
public void setKeyboard(Keyboard keyboard)
2)//設(shè)置虛擬鍵盤事件的監(jiān)聽,此方法必須設(shè)置,不然會報錯。
public void setOnKeyboardActionListener(OnKeyboardActionListener listener) 步驟上呢,做完第一步的關(guān)聯(lián),并設(shè)置第二步的事件,調(diào)用KeyboardView.setVisible(true);鍵盤就可以顯示出來了, 是不是很簡單。不過到這里還沒有結(jié)束哦,接下來我們?yōu)榱耸褂蒙系谋憷M行相應(yīng)的封裝。 封裝 這里我們通過繼承EditText來對Keyboard與KeyboardView進行封裝。
attr.xml文件,這里我們需要通過一個xml類型的自定義屬性引入我們的鍵盤描述文件。
1、新建一個類,我取名叫KeyUtils然后在里面新建三個屬性。KeyBoard用處可大了,他才是本體,可以通過設(shè)置他來切換鍵盤。
2、構(gòu)造函數(shù),初始下三個參數(shù)。
3、先說下預(yù)覽圖吧,就是效果圖上的預(yù)覽圖,需要預(yù)覽圖的話的將setPreviewEnabled設(shè)置為true,不過還得在布局文件中的android.inputmethodservice.KeyboardView標簽對立面設(shè)置預(yù)覽布局。否則,不會有字。至于設(shè)置的布局,一個TextView就好了~
onPress: 按下觸發(fā)。
onRelease:松開觸發(fā)。
onKey : 松開觸發(fā),在OnRelease之前觸發(fā)。
swipeLeft : 左滑動,其他同理。哈哈~就這么懶。
onText :需要在 鍵盤xml,也就是我此時的number.xml里面中key標簽對里添加一個
網(wǎng)站標題:android%p,androidphone停止運行
本文URL:http://chinadenli.net/article0/dsejdio.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供靜態(tài)網(wǎng)站、App設(shè)計、做網(wǎng)站、商城網(wǎng)站、微信公眾號、品牌網(wǎng)站設(shè)計
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)