View是所有控件的一个基类,无论是布局(Layout),还是控件(Widget)都是继承自View类。只不过layout是一个特殊的view,它里面创建一个view的数组可以包含其他的view而已。

这一篇文章把所有的layout和widget都统称为view,那么android是如何创建一个view的呢?


一。在代码中直接new出来。
比如说你要创建一个TextView的实例,那么你可以这样写:
Java代码
  1. TextViewtext=newTextView(c);//c为context对象,表明textview是在此对象中运行的。



二。把控件写在xml文件中然后通过LayoutInflater初始化一个view。

注意:下面的内容不是顺序的看的而是交替的看的。否则可能弄迷糊。
可以通过
Java代码
  1. //通过系统提供的实例获得一个LayoutInflater对象
  2. LayoutInflaterinflater=(LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
  3. //第一个参数为xml文件中view的id,第二个参数为此view的父组件,可以为null,android会自动寻找它是否拥有父组件
  4. Viewview=inflater.inflate(R.layout.resourceid,null);

这样也得到了一个view的实例,让我们一步一步来看,这个view是怎么new出来的。
看类android.view.LayoutInflater
Java代码
  1. publicViewinflate(intresource,ViewGrouproot){
  2. returninflate(resource,root,root!=null);
  3. }
  4. publicViewinflate(intresource,ViewGrouproot,booleanattachToRoot){
  5. /*可以看到通过resourceid返回了一个XmlResourceParser,通过类名就可以猜测
  6. 这是一个xml的解析类。但点进去一看,发现它只是一个接口,它继承自XmlPullParser用于pull方式解析xml的接口。和AttributeSet用于获取此view的所有属性。
  7. 那么需要能找到它的实现类。先看下面resource类。
  8. */
  9. XmlResourceParserparser=getContext().getResources().getLayout(resource);
  10. try{
  11. returninflate(parser,root,attachToRoot);
  12. }finally{
  13. parser.close();
  14. }
  15. }
  16. /**
  17. *终于到了重点,获取一个这个View的实例
  18. */
  19. publicViewinflate(XmlPullParserparser,ViewGrouproot,booleanattachToRoot){
  20. synchronized(mConstructorArgs){
  21. /**
  22. *获取一个实现此AttributeSet的实例。因为此XmlPullParser是继承自AttributeSet
  23. *的,所以parser对象可以直接作为一个AttributeSet对象。也可以用组合的方式
  24. *把parser传递给另外一个实现自AttributeSet的对象,来获取一个AttributeSet实例。
  25. **/
  26. finalAttributeSetattrs=Xml.asAttributeSet(parser);
  27. mConstructorArgs[0]=mContext;//构造函数的参数,第一个值是此view运行所在的对象context
  28. Viewresult=root;
  29. try{
  30. //parser同时也继承了xmlPullParser,所以可以用pull解析来获取此view的根节点
  31. inttype;
  32. while((type=parser.next())!=XmlPullParser.START_TAG&&
  33. type!=XmlPullParser.END_DOCUMENT){
  34. //Empty
  35. }
  36. if(type!=XmlPullParser.START_TAG){
  37. thrownewInflateException(parser.getPositionDescription()
  38. +":Nostarttagfound!");
  39. }
  40. //获得根节点标签的名字
  41. finalStringname=parser.getName();
  42. //如果它的根节点是一个merge对象,则必须手动设置此view的父节点,否则抛出异常
  43. //因为由merge创建的xml文件,常常被其他layout所包含
  44. if(TAG_MERGE.equals(name)){
  45. if(root==null||!attachToRoot){
  46. thrownewInflateException("<merge/>canbeusedonlywithavalid"
  47. +"ViewGrouprootandattachToRoot=true");
  48. }
  49. rInflate(parser,root,attrs);
  50. }else{
  51. //此inflate的xml文件中的rootview。即我们通过inflate返回得到的view
  52. Viewtemp=createViewFromTag(name,attrs);
  53. ViewGroup.LayoutParamsparams=null;
  54. if(root!=null){
  55. //Createlayoutparamsthatmatchroot,ifsupplied
  56. params=root.generateLayoutParams(attrs);
  57. if(!attachToRoot){
  58. //Setthelayoutparamsfortempifwearenot
  59. //attaching.(Ifweare,weuseaddView,below)
  60. temp.setLayoutParams(params);
  61. }
  62. }
  63. //加载temp下所有的子view
  64. rInflate(parser,temp,attrs);
  65. //如果给出了root,则把此view添加到root中去
  66. if(root!=null&&attachToRoot){
  67. root.addView(temp,params);
  68. }
  69. //Decidewhethertoreturntherootthatwaspassedinorthe
  70. //topviewfoundinxml.
  71. if(root==null||!attachToRoot){
  72. result=temp;
  73. }
  74. }
  75. }catch(XmlPullParserExceptione){
  76. InflateExceptionex=newInflateException(e.getMessage());
  77. ex.initCause(e);
  78. throwex;
  79. }catch(IOExceptione){
  80. InflateExceptionex=newInflateException(
  81. parser.getPositionDescription()
  82. +":"+e.getMessage());
  83. ex.initCause(e);
  84. throwex;
  85. }
  86. returnresult;
  87. }
  88. }
  89. /**
  90. *
  91. *有上至下递归的初始化所有子view和子view的子view。在此方法被调用完成后
  92. *会调用此view的parentview的onFinishInflate方法。表明其子view全部加载完毕
  93. */
  94. privatevoidrInflate(XmlPullParserparser,Viewparent,finalAttributeSetattrs)
  95. throwsXmlPullParserException,IOException{
  96. finalintdepth=parser.getDepth();
  97. inttype;
  98. while(((type=parser.next())!=XmlPullParser.END_TAG||
  99. parser.getDepth()>depth)&&type!=XmlPullParser.END_DOCUMENT){
  100. if(type!=XmlPullParser.START_TAG){
  101. continue;
  102. }
  103. finalStringname=parser.getName();
  104. if(TAG_REQUEST_FOCUS.equals(name)){
  105. parseRequestFocus(parser,parent);
  106. }elseif(TAG_INCLUDE.equals(name)){
  107. if(parser.getDepth()==0){
  108. thrownewInflateException("<include/>cannotbetherootelement");
  109. }
  110. parseInclude(parser,parent,attrs);
  111. }elseif(TAG_MERGE.equals(name)){
  112. thrownewInflateException("<merge/>mustbetherootelement");
  113. }else{//看这里,创建view的方法。而且这里已经重新获得了它的
  114. finalViewview=createViewFromTag(name,attrs);
  115. finalViewGroupviewGroup=(ViewGroup)parent;
  116. finalViewGroup.LayoutParamsparams=viewGroup.generateLayoutParams(attrs);
  117. rInflate(parser,view,attrs);
  118. viewGroup.addView(view,params);
  119. }
  120. }
  121. parent.onFinishInflate();
  122. }
  123. ViewcreateViewFromTag(Stringname,AttributeSetattrs){
  124. if(name.equals("view")){
  125. name=attrs.getAttributeValue(null,"class");
  126. }
  127. if(DEBUG)System.out.println("********Creatingview:"+name);
  128. try{
  129. Viewview=(mFactory==null)?null:mFactory.onCreateView(name,
  130. mContext,attrs);
  131. if(view==null){
  132. if(-1==name.indexOf('.')){//这里只是为了判断xml文件中tag的属性是否加了包名
  133. view=onCreateView(name,attrs);
  134. }else{
  135. view=createView(name,null,attrs);
  136. }
  137. }
  138. if(DEBUG)System.out.println("Createdviewis:"+view);
  139. returnview;
  140. }catch(InflateExceptione){
  141. throwe;
  142. }catch(ClassNotFoundExceptione){
  143. InflateExceptionie=newInflateException(attrs.getPositionDescription()
  144. +":Errorinflatingclass"+name);
  145. ie.initCause(e);
  146. throwie;
  147. }catch(Exceptione){
  148. InflateExceptionie=newInflateException(attrs.getPositionDescription()
  149. +":Errorinflatingclass"+name);
  150. ie.initCause(e);
  151. throwie;
  152. }
  153. }
  154. /**
  155. *真正创建一个view的方法,
  156. *此方法是用反射获取构造器来实例对象而不是直接new出来这是为了处于性能优化考虑,
  157. *同一个类名的不同对象,可以直接得到缓存的构造器直接获取一个构造器对象实例。而不需要
  158. *重复进行new操作。
  159. *
  160. *@paramname此View的全名
  161. *@paramprefix前缀,值为"android.view."其实就是是否包含包名
  162. *@paramattrs此view的属性值,传递给此view的构造函数
  163. */
  164. publicfinalViewcreateView(Stringname,Stringprefix,AttributeSetattrs)
  165. throwsClassNotFoundException,InflateException{
  166. Constructorconstructor=sConstructorMap.get(name);//缓存中是否已经有了一个构造函数
  167. Classclazz=null;
  168. try{
  169. if(constructor==null){
  170. //通过类名获得一个class对象
  171. clazz=mContext.getClassLoader().loadClass(
  172. prefix!=null?(prefix+name):name);
  173. if(mFilter!=null&&clazz!=null){
  174. booleanallowed=mFilter.onLoadClass(clazz);
  175. if(!allowed){
  176. failNotAllowed(name,prefix,attrs);
  177. }
  178. }
  179. //通过参数类型获得一个构造器,参数列表为context,attrs
  180. constructor=clazz.getConstructor(mConstructorSignature);
  181. sConstructorMap.put(name,constructor);//把此构造器缓存起来
  182. }else{
  183. //Ifwehaveafilter,applyittocachedconstructor
  184. if(mFilter!=null){
  185. //Haveweseenthisnamebefore?
  186. BooleanallowedState=mFilterMap.get(name);
  187. if(allowedState==null){
  188. //Newclass--rememberwhetheritisallowed
  189. clazz=mContext.getClassLoader().loadClass(
  190. prefix!=null?(prefix+name):name);
  191. booleanallowed=clazz!=null&&mFilter.onLoadClass(clazz);
  192. mFilterMap.put(name,allowed);
  193. if(!allowed){
  194. failNotAllowed(name,prefix,attrs);
  195. }
  196. }elseif(allowedState.equals(Boolean.FALSE)){
  197. failNotAllowed(name,prefix,attrs);
  198. }
  199. }
  200. }
  201. Object[]args=mConstructorArgs;
  202. args[1]=attrs;//args[0]已经在前面初始好了。这里只要初始化args[1]
  203. return(View)constructor.newInstance(args);//通过反射new出一个对象。。大功告成
  204. }catch(NoSuchMethodExceptione){
  205. InflateExceptionie=newInflateException(attrs.getPositionDescription()
  206. +":Errorinflatingclass"
  207. +(prefix!=null?(prefix+name):name));
  208. ie.initCause(e);
  209. throwie;
  210. }catch(ClassNotFoundExceptione){
  211. //IfloadClassfails,weshouldpropagatetheexception.
  212. throwe;
  213. }catch(Exceptione){
  214. InflateExceptionie=newInflateException(attrs.getPositionDescription()
  215. +":Errorinflatingclass"
  216. +(clazz==null?"<unknown>":clazz.getName()));
  217. ie.initCause(e);
  218. throwie;
  219. }
  220. }


在类android.content.res.Resources类中获取XmlResourceParser对象;
Java代码
  1. publicXmlResourceParsergetLayout(intid)throwsNotFoundException{
  2. returnloadXmlResourceParser(id,"layout");
  3. }
  4. ackage*/XmlResourceParserloadXmlResourceParser(intid,Stringtype)
  5. throwsNotFoundException{
  6. synchronized(mTmpValue){
  7. /*TypedValue对象保存了一些有关resource的数据值,比如说,对于一个view来说,在xml
  8. 文件中可以定义许多属性,TypedValue保存了其中一个属性的相关信息,包括此属性的值的类型
  9. type,是boolean还是color还是reference还是String,这些在attr.xml文件下都有定义。
  10. 它的值的字符串名称;一个属性有多个值时,它从xml文件中获取的值它的顺序data;如果此属性的值
  11. 的类型是一个reference则保存它的resourceid的等等。
  12. */
  13. TypedValuevalue=mTmpValue;
  14. getValue(id,value,true);
  15. if(value.type==TypedValue.TYPE_STRING){
  16. returnloadXmlResourceParser(value.string.toString(),id,
  17. value.assetCookie,type);
  18. }
  19. thrownewNotFoundException(
  20. "ResourceID#0x"+Integer.toHexString(id)+"type#0x"
  21. +Integer.toHexString(value.type)+"isnotvalid");
  22. }
  23. }
  24. /**
  25. *getValue方法,id表示要查找的控件的id,outValue是一个对象,用于保存一些属性相关信息
  26. *resolveRefs为true表明,当通过属性id找到xml文件中的标签时,比如是一个<Buttonandroid:id="@+id/button"/>
  27. *它的值是一个引用reference,则继续解析获得这个id的值。这里看AssetManager类的实现*/
  28. publicvoidgetValue(intid,TypedValueoutValue,booleanresolveRefs)
  29. throwsNotFoundException{
  30. booleanfound=mAssets.getResourceValue(id,outValue,resolveRefs);
  31. if(found){
  32. return;
  33. }
  34. thrownewNotFoundException("ResourceID#0x"
  35. +Integer.toHexString(id));
  36. }
  37. /*package*/XmlResourceParserloadXmlResourceParser(Stringfile,intid,
  38. intassetCookie,Stringtype)throwsNotFoundException{
  39. if(id!=0){
  40. try{
  41. //取缓存
  42. synchronized(mCachedXmlBlockIds){
  43. //Firstseeifthisblockisinourcache.
  44. finalintnum=mCachedXmlBlockIds.length;
  45. for(inti=0;i<num;i++){
  46. if(mCachedXmlBlockIds[i]==id){
  47. //System.out.println("****REUSINGXMLBLOCK!id="
  48. //+id+",index="+i);
  49. returnmCachedXmlBlocks[i].newParser();
  50. }
  51. }
  52. //第一次加载时,会打开这个文件获取一个xml数据块对象。
  53. //这里先看AssetManager类的实现
  54. XmlBlockblock=mAssets.openXmlBlockAsset(
  55. assetCookie,file);
  56. //下面会把此xmlBlock对象缓存起来,保存id和block,
  57. //以后如果是同样的id,直接在缓存中取XmlBlock。
  58. //这样就不用再在本地方法中打开文件创建解析树了。
  59. if(block!=null){
  60. intpos=mLastCachedXmlBlockIndex+1;
  61. if(pos>=num)pos=0;
  62. mLastCachedXmlBlockIndex=pos;
  63. XmlBlockoldBlock=mCachedXmlBlocks[pos];
  64. if(oldBlock!=null){
  65. oldBlock.close();
  66. }
  67. mCachedXmlBlockIds[pos]=id;
  68. mCachedXmlBlocks[pos]=block;
  69. //返回的内部类继承了XmlResourceParser,在APi中此类是隐藏的
  70. returnblock.newParser();
  71. }
  72. }
  73. }catch(Exceptione){
  74. NotFoundExceptionrnf=newNotFoundException(
  75. "File"+file+"fromxmltype"+type+"resourceID#0x"
  76. +Integer.toHexString(id));
  77. rnf.initCause(e);
  78. throwrnf;
  79. }
  80. }
  81. thrownewNotFoundException(
  82. "File"+file+"fromxmltype"+type+"resourceID#0x"
  83. +Integer.toHexString(id));
  84. }


android.content.res.AssetManager类
Java代码
  1. /*package*/finalbooleangetResourceValue(intident,
  2. TypedValueoutValue,
  3. booleanresolveRefs)
  4. {
  5. intblock=loadResourceValue(ident,outValue,resolveRefs);
  6. if(block>=0){
  7. if(outValue.type!=TypedValue.TYPE_STRING){
  8. returntrue;
  9. }
  10. //mStringBlocks通过本地方法保存所有布局文件的文件名
  11. outValue.string=mStringBlocks[block].get(outValue.data);
  12. returntrue;
  13. }
  14. returnfalse;
  15. }
  16. //这是一个本地方法,是在本地方法中获取这个控件信息,返回通过此控件的id找到的文件名
  17. //的位置,由于个人对c++不是很了解,只初略的解释本地方法的一些功能。
  18. //对于的JNI文件位于:\frameworks\base\core\jni\android_util_AssetManager.cpp
  19. privatenativefinalintloadResourceValue(intident,TypedValueoutValue,
  20. booleanresolve);
  21. /**
  22. *通过文件名,在本地方法中找到这个xml文件,并且在本地方法中生成一个xml解析对象。
  23. *返回一个id,这个id对应java中的xmlBlock对象。这样xml文件就被load进了内存。
  24. *也就是android所说的预编译,以后再访问只要直接去取数据即可
  25. */
  26. /*package*/finalXmlBlockopenXmlBlockAsset(intcookie,StringfileName)
  27. throwsIOException{
  28. synchronized(this){
  29. if(!mOpen){
  30. thrownewRuntimeException("Assetmanagerhasbeenclosed");
  31. }
  32. intxmlBlock=openXmlAssetNative(cookie,fileName);
  33. if(xmlBlock!=0){
  34. /*
  35. *在XmlBlock对象中,终于到找了实现XmlResourceParser接口的类
  36. *Parser,它是XmlBlock的一个内部类。这里面可以获取所有xml文件中的内容。
  37. *不管是属性还是Tag标签。这里xmlBlock是用来与本地类中的解析树对象对应的。
  38. *所有的解析方法,其实都是调用的本地xml解析树中的方法。所以此类中有大量的
  39. *本地方法。
  40. */
  41. XmlBlockres=newXmlBlock(this,xmlBlock);
  42. incRefsLocked(res.hashCode());
  43. returnres;
  44. }
  45. }
  46. thrownewFileNotFoundException("AssetXMLfile:"+fileName);
  47. }



三 。通过view.findViewById(resourceid)获得一个view的实例
android.View.View类中
Java代码
  1. //调用了通过id检索view的方法
  2. publicfinalViewfindViewById(intid){
  3. if(id<0){
  4. returnnull;
  5. }
  6. returnfindViewTraversal(id);
  7. }
  8. //不是吧,这不是坑爹吗?猜想肯定是被viewgroup重写了
  9. protectedViewfindViewTraversal(intid){
  10. if(id==mID){
  11. returnthis;
  12. }
  13. returnnull;
  14. }


android.View.ViewGroup类中
Java代码
  1. //哈哈,果然重写了此方法。其实就是在viewgroup包含的
  2. //子view数组中进行遍历。那么view是什么时候被加入进
  3. //viewgroup中的呢?如果是在代码中写,肯定是直接使用
  4. //addView方法把view加入viewGroup。如果写在xml布局文件
  5. //中,其实是在第二种方法中被加入view的。inflate加载父view
  6. //时会同时把其所有的子view加载完,同时addView到父view中
  7. protectedViewfindViewTraversal(intid){
  8. if(id==mID){
  9. returnthis;
  10. }
  11. finalView[]where=mChildren;
  12. finalintlen=mChildrenCount;
  13. for(inti=0;i<len;i++){
  14. Viewv=where[i];
  15. if((v.mPrivateFlags&IS_ROOT_NAMESPACE)==0){
  16. v=v.findViewById(id);
  17. if(v!=null){
  18. returnv;
  19. }
  20. }
  21. }
  22. returnnull;
  23. }



四。通过activity的setContentView方法和findViewById获取一个view的实例。
它是通过
getWindow().setContentView(layoutResID);设置window对象的view
再来看看window对象是在哪里获得到的,在类Activity中找到
mWindow = PolicyManager.makeNewWindow(this);
它是由PolicyManager生成的。
找到com.android.internal.policy.PolicyManager,找到方法
Java代码
  1. //window是由sPolicy对象创建的
  2. publicstaticWindowmakeNewWindow(Contextcontext){
  3. returnsPolicy.makeNewWindow(context);
  4. }
  5. //sPolicy对象是通过反射,获取的一个实例
  6. //此类的实现在com.android.internal.policy.impl.Policy中
  7. privatestaticfinalStringPOLICY_IMPL_CLASS_NAME=
  8. "com.android.internal.policy.impl.Policy";
  9. privatestaticfinalIPolicysPolicy;
  10. static{
  11. //Pullintheactualimplementationofthepolicyatrun-time
  12. try{
  13. ClasspolicyClass=Class.forName(POLICY_IMPL_CLASS_NAME);
  14. sPolicy=(IPolicy)policyClass.newInstance();
  15. }catch(ClassNotFoundExceptionex){
  16. thrownewRuntimeException(
  17. POLICY_IMPL_CLASS_NAME+"couldnotbeloaded",ex);
  18. }catch(InstantiationExceptionex){
  19. thrownewRuntimeException(
  20. POLICY_IMPL_CLASS_NAME+"couldnotbeinstantiated",ex);
  21. }catch(IllegalAccessExceptionex){
  22. thrownewRuntimeException(
  23. POLICY_IMPL_CLASS_NAME+"couldnotbeinstantiated",ex);
  24. }
  25. }



找到com.android.internal.policy.impl.Policy类

Java代码
  1. publicPhoneWindowmakeNewWindow(Contextcontext){
  2. returnnewPhoneWindow(context);
  3. }



它其实是一个phoneWindow对象,继承自window对象
找到com.android.internal.policy.impl.phoneWindow 看它内部是如何把resourceid加载成一个view的

Java代码
  1. privateViewGroupmContentParent;
  2. //这是window的顶层视图,它包含一些窗口的装饰,比图titlebar,状态栏等等
  3. privateDecorViewmDecor;
  4. //这里的layoutResID也是由mLayoutInflater进行加载的,加载的方式与第二种方法一样。
  5. //只不过这里把的到的view变成了mContentParent的子view
  6. @Override
  7. publicvoidsetContentView(intlayoutResID){
  8. if(mContentParent==null){
  9. installDecor();
  10. }else{
  11. mContentParent.removeAllViews();
  12. }
  13. mLayoutInflater.inflate(layoutResID,mContentParent);
  14. finalCallbackcb=getCallback();
  15. if(cb!=null){//这是回调方法,表明mContentParent的子view已经发生改变
  16. cb.onContentChanged();
  17. }
  18. }
  19. //再来看看mContentParent究竟是何物,它肯定是一个viewGroup
  20. privatevoidinstallDecor(){
  21. if(mDecor==null){
  22. mDecor=generateDecor();
  23. mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
  24. mDecor.setIsRootNamespace(true);
  25. }
  26. if(mContentParent==null){
  27. mContentParent=generateLayout(mDecor);
  28. mTitleView=(TextView)findViewById(com.android.internal.R.id.title);
  29. if(mTitleView!=null){//这里设置的是是否隐藏titleContainer,即头部titlebar
  30. if((getLocalFeatures()&(1<<FEATURE_NO_TITLE))!=0){
  31. ViewtitleContainer=findViewById(com.android.internal.R.id.title_container);
  32. if(titleContainer!=null){
  33. titleContainer.setVisibility(View.GONE);
  34. }else{
  35. mTitleView.setVisibility(View.GONE);
  36. }
  37. if(mContentParentinstanceofFrameLayout){
  38. ((FrameLayout)mContentParent).setForeground(null);
  39. }
  40. }else{
  41. mTitleView.setText(mTitle);
  42. }
  43. }
  44. }
  45. }
  46. //当顶层view为null是,new了一个DecorView
  47. protectedDecorViewgenerateDecor(){
  48. returnnewDecorView(getContext(),-1);
  49. }
  50. //这里生成了mContentParent。
  51. protectedViewGroupgenerateLayout(DecorViewdecor){
  52. mDecor.startChanging();
  53. //根据window的不同参数选择layoutResource
  54. Viewin=mLayoutInflater.inflate(layoutResource,null);
  55. decor.addView(in,newViewGroup.LayoutParams(MATCH_PARENT,MATCH_PARENT));
  56. ViewGroupcontentParent=(ViewGroup)findViewById(ID_ANDROID_CONTENT);
  57. if(contentParent==null){
  58. thrownewRuntimeException("Windowcouldn'tfindcontentcontainerview");
  59. }
  60. returncontentParent;
  61. }
  62. //顶层view是一个framelayout
  63. privatefinalclassDecorViewextendsFrameLayoutimplementsRootViewSurfaceTaker{
  64. publicDecorView(Contextcontext,intfeatureId){
  65. super(context);
  66. mFeatureId=featureId;
  67. }
  68. }
  69. //下面说明findVIewById
  70. //首先是获取顶层view,即继承自FrameLayout的viewgorup
  71. @Override
  72. publicfinalViewgetDecorView(){
  73. if(mDecor==null){
  74. installDecor();
  75. }
  76. returnmDecor;
  77. }
  78. //然后mDecor.findViewById根据id获取它的子view
  79. //这里就是通过第三种方法获取它的子view

更多相关文章

  1. 一款常用的 Squid 日志分析工具
  2. GitHub 标星 8K+!一款开源替代 ls 的工具你值得拥有!
  3. RHEL 6 下 DHCP+TFTP+FTP+PXE+Kickstart 实现无人值守安装
  4. Linux 环境下实战 Rsync 备份工具及配置 rsync+inotify 实时同步
  5. 第三方应用获得system权限
  6. Android(安卓)Handler机制之循环消息队列的退出
  7. 在 Eclipse 中使用 NDK
  8. Android学习笔记——Activity的四种启动模式
  9. Android中的Menu(菜单)的三种类型菜单的学习

随机推荐

  1. Qt on Android: Android(安卓)SDK安装
  2. android makefile prebuild
  3. android LinearLayout android:layout_we
  4. Mac下Android(安卓)Studio使用
  5. android gravity和layout_gravity区别
  6. android 动态切换主题,动态换肤
  7. 《精通Android(安卓)2》书评
  8. 使用 Android(安卓)Compatibility Packag
  9. Android(安卓)ListView 不显示分割条 分
  10. 【Android】跑马灯效果(文字滚动)