FROM:http://blog.csdn.net/hdhd588/article/details/6739281


=============================================================


应用程序包的安装是android的特点

APK---AndroidPackage


Android应用安装有如下四种方式

1.系统应用安装---开机时完成,没有安装界面

2.网络下载应用安装---通过market应用完成,没有安装界面

3.ADB工具安装---没有安装界面。

4.第三方应用安装---通过SD卡里的APK文件安装,有安装界面,由 packageinstaller.apk应用处理安装及卸载过程的界面。


应用安装的流程及路径
应用安装涉及到如下几个目录:

system/app---------------系统自带的应用程序,获得adbroot权限才能删除

data/app---------------用户程序安装的目录。安装时把apk文件复制到此目录

data/data---------------存放应用程序的数据

data/dalvik-cache--------将apk中的dex文件安装到dalvik-cache目录下(dex文件是dalvik虚拟机的可执行文件,其大小约为原始apk文件大小的四分之一)


安装过程:

复制APK安装包到data/app目录下,解压并扫描安装包,把dex文件(Dalvik字节码)保存到dalvik-cache目录,并data/data目录下创建对应的应用数据目录。

卸载过程:

删除安装过程中在上述三个目录下创建的文件及目录。


安装应用的过程解析

一.开机安装
PackageManagerService处理各种应用的安装,卸载,管理等工作,开机时由systemServer启动此服务

(源文件路径:android\frameworks\base\services\java\com\android\server\PackageManagerService.java)

PackageManagerService服务启动的流程:

1.首先扫描安装“system\framework”目录下的jar包

[java] view plain copy
  1. //Findbaseframeworks(resourcepackageswithoutcode).
  2. mFrameworkInstallObserver=newAppDirObserver(
  3. mFrameworkDir.getPath(),OBSERVER_EVENTS,true);
  4. mFrameworkInstallObserver.startWatching();
  5. scanDirLI(mFrameworkDir,PackageParser.PARSE_IS_SYSTEM
  6. |PackageParser.PARSE_IS_SYSTEM_DIR,
  7. scanMode|SCAN_NO_DEX,0);

2.扫描安装系统system/app的应用程序

[java] view plain copy
  1. //Collectallsystempackages.
  2. mSystemAppDir=newFile(Environment.getRootDirectory(),"app");
  3. mSystemInstallObserver=newAppDirObserver(
  4. mSystemAppDir.getPath(),OBSERVER_EVENTS,true);
  5. mSystemInstallObserver.startWatching();
  6. scanDirLI(mSystemAppDir,PackageParser.PARSE_IS_SYSTEM
  7. |PackageParser.PARSE_IS_SYSTEM_DIR,scanMode,0);

3.制造商的目录下/vendor/app应用包

[java] view plain copy
  1. //Collectallvendorpackages.
  2. mVendorAppDir=newFile("/vendor/app");
  3. mVendorInstallObserver=newAppDirObserver(
  4. mVendorAppDir.getPath(),OBSERVER_EVENTS,true);
  5. mVendorInstallObserver.startWatching();
  6. scanDirLI(mVendorAppDir,PackageParser.PARSE_IS_SYSTEM
  7. |PackageParser.PARSE_IS_SYSTEM_DIR,scanMode,0);

4.扫描“data\app”目录,即用户安装的第三方应用

[java] view plain copy
  1. scanDirLI(mAppInstallDir,0,scanMode,0);

5.扫描"data\app-private"目录,即安装DRM保护的APK文件(一个受保护的歌曲或受保 护的视频是使用DRM保护的文件)

[java] view plain copy
  1. scanDirLI(mDrmAppPrivateInstallDir,PackageParser.PARSE_FORWARD_LOCK,
  2. scanMode,0);

扫描方法的代码清单

