关键词:android电池关机充电androidboot.mode charger关机充电 充电画面显示
平台信息:
内核:linux2.6/linux3.0
系统:android/android4.0
平台:S5PV310(samsungexynos 4210)

作者:xubin341719(欢迎转载,请注明作者)

android 电池(一):锂电池基本原理篇

android 电池(二):android关机充电流程、充电画面显示

android 电池(三):android电池系统

android电池(四):电池 电量计(MAX17040)驱动分析篇

android电池(五):电池 充电IC(PM2301)驱动分析篇


上一篇我们讲了锂电池的充放电的流程和电池的一些特性,这一节我们重点说一下android关机充电是怎么、充电画面显示是怎么实现的,这个在工作中也比较有用,我们开始做这一块的时候也走了不少的弯路。我记得我们做adnroid2.3的时候,关机状态和充电logo显示是在uboot中做的。应该是有两种做法,回头我再看下uboot中做画面显示那一块是怎么做的,这一节我们重点说系统中的充电logo显示。

一、android正常开机流程、关机充电流程

在写这篇文章之前我们先看两个流程:正常开机流程,关机充电系统启动流程

1、正常开机流程,按开机键。

可大致分成三部分

(1)、OS_level:UBOOT、kenrel、init这三步完成系统启动;

(2)、Android_level:这部分完成android部的初始化;

(3)、Home Screen:这部分就是我们看到的launcher部分。


2、关机充电系统启动流程

与前面相比,这个流程只走到init这一部分,就没有往后走了,这部分我们会在后面的代码中分析。


二、关机充电逻辑硬件逻辑

1、插入DC,charger IC从硬件上唤醒系统,相当于长按开机键开机。


下面这部分是charger IC连接系统的控制部分。


三、软件逻辑。

DC插入,其实相当于关机状态下“按开机键”开机。第一步要走UBOOT、kernel 、android init这一流程。

1、UBOOT

UBOOT启动代码我们不在这里详细分析,这里我们只要注意二个问题:

a:如何判断是DC插入;

b:设定setenv("bootargs", "androidboot.mode=charger"),androidboot.mode这个参数相当重要,这个参数决定系统是正常启动、还是关机充电状态。

Uboot/board/samsung/smdk4212/smkd4212.c

[cpp] view plain copy
  1. intboard_late_init(void)
  2. {
  3. intkeystate=0;
  4. printf("checkstartmode\n");
  5. if((*(int*)0x10020800==0x19721212)||(*(int*)0x10020804==0x19721212)
  6. ||(*(int*)0x10020808==0x19721212))//(1)、检查是否有DC插入;
  7. {
  8. setenv("bootargs","");//(2)、没有DC插入;
  9. }else{//DC插入
  10. inttmp=*(int*)0x11000c08;
  11. *(int*)0x10020800=*(int*)0x10020804=0x19721212;
  12. *(int*)0x11000c08=(tmp&(~0xc000))|0xc000;
  13. udelay(10000);
  14. if((*(int*)0x11000c04&0x80)!=0x80&&INF_REG4_REG!=0xf){
  15. setenv("bootargs","androidboot.mode=charger");//(3)、设定bootargs为charger状态
  16. printf("chargermode\n");
  17. }else{
  18. setenv("bootargs","");
  19. }
  20. *(int*)0x11000c08=tmp;
  21. }
  22. #ifdefCONFIG_CPU_EXYNOS4X12
  23. intcharge_status=CheckBatteryLow();//(4)、检查电池电量;
  24. keystate=board_key_check();//(5)、检查按键状态;
  25. //fusebootloader
  26. if(second_boot_info!=0){
  27. boot_symbol=1;
  28. INF_REG2_REG=0x8;
  29. run_command(CONFIG_BOOTCMD_FUSE_BOOTLOADER,NULL);
  30. }
  31. if((INF_REG4_REG==0xd)){
  32. //rebootdefault
  33. charbuf[10];
  34. sprintf(buf,"%d",CONFIG_BOOTDELAY);
  35. setenv("bootdelay",buf);
  36. setenv("reserved",NULL);
  37. saveenv();
  38. }elseif((INF_REG4_REG==0xe)||keystate==(0x1|0x2)){//(6)、按键进入fastboot模式;
  39. //rebootbootloader
  40. boot_symbol=1;
  41. INF_REG2_REG=0x8;
  42. printf("BOOTLOADER-FASTBOOT\n");
  43. setenv("reserved","fastboot");
  44. setenv("bootdelay","0");
  45. }elseif((INF_REG4_REG==0xf)||keystate==(0x1|0x2|0x4)){//(7)、按键进入recovery模式;
  46. //rebootrecovery
  47. printf("BOOTLOADER-RECOVERY\n");
  48. boot_symbol=1;
  49. INF_REG2_REG=0x8;
  50. setenv("reserved",CONFIG_BOOTCMD_RECOVERY);
  51. setenv("bootdelay","0");
  52. }else
  53. if(keystate==(0x1|0x4)||second_boot_info!=0||partition_check()){//(8)、按键进入卡升级模式;
  54. //2ndboot
  55. printf("BOOTLOADER-2NDBOOTDEVICE\n");
  56. boot_symbol=1;
  57. INF_REG2_REG=0x8;
  58. setenv("bootcmd",CONFIG_BOOTCOMMAND);
  59. setenv("reserved",CONFIG_BOOTCMD_FUSE_RELEASE);
  60. setenv("bootdelay","0");
  61. }else{//(9)、正常启动;
  62. //normalcase
  63. charbuf[10];
  64. sprintf(buf,"%d",CONFIG_BOOTDELAY);
  65. setenv("bootdelay",buf);
  66. }
  67. INF_REG4_REG=0;
  68. return0;
  69. }