[java] view plain copy
  1. privatevoidscanDirLI(Filedir,intflags,intscanMode,longcurrentTime){
  2. String[]files=dir.list();
  3. if(files==null){
  4. Log.d(TAG,"Nofilesinappdir"+dir);
  5. return;
  6. }
  7. if(false){
  8. Log.d(TAG,"Scanningappdir"+dir);
  9. }
  10. inti;
  11. for(i=0;i<files.length;i++){
  12. Filefile=newFile(dir,files[i]);
  13. if(!isPackageFilename(files[i])){
  14. //Ignoreentrieswhicharenotapk's
  15. continue;
  16. }
  17. PackageParser.Packagepkg=scanPackageLI(file,
  18. flags|PackageParser.PARSE_MUST_BE_APK,scanMode,currentTime);
  19. //Don'tmessaroundwithappsinsystempartition.
  20. if(pkg==null&&(flags&PackageParser.PARSE_IS_SYSTEM)==0&&
  21. mLastScanError==PackageManager.INSTALL_FAILED_INVALID_APK){
  22. //Deletetheapk
  23. Slog.w(TAG,"Cleaningupfailedinstallof"+file);
  24. file.delete();
  25. }
  26. }
  27. }

并且从该扫描方法中可以看出调用了scanPackageLI()

privatePackageParser.PackagescanPackageLI(FilescanFile,

intparseFlags,intscanMode,longcurrentTime)

跟踪scanPackageLI()方法后发现,程序经过很多次的ifelse的筛选,最后判定可以安装后调用了mInstaller.install

[java] view plain copy
  1. if(mInstaller!=null){
  2. intret=mInstaller.install(pkgName,useEncryptedFSDir,pkg.applicationInfo.uid,pkg.applicationInfo.uid);
  3. if(ret<0){
  4. //Errorfrominstaller
  5. mLastScanError=PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
  6. returnnull;
  7. }
  8. }

mInstaller.install()通过 LocalSocketAddressaddress=newLocalSocketAddress("installd",LocalSocketAddress.Namespace.RESERVED);

指挥installd在C语言的文件中完成工作。


PackageManagerService小节:1)从apk,xml中载入pacakge信息,存储到内部成员变量中,用于后面的查找.关键的方法是scanPackageLI().
2)各种查询操作,包括queryIntent操作.
3)installpackage和deletepackage的操作.还有后面的关键方法是installPackageLI().


二、从网络上下载应用:

下载完成后,会自动调用Packagemanager的安装方法installPackage()

/*Calledwhenadownloadedpackageinstallationhasbeenconfirmedbytheuser*/

由英文注释可见PackageManagerService类的installPackage()函数为安装程序入口。

[java] view plain copy
  1. publicvoidinstallPackage(
  2. finalUripackageURI,finalIPackageInstallObserverobserver,finalintflags,
  3. finalStringinstallerPackageName){
  4. mContext.enforceCallingOrSelfPermission(
  5. android.Manifest.permission.INSTALL_PACKAGES,null);
  6. Messagemsg=mHandler.obtainMessage(INIT_COPY);
  7. msg.obj=newInstallParams(packageURI,observer,flags,
  8. installerPackageName);
  9. mHandler.sendMessage(msg);
  10. }

其中是通过PackageHandler的实例mhandler.sendMessage(msg)把信息发给继承Handler的类HandleMessage()方法

[java] view plain copy
  1. classPackageHandlerextendsHandler{
  2. *****************省略若干********************
  3. publicvoidhandleMessage(Messagemsg){
  4. try{
  5. doHandleMessage(msg);
  6. }finally{
  7. Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
  8. }
  9. }
  10. ******************省略若干**********************
  11. }

把信息发给doHandleMessage()方法,方法中用switch()语句进行判定传来Message

[java] view plain copy
  1. voiddoHandleMessage(Messagemsg){
  2. switch(msg.what){
  3. caseINIT_COPY:{
  4. if(DEBUG_SD_INSTALL)Log.i(TAG,"init_copy");
  5. HandlerParamsparams=(HandlerParams)msg.obj;
  6. intidx=mPendingInstalls.size();
  7. if(DEBUG_SD_INSTALL)Log.i(TAG,"idx="+idx);
  8. //Ifabindwasalreadyinitiatedwedontreally
  9. //needtodoanything.Thependinginstall
  10. //willbeprocessedlateron.
  11. if(!mBound){
  12. //Ifthisistheonlyonependingwemight
  13. //havetobindtotheserviceagain.
  14. if(!connectToService()){
  15. Slog.e(TAG,"Failedtobindtomediacontainerservice");
  16. params.serviceError();
  17. return;
  18. }else{
  19. //Oncewebindtotheservice,thefirst
  20. //pendingrequestwillbeprocessed.
  21. mPendingInstalls.add(idx,params);
  22. }
  23. }else{
  24. mPendingInstalls.add(idx,params);
  25. //Alreadyboundtotheservice.Justmake
  26. //surewetriggeroffprocessingthefirstrequest.
  27. if(idx==0){
  28. mHandler.sendEmptyMessage(MCS_BOUND);
  29. }
  30. }
  31. break;
  32. }
  33. caseMCS_BOUND:{
  34. if(DEBUG_SD_INSTALL)Log.i(TAG,"mcs_bound");
  35. if(msg.obj!=null){
  36. mContainerService=(IMediaContainerService)msg.obj;
  37. }
  38. if(mContainerService==null){
  39. //Somethingseriouslywrong.Bailout
  40. Slog.e(TAG,"Cannotbindtomediacontainerservice");
  41. for(HandlerParamsparams:mPendingInstalls){
  42. mPendingInstalls.remove(0);
  43. //Indicateservicebinderror
  44. params.serviceError();
  45. }
  46. mPendingInstalls.clear();
  47. }elseif(mPendingInstalls.size()>0){
  48. HandlerParamsparams=mPendingInstalls.get(0);
  49. if(params!=null){
  50. params.startCopy();
  51. }
  52. }else{
  53. //Shouldneverhappenideally.
  54. Slog.w(TAG,"Emptyqueue");
  55. }
  56. break;
  57. }
  58. ****************省略若干**********************
  59. }
  60. }

publicfinalbooleansendMessage(Messagemsg)

publicfinalbooleansendEmptyMessage(intwhat)

两者参数有别。

然后调用抽象类HandlerParams中的一个startCopy()方法

abstractclassHandlerParams{

finalvoidstartCopy(){

***************若干if语句判定否这打回handler消息*******

handleReturnCode();

}
}

handleReturnCode()复写了两次其中有一次是删除时要调用的,只列出安装调用的一个方法

[java] view plain copy
  1. @Override
  2. voidhandleReturnCode(){
  3. //IfmArgsisnull,thenMCScouldn'tbereached.Whenit
  4. //reconnects,itwilltryagaintoinstall.Atthatpoint,this
  5. //willsucceed.
  6. if(mArgs!=null){
  7. processPendingInstall(mArgs,mRet);
  8. }
  9. }

这时可以清楚的看见processPendingInstall()被调用。

其中run()方法如下

[java] view plain copy
  1. run(){
  2. synchronized(mInstallLock){
  3. ************省略*****************
  4. installPackageLI(args,true,res);
  5. }
  6. }
  7. instaPacakgeLI()args,res参数分析

-----------------------------------------------------------------------------------------

//InstallArgs是在PackageService定义的staticabstractclassInstallArgs静态抽象类。

[java] view plain copy
  1. staticabstractclassInstallArgs{
  2. *********************************************************************
  3. 其中定义了flag标志,packageURL,创建文件,拷贝apk,修改包名称,
  4. 还有一些删除文件的清理,释放存储函数。
  5. *********************************************************************
  6. }
  7. classPackageInstalledInfo{
  8. Stringname;
  9. intuid;
  10. PackageParser.Packagepkg;
  11. intreturnCode;
  12. PackageRemovedInforemovedInfo;
  13. }

-----------------------------------------------------------------------------------------