(1)、检查是否有DC插入;

[cpp] view plain copy
  1. if((*(int*)0x10020800==0x19721212)||(*(int*)0x10020804==0x19721212)
  2. (*(int*)0x10020808==0x19721212))

这部分检查寄存器的值。

(2)、没有DC插入;

(3)、设定bootargs为charger状态

[cpp] view plain copy
  1. if((*(int*)0x11000c04&0x80)!=0x80&&INF_REG4_REG!=0xf){
  2. setenv("bootargs","androidboot.mode=charger");

这是这部分的重点,如果能过寄存器判断是DC插入,把androidboot.mode设定为charger状态。

以下这部分根据需要加入,通过判断不同的情况进入不同的功能,如fastboot\revovery…………,这部分不做详细解释。

(4)、检查电池电量;

这个在正常开机状态下,如果检测电量太低,则不开机,这部分代码就不做分析。

(5)、检查按键状态;

我们这个平台有几种模式:fastboot\recovery\卡升级等……

(6)、按键进入fastboot模式;

(7)、按键进入recovery模式;

(8)、按键进入卡升级模式

(9)、正常启动;

2、kernel

这部分和正常启动是一样的。

3、init

前面所有的描述其实只有一点和正常启动不太一样,那就是在UBOOT中把androidboot.mode设定为charger状态,内核正常流程启动,然后到init时要对charger这种状态处理。

system\core\init\init.c

[cpp] view plain copy
  1. intmain(intargc,char**argv)
  2. {
  3. ………………
  4. action_for_each_trigger("early-init",action_add_queue_tail);
  5. queue_builtin_action(wait_for_coldboot_done_action,"wait_for_coldboot_done");
  6. queue_builtin_action(property_init_action,"property_init");
  7. queue_builtin_action(keychord_init_action,"keychord_init");
  8. queue_builtin_action(console_init_action,"console_init");//(1)、显示initlogo.rle,也就是android第二张图片;
  9. queue_builtin_action(set_init_properties_action,"set_init_properties");
  10. /*executeallthebootactionstogetusstarted*/
  11. action_for_each_trigger("init",action_add_queue_tail);
  12. /*skipmountingfilesystemsinchargermode*/
  13. if(strcmp(bootmode,"charger")!=0){//(2)、这里就是UBOOT中设定的bootmode,如果是charger模式,跳过下面初始化;
  14. action_for_each_trigger("early-fs",action_add_queue_tail);
  15. action_for_each_trigger("fs",action_add_queue_tail);
  16. action_for_each_trigger("post-fs",action_add_queue_tail);
  17. action_for_each_trigger("post-fs-data",action_add_queue_tail);
  18. }
  19. queue_builtin_action(property_service_init_action,"property_service_init");
  20. queue_builtin_action(signal_init_action,"signal_init");
  21. queue_builtin_action(check_startup_action,"check_startup");
  22. if(!strcmp(bootmode,"charger")){//(3)、如果为charger,则调用charger.c。
  23. action_for_each_trigger("charger",action_add_queue_tail);
  24. }else{
  25. action_for_each_trigger("early-boot",action_add_queue_tail);
  26. action_for_each_trigger("boot",action_add_queue_tail);
  27. }
  28. ……………………
  29. }

(1)、显示initlogo.rle,也就是android第二张图片;

queue_builtin_action(console_init_action,"console_init");调用console_init_action

[cpp] view plain copy
  1. staticintconsole_init_action(intnargs,char**args)
  2. {
  3. intfd;
  4. chartmp[PROP_VALUE_MAX];
  5. if(console[0]){
  6. snprintf(tmp,sizeof(tmp),"/dev/%s",console);
  7. console_name=strdup(tmp);
  8. }
  9. fd=open(console_name,O_RDWR);
  10. if(fd>=0)
  11. have_console=1;
  12. close(fd);
  13. if(load_565rle_image(INIT_IMAGE_FILE)){//这里定义rle文件的名称#defineINIT_IMAGE_FILE"/initlogo.rle"
  14. fd=open("/dev/tty0",O_WRONLY);
  15. if(fd>=0){//如果没有这张图片,就显示android字样,在屏幕左上角;
  16. constchar*msg;
  17. msg="\n"
  18. "\n"
  19. "\n"//consoleis40colsx30lines
  20. "\n"
  21. "\n"
  22. "\n"
  23. "\n"
  24. "\n"
  25. "\n"
  26. "\n"
  27. "ANDROID";
  28. write(fd,msg,strlen(msg));
  29. close(fd);
  30. }
  31. }
  32. return0;
  33. }

(2)、这里就是UBOOT中设定的bootmode,如果是charger模式,跳过下面初始化;

[cpp] view plain copy
  1. /*skipmountingfilesystemsinchargermode*/
  2. if(strcmp(bootmode,"charger")!=0){
  3. action_for_each_trigger("early-fs",action_add_queue_tail);
  4. action_for_each_trigger("fs",action_add_queue_tail);
  5. action_for_each_trigger("post-fs",action_add_queue_tail);
  6. action_for_each_trigger("post-fs-data",action_add_queue_tail);
  7. }

(3)、如果为charger,则调用charger.c

[cpp] view plain copy
  1. action_for_each_trigger("charger",action_add_queue_tail);

我们在后面细分charger这部分。

4、charger.c

这部分就是我们充电部分,充电画面显示的实现。

system\core\charger\charger.c

[cpp] view plain copy
  1. intmain(intargc,char**argv)
  2. {
  3. ………………
  4. klog_set_level(CHARGER_KLOG_LEVEL);
  5. dump_last_kmsg();
  6. LOGI("---------------STARTINGCHARGERMODE---------------\n");
  7. gr_init();
  8. gr_font_size(&char_width,&char_height);//(1)、初始化graphics,包括buf大小;
  9. ev_init(input_callback,charger);//(2)初始化按键;
  10. fd=uevent_open_socket(64*1024,true);
  11. if(fd>=0){
  12. fcntl(fd,F_SETFL,O_NONBLOCK);
  13. ev_add_fd(fd,uevent_callback,charger);
  14. }
  15. charger->uevent_fd=fd;
  16. coldboot(charger,"/sys/class/power_supply","add");//(3)、创建/sys/class/power_supply结点,把socket信息通知应用层;
  17. ret=res_create_surface("charger/battery_fail",&charger->surf_unknown);
  18. if(ret<0){
  19. LOGE("Cannotloadimage\n");
  20. charger->surf_unknown=NULL;
  21. }
  22. for(i=0;i<charger->batt_anim->num_frames;i++){//(4)、这里是显示chargerlogo,res_create_surface显示图片函数;
  23. structframe*frame=&charger->batt_anim->frames[i];
  24. ret=res_create_surface(frame->name,&frame->surface);
  25. if(ret<0){
  26. LOGE("Cannotloadimage%s\n",frame->name);
  27. /*TODO:freethealreadyallocatedsurfaces...*/
  28. charger->batt_anim->num_frames=0;
  29. charger->batt_anim->num_cycles=1;
  30. break;
  31. }
  32. }
  33. ev_sync_key_state(set_key_callback,charger);
  34. gr_fb_blank(true);
  35. charger->next_screen_transition=now-1;
  36. charger->next_key_check=-1;
  37. charger->next_pwr_check=-1;
  38. reset_animation(charger->batt_anim);
  39. kick_animation(charger->batt_anim);
  40. event_loop(charger);//(5)、event_loop循环,电池状态,检测按键是否按下;
  41. return0;
  42. }

(1)、初始化graphics,包括buf大小

android/bootable/recovery/minui/graphics.c

gr_init():minui/graphics.c[settty0 to graphic mode, open fb0],设制tty0为图形模式,打开fb0;

[cpp] view plain copy
  1. intgr_init(void)
  2. {
  3. gglInit(&gr_context);
  4. GGLContext*gl=gr_context;
  5. gr_init_font();
  6. gr_vt_fd=open("/dev/tty0",O_RDWR|O_SYNC);
  7. if(gr_vt_fd<0){
  8. //Thisisnon-fatal;post-Cupcakekernelsdon'thavetty0.
  9. perror("can'topen/dev/tty0");
  10. }elseif(ioctl(gr_vt_fd,KDSETMODE,(void*)KD_GRAPHICS)){
  11. //However,ifwedoopentty0,weexpecttheioctltowork.
  12. perror("failedKDSETMODEtoKD_GRAPHICSontty0");
  13. gr_exit();
  14. return-1;
  15. }
  16. gr_fb_fd=get_framebuffer(gr_framebuffer);
  17. if(gr_fb_fd<0){
  18. gr_exit();
  19. return-1;
  20. }
  21. get_memory_surface(&gr_mem_surface);
  22. fprintf(stderr,"framebuffer:fd%d(%dx%d)\n",
  23. gr_fb_fd,gr_framebuffer[0].width,gr_framebuffer[0].height);
  24. /*startwith0asfront(displayed)and1asback(drawing)*/
  25. gr_active_fb=0;
  26. set_active_framebuffer(0);
  27. gl->colorBuffer(gl,&gr_mem_surface);
  28. gl->activeTexture(gl,0);
  29. gl->enable(gl,GGL_BLEND);
  30. gl->blendFunc(gl,GGL_SRC_ALPHA,GGL_ONE_MINUS_SRC_ALPHA);
  31. gr_fb_blank(true);
  32. gr_fb_blank(false);
  33. return0;
  34. }

(2)android/bootable/recovery/minui/events.c

ev_init():minui/events.c[open /dev/input/event*]打开 /dev/input/event*

这部分是在,充电状态下,按键操作的初始化,比如:短按显示充电logo,长按开机,初始化代码如下。

[cpp] view plain copy
  1. intev_init(ev_callbackinput_cb,void*data)
  2. {
  3. DIR*dir;
  4. structdirent*de;
  5. intfd;
  6. dir=opendir("/dev/input");//打开驱动结点;
  7. if(dir!=0){
  8. while((de=readdir(dir))){
  9. unsignedlongev_bits[BITS_TO_LONGS(EV_MAX)];
  10. //fprintf(stderr,"/dev/input/%s\n",de->d_name);
  11. if(strncmp(de->d_name,"event",5))continue;
  12. fd=openat(dirfd(dir),de->d_name,O_RDONLY);
  13. if(fd<0)continue;
  14. /*readtheevbitsoftheinputdevice*/
  15. if(ioctl(fd,EVIOCGBIT(0,sizeof(ev_bits)),ev_bits)<0){
  16. close(fd);
  17. continue;
  18. }
  19. /*TODO:addabilitytospecifyeventmasks.Fornow,justassume
  20. *thatonlyEV_KEYandEV_RELeventtypesareeverneeded.*/
  21. if(!test_bit(EV_KEY,ev_bits)&&!test_bit(EV_REL,ev_bits)){
  22. close(fd);
  23. continue;
  24. }
  25. ev_fds[ev_count].fd=fd;
  26. ev_fds[ev_count].events=POLLIN;
  27. ev_fdinfo[ev_count].cb=input_cb;
  28. ev_fdinfo[ev_count].data=data;
  29. ev_count++;
  30. ev_dev_count++;
  31. if(ev_dev_count==MAX_DEVICES)break;
  32. }
  33. }
  34. return0;
  35. }

(3)、创建/sys/class/power_supply结点,把socket信息通知应用层

uevent_open_socket这个函数是通过kobject_uevent的方式通知的应用层,就是往一个socket广播一个消息,只需要在应用层打开socket监听NETLINK_KOBJECT_UEVENT组的消息,就可以收到了,主要是创建了socket接口获得uevent的文件描述符,然后触发/sys/class/power_supply目录及其子目录下的uevent,然后接受并创建设备节点,至此设备节点才算创建。

(4)、这里显示charger logo,res_create_surface显示图片函数;

res_create_surface:minui/resource.c[create surfaces for all bitmaps used later, include icons, bmps]

创建surface为所以的位图,包括图标、位图。 这些图片的位置为:system\core\charger\images

(5)、event_loop循环,电池状态,检测按键是否按下;

5、event_loop

这个函数判断按键状态,DC是否插拔。如果长按开机:执行android_reboot(ANDROID_RB_RESTART,0, 0);如果拔出DC:执行android_reboot(ANDROID_RB_POWEROFF,0, 0);

[cpp] view plain copy
  1. staticvoidevent_loop(structcharger*charger)
  2. {
  3. intret;
  4. while(true){
  5. int64_tnow=curr_time_ms();//(1)、获得当前时间;
  6. LOGV("[%lld]event_loop()\n",now);
  7. handle_input_state(charger,now);//(2)、检查按键状态;
  8. handle_power_supply_state(charger,now);//(3)、检查DC是否拔出;
  9. /*doscreenupdatelastincaseanyoftheabovewanttostart
  10. *screentransitions(animations,etc)
  11. */
  12. update_screen_state(charger,now);//(4)、对按键时间状态标志位的判断,显示不同电量的充电logo;
  13. wait_next_event(charger,now);
  14. }
  15. }

(1)、获得当前时间;

int64_t now = curr_time_ms();

这个时间来判断,有没有屏幕超时,如果超时关闭屏幕充电logo显示。

(2)、检查按键状态;

[cpp] view plain copy
  1. staticvoidhandle_input_state(structcharger*charger,int64_tnow)
  2. {
  3. process_key(charger,KEY_POWER,now);
  4. if(charger->next_key_check!=-1&&now>charger->next_key_check)
  5. charger->next_key_check=-1;
  6. }
  7. 我们再看下:process_key(charger,KEY_POWER,now);
  8. staticvoidprocess_key(structcharger*charger,intcode,int64_tnow)
  9. {
  10. ………………
  11. if(code==KEY_POWER){
  12. if(key->down){
  13. int64_treboot_timeout=key->timestamp+POWER_ON_KEY_TIME;
  14. if(now>=reboot_timeout){//如果长按power键,就重新启动,也就是重启开机;
  15. LOGI("[%lld]rebooting\n",now);
  16. android_reboot(ANDROID_RB_RESTART,0,0);//重启命令;
  17. }
  18. ………………
  19. }
  20. key->pending=false;
  21. }

(3)、检查DC是否拔出;

handle_power_supply_state(charger, now);

[cpp] view plain copy
  1. staticvoidhandle_power_supply_state(structcharger*charger,int64_tnow)
  2. {
  3. if(charger->num_supplies_online==0){
  4. if(charger->next_pwr_check==-1){
  5. charger->next_pwr_check=now+UNPLUGGED_SHUTDOWN_TIME;
  6. LOGI("[%lld]deviceunplugged:shuttingdownin%lld(@%lld)\n",
  7. now,UNPLUGGED_SHUTDOWN_TIME,charger->next_pwr_check);
  8. }elseif(now>=charger->next_pwr_check){
  9. LOGI("[%lld]shuttingdown\n",now);
  10. android_reboot(ANDROID_RB_POWEROFF,0,0);//如果DC拔出,则关机;
  11. }
  12. ………………
  13. }

(4)、对按键时间状态标志位的判断,显示不同电量的充电logo;

update_screen_state(charger, now);

这个函数比较长了,其实做用就是:我们在状态的过程中,充电logo的电量是要增加的,比如电量是20%时,要从第一格开始闪烁;如果是80%时,则要从第三格开始闪烁,电量显示就是通过这个函数来计算实现的。

[cpp] view plain copy
  1. staticvoidupdate_screen_state(structcharger*charger,int64_tnow)
  2. {
  3. structanimation*batt_anim=charger->batt_anim;
  4. intcur_frame;
  5. intdisp_time;
  6. if(!batt_anim->run||now<charger->next_screen_transition)
  7. return;
  8. /*animationisover,blankscreenandleave*/
  9. if(batt_anim->cur_cycle==batt_anim->num_cycles){
  10. reset_animation(batt_anim);
  11. charger->next_screen_transition=-1;
  12. gr_fb_blank(true);
  13. LOGV("[%lld]animationdone\n",now);
  14. return;
  15. }
  16. disp_time=batt_anim->frames[batt_anim->cur_frame].disp_time;
  17. /*animationstarting,setuptheanimation*/
  18. if(batt_anim->cur_frame==0){
  19. intbatt_cap;
  20. intret;
  21. LOGV("[%lld]animationstarting\n",now);
  22. batt_cap=get_battery_capacity(charger);
  23. if(batt_cap>=0&&batt_anim->num_frames!=0){
  24. inti;
  25. /*findfirstframegivencurrentcapacity*/
  26. for(i=1;i<batt_anim->num_frames;i++){
  27. if(batt_cap<batt_anim->frames[i].min_capacity)
  28. break;
  29. }
  30. batt_anim->cur_frame=i-1;
  31. /*showthefirstframefortwiceaslong*/
  32. disp_time=batt_anim->frames[batt_anim->cur_frame].disp_time*2;
  33. }
  34. batt_anim->capacity=batt_cap;
  35. }
  36. /*unblankthescreenonfirstcycle*/
  37. if(batt_anim->cur_cycle==0)
  38. gr_fb_blank(false);
  39. /*drawthenewframe(@cur_frame)*/
  40. redraw_screen(charger);
  41. /*ifwedon'thaveanimframes,weonlyhaveoneimage,sojustbump
  42. *thecyclecounterandexit
  43. */
  44. if(batt_anim->num_frames==0||batt_anim->capacity<0){
  45. LOGV("[%lld]animationmissingorunknownbatterystatus\n",now);
  46. charger->next_screen_transition=now+BATTERY_UNKNOWN_TIME;
  47. batt_anim->cur_cycle++;
  48. return;
  49. }
  50. /*schedulenextscreentransition*/
  51. charger->next_screen_transition=now+disp_time;
  52. /*advanceframecntrtothenextvalidframe
  53. *ifnecessary,advancecyclecntr,andresetframecntr
  54. */
  55. batt_anim->cur_frame++;
  56. /*iftheframeisusedforlevel-only,thatisonlyshowitwhenit's
  57. *thecurrentlevel,skipitduringtheanimation.
  58. */
  59. while(batt_anim->cur_frame<batt_anim->num_frames&&
  60. batt_anim->frames[batt_anim->cur_frame].level_only)
  61. batt_anim->cur_frame++;
  62. if(batt_anim->cur_frame>=batt_anim->num_frames){
  63. batt_anim->cur_cycle++;
  64. batt_anim->cur_frame=0;
  65. /*don'tresetthecyclecounter,sinceweusethatasasignal
  66. *inatestabovetocheckifanimationisover
  67. */
  68. }
  69. }

下面是不能容量时显示logo的函数:

[cpp] view plain copy
  1. staticstructframebatt_anim_frames[]={
  2. {
  3. .name="charger/battery_0",
  4. .disp_time=750,
  5. .min_capacity=0,
  6. },
  7. {
  8. .name="charger/battery_1",
  9. .disp_time=750,
  10. .min_capacity=20,
  11. },
  12. {
  13. .name="charger/battery_2",
  14. .disp_time=750,
  15. .min_capacity=40,
  16. },
  17. {
  18. .name="charger/battery_3",
  19. .disp_time=750,
  20. .min_capacity=60,
  21. },
  22. {
  23. .name="charger/battery_4",
  24. .disp_time=750,
  25. .min_capacity=80,
  26. .level_only=true,
  27. },
  28. {
  29. .name="charger/battery_5",
  30. .disp_time=750,
  31. .min_capacity=BATTERY_FULL_THRESH,
  32. },
  33. };

更多相关文章

  1. Android基础UI之ListView
  2. Android(安卓)layout布局属性、标签属性总结大全
  3. android TextView 控件居右显示
  4. Android(安卓)布局的属性
  5. Android软键盘的隐藏显示
  6. android ImageView scaleType属性
  7. Android(安卓)Selector全解
  8. Android单行显示ellipse和singleLine
  9. Android(安卓)TextView文本的省略与显示

随机推荐

  1. android:targetSdkVersion问题。
  2. Coroutines in Android(安卓)- One Shot
  3. Netty多语言(Java、Android(安卓)、C#、We
  4. android MultiDex multiDex原理(一)
  5. 浅谈Android系统启动过程
  6. Android中的?attr/和?android:attr/
  7. Android布局管理器
  8. android browser 的几个小feature (二)
  9. Android开发从零开始视频教程
  10. Android应用程序与SurfaceFlinger服务的