[java] view plain copy
  1. privatevoidinstallPackageLI(InstallArgsargs,
  2. booleannewInstall,PackageInstalledInfores){
  3. intpFlags=args.flags;
  4. StringinstallerPackageName=args.installerPackageName;
  5. FiletmpPackageFile=newFile(args.getCodePath());
  6. booleanforwardLocked=((pFlags&PackageManager.INSTALL_FORWARD_LOCK)!=0);
  7. booleanonSd=((pFlags&PackageManager.INSTALL_EXTERNAL)!=0);
  8. booleanreplace=false;
  9. intscanMode=(onSd?0:SCAN_MONITOR)|SCAN_FORCE_DEX|SCAN_UPDATE_SIGNATURE
  10. |(newInstall?SCAN_NEW_INSTALL:0);
  11. //Resultobjecttobereturned
  12. res.returnCode=PackageManager.INSTALL_SUCCEEDED;
  13. //RetrievePackageSettingsandparsepackage
  14. intparseFlags=PackageParser.PARSE_CHATTY|
  15. (forwardLocked?PackageParser.PARSE_FORWARD_LOCK:0)|
  16. (onSd?PackageParser.PARSE_ON_SDCARD:0);
  17. parseFlags|=mDefParseFlags;
  18. PackageParserpp=newPackageParser(tmpPackageFile.getPath());
  19. pp.setSeparateProcesses(mSeparateProcesses);
  20. finalPackageParser.Packagepkg=pp.parsePackage(tmpPackageFile,
  21. null,mMetrics,parseFlags);
  22. if(pkg==null){
  23. res.returnCode=pp.getParseError();
  24. return;
  25. }
  26. StringpkgName=res.name=pkg.packageName;
  27. if((pkg.applicationInfo.flags&ApplicationInfo.FLAG_TEST_ONLY)!=0){
  28. if((pFlags&PackageManager.INSTALL_ALLOW_TEST)==0){
  29. res.returnCode=PackageManager.INSTALL_FAILED_TEST_ONLY;
  30. return;
  31. }
  32. }
  33. if(GET_CERTIFICATES&&!pp.collectCertificates(pkg,parseFlags)){
  34. res.returnCode=pp.getParseError();
  35. return;
  36. }
  37. //Getridofallreferencestopackagescanpathviaparser.
  38. pp=null;
  39. StringoldCodePath=null;
  40. booleansystemApp=false;
  41. synchronized(mPackages){
  42. //Checkifinstallingalreadyexistingpackage
  43. if((pFlags&PackageManager.INSTALL_REPLACE_EXISTING)!=0){
  44. StringoldName=mSettings.mRenamedPackages.get(pkgName);
  45. if(pkg.mOriginalPackages!=null
  46. &&pkg.mOriginalPackages.contains(oldName)
  47. &&mPackages.containsKey(oldName)){
  48. //Thispackageisderivedfromanoriginalpackage,
  49. //andthisdevicehasbeenupdatingfromthatoriginal
  50. //name.Wemustcontinueusingtheoriginalname,so
  51. //renamethenewpackagehere.
  52. pkg.setPackageName(oldName);
  53. pkgName=pkg.packageName;
  54. replace=true;
  55. }elseif(mPackages.containsKey(pkgName)){
  56. //Thispackage,underitsofficialname,alreadyexists
  57. //onthedevice;weshouldreplaceit.
  58. replace=true;
  59. }
  60. }
  61. PackageSettingps=mSettings.mPackages.get(pkgName);
  62. if(ps!=null){
  63. oldCodePath=mSettings.mPackages.get(pkgName).codePathString;
  64. if(ps.pkg!=null&&ps.pkg.applicationInfo!=null){
  65. systemApp=(ps.pkg.applicationInfo.flags&
  66. ApplicationInfo.FLAG_SYSTEM)!=0;
  67. }
  68. }
  69. }
  70. if(systemApp&&onSd){
  71. //Disableupdatestosystemappsonsdcard
  72. Slog.w(TAG,"Cannotinstallupdatestosystemappsonsdcard");
  73. res.returnCode=PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
  74. return;
  75. }
  76. if(!args.doRename(res.returnCode,pkgName,oldCodePath)){
  77. res.returnCode=PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
  78. return;
  79. }
  80. //Setapplicationobjectspathexplicitlyaftertherename
  81. setApplicationInfoPaths(pkg,args.getCodePath(),args.getResourcePath());
  82. pkg.applicationInfo.nativeLibraryDir=args.getNativeLibraryPath();
  83. if(replace){
  84. replacePackageLI(pkg,parseFlags,scanMode,
  85. installerPackageName,res);
  86. }else{
  87. installNewPackageLI(pkg,parseFlags,scanMode,
  88. installerPackageName,res);
  89. }
  90. }

最后判断如果以前不存在那么调用installNewPackageLI()

[java] view plain copy
  1. privatevoidinstallNewPackageLI(PackageParser.Packagepkg,
  2. intparseFlags,intscanMode,
  3. StringinstallerPackageName,PackageInstalledInfores){
  4. ***********************省略若干*************************************************
  5. PackageParser.PackagenewPackage=scanPackageLI(pkg,parseFlags,scanMode,
  6. System.currentTimeMillis());
  7. ***********************省略若干**************************************************
  8. }

最后终于回到了和开机安装一样的地方.与开机方式安装调用统一方法。

三、从ADB工具安装

其入口函数源文件为pm.java

(源文件路径:android\frameworks\base\cmds\pm\src\com\android\commands\pm\pm.java)

其中\system\framework\pm.jar包管理库

包管理脚本\system\bin\pm解析

showUsage就是使用方法

[java] view plain copy
  1. privatestaticvoidshowUsage(){
  2. System.err.println("usage:pm[list|path|install|uninstall]");
  3. System.err.println("pmlistpackages[-f]");
  4. System.err.println("pmlistpermission-groups");
  5. System.err.println("pmlistpermissions[-g][-f][-d][-u][GROUP]");
  6. System.err.println("pmlistinstrumentation[-f][TARGET-PACKAGE]");
  7. System.err.println("pmlistfeatures");
  8. System.err.println("pmpathPACKAGE");
  9. System.err.println("pminstall[-l][-r][-t][-iINSTALLER_PACKAGE_NAME][-s][-f]PATH");
  10. System.err.println("pmuninstall[-k]PACKAGE");
  11. System.err.println("pmenablePACKAGE_OR_COMPONENT");
  12. System.err.println("pmdisablePACKAGE_OR_COMPONENT");
  13. System.err.println("pmsetInstallLocation[0/auto][1/internal][2/external]");
  14. **********************省略**************************
  15. }

安装时候会调用runInstall()方法

[java] view plain copy
  1. privatevoidrunInstall(){
  2. intinstallFlags=0;
  3. StringinstallerPackageName=null;
  4. Stringopt;
  5. while((opt=nextOption())!=null){
  6. if(opt.equals("-l")){
  7. installFlags|=PackageManager.INSTALL_FORWARD_LOCK;
  8. }elseif(opt.equals("-r")){
  9. installFlags|=PackageManager.INSTALL_REPLACE_EXISTING;
  10. }elseif(opt.equals("-i")){
  11. installerPackageName=nextOptionData();
  12. if(installerPackageName==null){
  13. System.err.println("Error:novaluespecifiedfor-i");
  14. showUsage();
  15. return;
  16. }
  17. }elseif(opt.equals("-t")){
  18. installFlags|=PackageManager.INSTALL_ALLOW_TEST;
  19. }elseif(opt.equals("-s")){
  20. //Overrideif-soptionisspecified.
  21. installFlags|=PackageManager.INSTALL_EXTERNAL;
  22. }elseif(opt.equals("-f")){
  23. //Overrideif-soptionisspecified.
  24. installFlags|=PackageManager.INSTALL_INTERNAL;
  25. }else{
  26. System.err.println("Error:Unknownoption:"+opt);
  27. showUsage();
  28. return;
  29. }
  30. }
  31. StringapkFilePath=nextArg();
  32. System.err.println("\tpkg:"+apkFilePath);
  33. if(apkFilePath==null){
  34. System.err.println("Error:nopackagespecified");
  35. showUsage();
  36. return;
  37. }
  38. PackageInstallObserverobs=newPackageInstallObserver();
  39. try{
  40. mPm.installPackage(Uri.fromFile(newFile(apkFilePath)),obs,installFlags,
  41. installerPackageName);
  42. synchronized(obs){
  43. while(!obs.finished){
  44. try{
  45. obs.wait();
  46. }catch(InterruptedExceptione){
  47. }
  48. }
  49. if(obs.result==PackageManager.INSTALL_SUCCEEDED){
  50. System.out.println("Success");
  51. }else{
  52. System.err.println("Failure["
  53. +installFailureToString(obs.result)
  54. +"]");
  55. }
  56. }
  57. }catch(RemoteExceptione){
  58. System.err.println(e.toString());
  59. System.err.println(PM_NOT_RUNNING_ERR);
  60. }
  61. }

其中的

PackageInstallObserverobs=newPackageInstallObserver();

mPm.installPackage(Uri.fromFile(newFile(apkFilePath)),obs,installFlags,installerPackageName);

如果安装成功

obs.result==PackageManager.INSTALL_SUCCEEDED)

又因为有

IPackageManagemPm;

mPm=IpackageManager.Stub.asInterface(ServiceManager.getService("package"));


Stub是接口IPackageManage的静态抽象类,asInterface是返回IPackageManager代理的静态方法。

因为classPackageManagerServiceextendsIPackageManager.Stub

所以mPm.installPackage调用

/*Calledwhenadownloadedpackageinstallationhasbeenconfirmedbytheuser*/

publicvoidinstallPackage(

finalUripackageURI,finalIPackageInstallObserverobserver,finalintflags,finalStringinstallerPackageName)

这样就是从网络下载安装的入口了。

四,从SD卡安装

系统调用PackageInstallerActivity.java(/home/zhongda/androidSRC/vortex-8inch-for-hoperun/packages/apps/PackageInstaller/src/com/android/packageinstaller)

进入这个Activity会判断信息是否有错,然后调用

privatevoidinitiateInstall()判断是否曾经有过同名包的安装,或者包已经安装

通过后执行privatevoidstartInstallConfirm()点击OK按钮后经过一系列的安装信息的判断Intent跳转到

[java] view plain copy
  1. publicclassInstallAppProgressextendsActivityimplementsView.OnClickListener,OnCancelListener
  2. publicvoidonCreate(Bundleicicle){
  3. super.onCreate(icicle);
  4. Intentintent=getIntent();
  5. mAppInfo=intent.getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO);
  6. mPackageURI=intent.getData();
  7. initView();
  8. }

方法中调用了initView()方法

[java] view plain copy
  1. publicvoidinitView(){
  2. requestWindowFeature(Window.FEATURE_NO_TITLE);
  3. setContentView(R.layout.op_progress);
  4. intinstallFlags=0;
  5. PackageManagerpm=getPackageManager();
  6. try{
  7. PackageInfopi=pm.getPackageInfo(mAppInfo.packageName,
  8. PackageManager.GET_UNINSTALLED_PACKAGES);
  9. if(pi!=null){
  10. installFlags|=PackageManager.INSTALL_REPLACE_EXISTING;
  11. }
  12. }catch(NameNotFoundExceptione){
  13. }
  14. if((installFlags&PackageManager.INSTALL_REPLACE_EXISTING)!=0){
  15. Log.w(TAG,"Replacingpackage:"+mAppInfo.packageName);
  16. }
  17. PackageUtil.AppSnippetas=PackageUtil.getAppSnippet(this,mAppInfo,
  18. mPackageURI);
  19. mLabel=as.label;
  20. PackageUtil.initSnippetForNewApp(this,as,R.id.app_snippet);
  21. mStatusTextView=(TextView)findViewById(R.id.center_text);
  22. mStatusTextView.setText(R.string.installing);
  23. mProgressBar=(ProgressBar)findViewById(R.id.progress_bar);
  24. mProgressBar.setIndeterminate(true);
  25. //Hidebuttontillprogressisbeingdisplayed
  26. mOkPanel=(View)findViewById(R.id.buttons_panel);
  27. mDoneButton=(Button)findViewById(R.id.done_button);
  28. mLaunchButton=(Button)findViewById(R.id.launch_button);
  29. mOkPanel.setVisibility(View.INVISIBLE);
  30. StringinstallerPackageName=getIntent().getStringExtra(
  31. Intent.EXTRA_INSTALLER_PACKAGE_NAME);
  32. PackageInstallObserverobserver=newPackageInstallObserver();
  33. pm.installPackage(mPackageURI,observer,installFlags,installerPackageName);
  34. }


方法最后我们可以看到再次调用安装接口installPackage()完成安装。

更多相关文章

  1. 浅谈Java中Collections.sort对List排序的两种方法
  2. Pycharm安装PyQt5的详细教程
  3. Python list sort方法的具体使用
  4. python list.sort()根据多个关键字排序的方法实现
  5. onRetainNonConfigurationInstance和getLastNonConfigurationIns
  6. android Activity生命周期详解(图文)
  7. Android获取本机Mac地址及IP地址方法
  8. popwindow动画显示消失,activity切换动画
  9. Android硬件访问服务 (硬核最终篇)

随机推荐

  1. android textview部分字体变颜色
  2. Android菜单实例
  3. Android(安卓)图形密码
  4. Android(安卓)珍藏(三)
  5. android widget的关系图
  6. 复选框
  7. Android设置竖屏
  8. RelativeLayout用到的一些重要的属性
  9. 使用Android(安卓)studio分析内存泄露
  10. [Android]Common Sreen Size of Android