首页 文章 精选 留言 我的

精选列表

搜索[懒加载],共10000篇文章
优秀的个人博客,低调大师

去掉Flex4生成的SWF加载时的进度条

原文 http://www.cnblogs.com/modou/articles/1955507.html 方法一: <?xml version="1.0" encoding="utf-8"?> <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" usePreloader="false"> 方法二: package com.haso.flex4SH.component { import flash.display.Sprite; import flash.events.Event; import flash.events.ProgressEvent; import mx.events.FlexEvent; import mx.preloaders.DownloadProgressBar; public class pbarunsee extends DownloadProgressBar { public function pbarunsee() { super(); } override public function set preloader( preloader:Sprite ):void { preloader.addEventListener( ProgressEvent.PROGRESS , SWFDownloadProgress ); preloader.addEventListener( Event.COMPLETE , SWFDownloadComplete ); preloader.addEventListener( FlexEvent.INIT_PROGRESS , FlexInitProgress ); preloader.addEventListener( FlexEvent.INIT_COMPLETE , FlexInitComplete ); } private function SWFDownloadProgress( event:ProgressEvent ):void { this.visible=false; } private function SWFDownloadComplete( event:Event ):void {this.visible=false;} private function FlexInitProgress( event:Event ):void { this.visible=false; } private function FlexInitComplete( event:Event ):void { dispatchEvent(new Event(Event.COMPLETE)); } } } <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600" preloader="com.haso.flex4SH.component.pbaru

优秀的个人博客,低调大师

支持上拉加载更多

先讲下原理: ScrollView的子View 主要分为3部分:head头部,滚动内容,fooder底部 我们实现惯性滑动,以及回弹,都是靠超过head或者fooder 就重新滚动到 ,内容的顶部或者底部。 之前看了Pulltorefresh 他是通过不断改变 head或者 fooder的 pading 值来实现 上拉或者 下拉的效果。感觉有点不流畅,而且层次嵌套得比较多。当然他的好处是扩展性好。 因工作需求,需要层次嵌套少,对性能要求非常高。因此重新自定义了ViewGroup实现。 直接上代码: [java] view plain copy packagecom.example.administrator.customscrollview; importandroid.content.Context; importandroid.content.res.TypedArray; importandroid.util.AttributeSet; importandroid.util.Log; importandroid.view.Gravity; importandroid.view.MotionEvent; importandroid.view.VelocityTracker; importandroid.view.View; importandroid.view.ViewConfiguration; importandroid.view.ViewGroup; importandroid.widget.OverScroller; /** *自定义pulltorefreshLayout *TODO:ferris2015年9月11日18:52:40 */ publicclassPullTorefreshScrollViewextendsViewGroup{ privateFoodeLayoutfooder_layout;//topandbuttom privateViewtop_layout; privateintdesireWidth,desireHeight; privateVelocityTrackervelocityTracker; privateintmPointerId; privatefloatx,y; privateOverScrollermScroller; privateintmaxFlingVelocity,minFlingVelocity; privateintmTouchSlop; protectedBooleanisMove=false; protectedfloatdownX=0,downY=0; privateinttop_hight=0; privateintscrollYButtom=0; privateintnScrollYButtom=0; privateintpullDownMin=0; privateBooleanisEnablePullDown=true; privateBooleanisFirst=true; publicvoidsetEnablePullDown(BooleanisEnablePullDown){ this.isEnablePullDown=isEnablePullDown; } publicPullTorefreshScrollView(Contextcontext){ super(context); init(null,0); } publicPullTorefreshScrollView(Contextcontext,AttributeSetattrs){ super(context,attrs); init(attrs,0); } publicPullTorefreshScrollView(Contextcontext,AttributeSetattrs,intdefStyle){ super(context,attrs,defStyle); init(attrs,defStyle); } privatevoidinit(AttributeSetattrs,intdefStyle){ //Loadattributes //finalTypedArraya=getContext().obtainStyledAttributes( //attrs,R.styleable.PullTorefreshScrollView,defStyle,0); // // //a.recycle(); mScroller=newOverScroller(getContext()); maxFlingVelocity=ViewConfiguration.get(getContext()).getScaledMaximumFlingVelocity(); minFlingVelocity=ViewConfiguration.get(getContext()).getScaledMinimumFlingVelocity(); mTouchSlop=ViewConfiguration.get(getContext()).getScaledTouchSlop(); } @Override protectedvoidonFinishInflate(){ super.onFinishInflate(); fooder_layout=(FoodeLayout)findViewById(R.id.fooder_layout); top_layout=findViewById(R.id.top_layout); if(isEnablePullDown){ fooder_layout.showFooderPull(); }else{ fooder_layout.hideFooder(); } } publicintgetScrollYTop(){ returntop_hight; } publicintgetScrollYButtom(){ returnscrollYButtom; } publicintgetNScrollYTop(){ return0; } publicintgetNScrollYButtom(){ returnnScrollYButtom; } publicintmeasureWidth(intwidthMeasureSpec){ intresult=0; intmeasureMode=MeasureSpec.getMode(widthMeasureSpec); intwidth=MeasureSpec.getSize(widthMeasureSpec); switch(measureMode){ caseMeasureSpec.AT_MOST: caseMeasureSpec.EXACTLY: result=width; break; default: break; } returnresult; } publicintmeasureHeight(intheightMeasureSpec){ intresult=0; intmeasureMode=MeasureSpec.getMode(heightMeasureSpec); intheight=MeasureSpec.getSize(heightMeasureSpec); switch(measureMode){ caseMeasureSpec.AT_MOST: caseMeasureSpec.EXACTLY: result=height; break; default: break; } returnresult; } @Override protectedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec){ //计算所有childview要占用的空间 intwidth=measureWidth(widthMeasureSpec); intheight=measureHeight(heightMeasureSpec); desireWidth=0; desireHeight=0; intcount=getChildCount(); for(inti=0;i<count;++i){ Viewv=getChildAt(i); if(v.getVisibility()!=View.GONE){ LayoutParamslp=(LayoutParams)v.getLayoutParams(); measureChildWithMargins(v,widthMeasureSpec,0, heightMeasureSpec,0); //只是在这里增加了垂直或者水平方向的判断 if(v.getId()==R.id.top_layout){ top_hight=v.getMeasuredHeight(); } desireWidth=Math.max(desireWidth,v.getMeasuredWidth() +lp.leftMargin+lp.rightMargin); desireHeight+=v.getMeasuredHeight()+lp.topMargin +lp.bottomMargin; } } //countwithpadding desireWidth+=getPaddingLeft()+getPaddingRight(); desireHeight+=getPaddingTop()+getPaddingBottom(); //seeifthesizeisbigenough desireWidth=Math.max(desireWidth,getSuggestedMinimumWidth()); desireHeight=Math.max(desireHeight,getSuggestedMinimumHeight()); //处理内容比较少的时候,就添加一定的高度 intscrollHight=height+top_hight*2; if(scrollHight>desireWidth){ intoffset=scrollHight-desireHeight; Viewview=newView(getContext()); view.setBackgroundResource(R.color.top_layout_color); LayoutParamslp=newLayoutParams(LayoutParams.MATCH_PARENT,offset); addView(view,getChildCount()-1,lp); desireWidth=scrollHight; } setMeasuredDimension(resolveSize(desireWidth,widthMeasureSpec), resolveSize(desireHeight,heightMeasureSpec)); scrollYButtom=desireHeight-getMeasuredHeight()-top_hight; nScrollYButtom=desireHeight-getMeasuredHeight(); //如果上啦拖出一半的高度,就代表将要执行上啦 pullDownMin=nScrollYButtom-top_hight/2; if(isFirst){ scrollTo(0,top_hight); isFirst=false; } } @Override protectedvoidonLayout(booleanchanged,intl,intt,intr,intb){ finalintparentLeft=getPaddingLeft(); finalintparentRight=r-l-getPaddingRight(); finalintparentTop=getPaddingTop(); finalintparentBottom=b-t-getPaddingBottom(); if(BuildConfig.DEBUG) Log.d("onlayout","parentleft:"+parentLeft+"parenttop:" +parentTop+"parentright:"+parentRight +"parentbottom:"+parentBottom); intleft=parentLeft; inttop=parentTop; intcount=getChildCount(); for(inti=0;i<count;++i){ Viewv=getChildAt(i); if(v.getVisibility()!=View.GONE){ LayoutParamslp=(LayoutParams)v.getLayoutParams(); finalintchildWidth=v.getMeasuredWidth(); finalintchildHeight=v.getMeasuredHeight(); finalintgravity=lp.gravity; finalinthorizontalGravity=gravity &Gravity.HORIZONTAL_GRAVITY_MASK; finalintverticalGravity=gravity &Gravity.VERTICAL_GRAVITY_MASK; //layoutvertical,andonlyconsiderhorizontalgravity left=parentLeft; top+=lp.topMargin; switch(horizontalGravity){ caseGravity.LEFT: break; caseGravity.CENTER_HORIZONTAL: left=parentLeft +(parentRight-parentLeft-childWidth)/2 +lp.leftMargin-lp.rightMargin; break; caseGravity.RIGHT: left=parentRight-childWidth-lp.rightMargin; break; } v.layout(left,top,left+childWidth,top+childHeight); top+=childHeight+lp.bottomMargin; } } } @Override protectedandroid.view.ViewGroup.LayoutParamsgenerateDefaultLayoutParams(){ returnnewLayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); } @Override publicandroid.view.ViewGroup.LayoutParamsgenerateLayoutParams( AttributeSetattrs){ returnnewLayoutParams(getContext(),attrs); } @Override protectedandroid.view.ViewGroup.LayoutParamsgenerateLayoutParams( android.view.ViewGroup.LayoutParamsp){ returnnewLayoutParams(p); } publicstaticclassLayoutParamsextendsMarginLayoutParams{ publicintgravity=-1; publicLayoutParams(Contextc,AttributeSetattrs){ super(c,attrs); TypedArrayta=c.obtainStyledAttributes(attrs, R.styleable.SlideGroup); gravity=ta.getInt(R.styleable.SlideGroup_layout_gravity,-1); ta.recycle(); } publicLayoutParams(intwidth,intheight){ this(width,height,-1); } publicLayoutParams(intwidth,intheight,intgravity){ super(width,height); this.gravity=gravity; } publicLayoutParams(android.view.ViewGroup.LayoutParamssource){ super(source); } publicLayoutParams(MarginLayoutParamssource){ super(source); } } /** *onInterceptTouchEvent()用来询问是否要拦截处理。onTouchEvent()是用来进行处理。 *<p/> *例如:parentLayout----childLayout----childView事件的分发流程: *parentLayout::onInterceptTouchEvent()---false?---> *childLayout::onInterceptTouchEvent()---false?---> *childView::onTouchEvent()---false?---> *childLayout::onTouchEvent()---false?--->parentLayout::onTouchEvent() *<p/> *<p/> *<p/> *如果onInterceptTouchEvent()返回false,且分发的子View的onTouchEvent()中返回true, *那么onInterceptTouchEvent()将收到所有的后续事件。 *<p/> *如果onInterceptTouchEvent()返回true,原本的target将收到ACTION_CANCEL,该事件 *将会发送给我们自己的onTouchEvent()。 */ @Override publicbooleanonInterceptTouchEvent(MotionEventev){ finalintaction=ev.getActionMasked(); if(BuildConfig.DEBUG) Log.d("onInterceptTouchEvent","action:"+action); if(action==MotionEvent.ACTION_DOWN&&ev.getEdgeFlags()!=0){ //该事件可能不是我们的 returnfalse; } booleanisIntercept=false; switch(action){ caseMotionEvent.ACTION_DOWN: //如果动画还未结束,则将此事件交给onTouchEvet()处理, //否则,先分发给子View isIntercept=!mScroller.isFinished(); //如果此时不拦截ACTION_DOWN时间,应该记录下触摸地址及手指id,当我们决定拦截ACTION_MOVE的event时, //将会需要这些初始信息(因为我们的onTouchEvent将可能接收不到ACTION_DOWN事件) mPointerId=ev.getPointerId(0); //if(!isIntercept){ downX=x=ev.getX(); downY=y=ev.getY(); //} break; caseMotionEvent.ACTION_MOVE: intpointerIndex=ev.findPointerIndex(mPointerId); if(BuildConfig.DEBUG) Log.d("onInterceptTouchEvent","pointerIndex:"+pointerIndex +",pointerId:"+mPointerId); floatmx=ev.getX(pointerIndex); floatmy=ev.getY(pointerIndex); if(BuildConfig.DEBUG) Log.d("onInterceptTouchEvent","action_move[touchSlop:" +mTouchSlop+",deltaX:"+(x-mx)+",deltaY:" +(y-my)+"]"); //根据方向进行拦截,(其实这样,如果我们的方向是水平的,里面有一个ScrollView,那么我们是支持嵌套的) if(Math.abs(y-my)>=mTouchSlop){ isIntercept=true; } //如果不拦截的话,我们不会更新位置,这样可以通过累积小的移动距离来判断是否达到可以认为是Move的阈值。 //这里当产生拦截的话,会更新位置(这样相当于损失了mTouchSlop的移动距离,如果不更新,可能会有一点点跳的感觉) if(isIntercept){ x=mx; y=my; } break; caseMotionEvent.ACTION_CANCEL: caseMotionEvent.ACTION_UP: //这是触摸的最后一个事件,无论如何都不会拦截 if(velocityTracker!=null){ velocityTracker.recycle(); velocityTracker=null; } break; caseMotionEvent.ACTION_POINTER_UP: solvePointerUp(ev); break; } returnisIntercept; } privatevoidsolvePointerUp(MotionEventevent){ //获取离开屏幕的手指的索引 intpointerIndexLeave=event.getActionIndex(); intpointerIdLeave=event.getPointerId(pointerIndexLeave); if(mPointerId==pointerIdLeave){ //离开屏幕的正是目前的有效手指,此处需要重新调整,并且需要重置VelocityTracker intreIndex=pointerIndexLeave==0?1:0; mPointerId=event.getPointerId(reIndex); //调整触摸位置,防止出现跳动 x=event.getX(reIndex); y=event.getY(reIndex); if(velocityTracker!=null) velocityTracker.clear(); } } @Override publicbooleanonTouchEvent(MotionEventevent){ finalintaction=event.getActionMasked(); if(velocityTracker==null){ velocityTracker=VelocityTracker.obtain(); } velocityTracker.addMovement(event); switch(action){ caseMotionEvent.ACTION_DOWN: //获取索引为0的手指id isMove=false; mPointerId=event.getPointerId(0); x=event.getX(); y=event.getY(); if(!mScroller.isFinished()) mScroller.abortAnimation(); break; caseMotionEvent.ACTION_MOVE: isMove=true; //获取当前手指id所对应的索引,虽然在ACTION_DOWN的时候,我们默认选取索引为0 //的手指,但当有第二个手指触摸,并且先前有效的手指up之后,我们会调整有效手指 //屏幕上可能有多个手指,我们需要保证使用的是同一个手指的移动轨迹, //因此此处不能使用event.getActionIndex()来获得索引 finalintpointerIndex=event.findPointerIndex(mPointerId); floatmx=event.getX(pointerIndex); floatmy=event.getY(pointerIndex); moveBy((int)(x-mx),(int)(y-my)); x=mx; y=my; break; caseMotionEvent.ACTION_UP: isMove=false; velocityTracker.computeCurrentVelocity(1000,maxFlingVelocity); floatvelocityX=velocityTracker.getXVelocity(mPointerId); floatvelocityY=velocityTracker.getYVelocity(mPointerId); completeMove(-velocityX,-velocityY); if(velocityTracker!=null){ velocityTracker.recycle(); velocityTracker=null; } break; caseMotionEvent.ACTION_POINTER_UP: //获取离开屏幕的手指的索引 isMove=false; intpointerIndexLeave=event.getActionIndex(); intpointerIdLeave=event.getPointerId(pointerIndexLeave); if(mPointerId==pointerIdLeave){ //离开屏幕的正是目前的有效手指,此处需要重新调整,并且需要重置VelocityTracker intreIndex=pointerIndexLeave==0?1:0; mPointerId=event.getPointerId(reIndex); //调整触摸位置,防止出现跳动 x=event.getX(reIndex); y=event.getY(reIndex); if(velocityTracker!=null) velocityTracker.clear(); } break; caseMotionEvent.ACTION_CANCEL: isMove=false; break; } returntrue; } privateBooleanisPull=false; //此处的moveBy是根据水平或是垂直排放的方向, //来选择是水平移动还是垂直移动 publicvoidmoveBy(intdeltaX,intdeltaY){ if(BuildConfig.DEBUG) Log.d("moveBy","deltaX:"+deltaX+"deltaY:"+deltaY); if(Math.abs(deltaY)>=Math.abs(deltaX)){ intmScrollY=getScrollY(); if(mScrollY<=0){ scrollTo(0,0); }elseif(mScrollY>=getNScrollYButtom()){ scrollTo(0,getNScrollYButtom()); }else{ scrollBy(0,deltaY); } if(isEnablePullDown&&deltaY>0&&mScrollY>=pullDownMin){ isPull=true; Log.d("onlayout","isPull:true"); } } } privatevoidcompleteMove(floatvelocityX,floatvelocityY){ intmScrollY=getScrollY(); intmaxY=getScrollYButtom(); intminY=getScrollYTop(); if(mScrollY>=maxY){//如果滚动,超过了下边界,就回弹到下边界 if(isPull){//滚动到最底部 mScroller.startScroll(0,mScrollY,0,getNScrollYButtom()-mScrollY,300); invalidate(); //显示雷达 fooder_layout.showFooderRadar(); if(pullDownListem!=null){ pullDownListem.onPullDown(); } Log.d("onlayout","isPull:true滚动到最底部,显示出雷达"); }else{ mScroller.startScroll(0,mScrollY,0,maxY-mScrollY); invalidate(); Log.d("onlayout","isPull:true"); } }elseif(mScrollY<=minY){//如果滚动,超过了上边界,就回弹到上边界 //超出了上边界,弹回 mScroller.startScroll(0,mScrollY,0,minY-mScrollY); invalidate(); }elseif(Math.abs(velocityY)>=minFlingVelocity&&maxY>0){//大于1页的时候 //mScroller.fling(0,mScrollY,0,(int)(velocityY*1.2f),0,0,minY,maxY); mScroller.fling(0,mScrollY,0,(int)(velocityY*2f),0,0,getNScrollYTop(),getNScrollYButtom()); invalidate(); } } publicvoidifNeedScrollBack(){ intmScrollY=getScrollY(); intmaxY=getScrollYButtom(); intminY=getScrollYTop(); if(mScrollY>maxY){ //超出了下边界,弹回 mScroller.startScroll(0,mScrollY,0,maxY-mScrollY); invalidate(); }elseif(mScrollY<minY){ //超出了上边界,弹回 mScroller.startScroll(0,mScrollY,0,minY-mScrollY); invalidate(); } } @Override protectedvoidonScrollChanged(intl,intt,intoldl,intoldt){ super.onScrollChanged(l,t,oldl,oldt); } @Override publicvoidcomputeScroll(){ if(mScroller.computeScrollOffset()){ scrollTo(0,mScroller.getCurrY()); postInvalidate(); }else{ Log.d("onlayout","computeScroll,isMove:"+isMove+",isPull:"+isPull); if(!isMove&&!isPull){ ifNeedScrollBack(); } } } publicvoidonPullSuccess(){ soomToBack(); } publicvoidsoomToBack(){ intmScrollY=getScrollY(); intmaxY=getScrollYButtom(); Log.d("onlayout","soomToBack:(maxY-mScrollY)="+(maxY-mScrollY)+",maxY="+maxY+",mScrollY="+mScrollY); //超出了下边界,弹回 mScroller.startScroll(0,mScrollY,0,maxY-mScrollY,300); invalidate(); postDelayed(newRunnable(){ @Override publicvoidrun(){ fooder_layout.showFooderPull(); isPull=false; } },310); } privatePullDownListempullDownListem; publicvoidsetPullDownListem(PullDownListempullDownListem){ this.pullDownListem=pullDownListem; } publicinterfacePullDownListem{ publicvoidonPullDown(); } } 本文转自莫水千流博客园博客,原文链接:http://www.cnblogs.com/zhoug2020/p/6076194.html,如需转载请自行联系原作者

优秀的个人博客,低调大师

android build system中product的继承(inherit-product),加载(import-products)和选...

一、前言 android源码中有很多product,进行配置时,会将源码中所有product的信息都读进来(不用的product的信息也会被读进来),其中每个product,可以包含如下信息 # # Functions for including product makefiles # _product_var_list := \ PRODUCT_NAME \ PRODUCT_MODEL \ PRODUCT_LOCALES \ PRODUCT_AAPT_CONFIG \ PRODUCT_AAPT_PREF_CONFIG \ PRODUCT_AAPT_PREBUILT_DPI \ PRODUCT_PACKAGES \ PRODUCT_PACKAGES_DEBUG \ PRODUCT_PACKAGES_ENG \ PRODUCT_PACKAGES_TESTS \ PRODUCT_DEVICE \ PRODUCT_MANUFACTURER \ PRODUCT_BRAND \ PRODUCT_PROPERTY_OVERRIDES \ PRODUCT_DEFAULT_PROPERTY_OVERRIDES \ PRODUCT_CHARACTERISTICS \ PRODUCT_COPY_FILES \ PRODUCT_OTA_PUBLIC_KEYS \ PRODUCT_EXTRA_RECOVERY_KEYS \ PRODUCT_PACKAGE_OVERLAYS \ DEVICE_PACKAGE_OVERLAYS \ PRODUCT_SDK_ATREE_FILES \ PRODUCT_SDK_ADDON_NAME \ PRODUCT_SDK_ADDON_COPY_FILES \ PRODUCT_SDK_ADDON_COPY_MODULES \ PRODUCT_SDK_ADDON_DOC_MODULES \ PRODUCT_SDK_ADDON_SYS_IMG_SOURCE_PROP \ PRODUCT_DEFAULT_WIFI_CHANNELS \ PRODUCT_DEFAULT_DEV_CERTIFICATE \ PRODUCT_RESTRICT_VENDOR_FILES \ PRODUCT_VENDOR_KERNEL_HEADERS \ PRODUCT_BOOT_JARS \ PRODUCT_SUPPORTS_BOOT_SIGNER \ PRODUCT_SUPPORTS_VBOOT \ PRODUCT_SUPPORTS_VERITY \ PRODUCT_SUPPORTS_VERITY_FEC \ PRODUCT_OEM_PROPERTIES \ PRODUCT_SYSTEM_PROPERTY_BLACKLIST \ PRODUCT_SYSTEM_SERVER_JARS \ PRODUCT_VBOOT_SIGNING_KEY \ PRODUCT_VBOOT_SIGNING_SUBKEY \ PRODUCT_VERITY_SIGNING_KEY \ PRODUCT_SYSTEM_VERITY_PARTITION \ PRODUCT_VENDOR_VERITY_PARTITION \ PRODUCT_DEX_PREOPT_MODULE_CONFIGS \ PRODUCT_DEX_PREOPT_DEFAULT_FLAGS \ PRODUCT_DEX_PREOPT_BOOT_FLAGS \ PRODUCT_SANITIZER_MODULE_CONFIGS \ PRODUCT_SYSTEM_BASE_FS_PATH \ PRODUCT_VENDOR_BASE_FS_PATH \ PRODUCT_SHIPPING_API_LEVEL \ lunch时,比如选择了aosp_arm-eng这个product,那么build system能找到对应的Makefile为build/target/product/aosp_arm.mkaosp_arm.mk(后面将介绍如何找),从而可以获得aosp_arm-eng产品对应的PRODUCT_PACKAGES、PRODUCT_PROPERTY_OVERRIDES等信息,从而可以进行编译 不同的product,有一些公用的信息,如何复用这些公用的信息,是一个问题 比较直观的做法是使用include,但是会带来一些问题: 1、假如A.mk中定义PRODUCT_PACKAGES := liba.so;B.mk中定义PRODUCT_PACKAGES := libb.so。C.mk想同时编译A.mk和B.mk中指定的模块,在C.mk中先include A,再include B,得到的是PRODUCT_PACKAGES := libb.so,但是我们想要的是PRODUCT_PACKAGES := liba.so libb.so 2、重复include同一个文件的问题 所以android使用了inherit-product的方式,去处理这个问题 二、inherit-product 打开build/target/product/aosp_arm.mk可以看到: include $(SRC_TARGET_DIR)/product/full.mk PRODUCT_NAME := aosp_arm build/target/product/full.mk中为: $(call inherit-product, $(SRC_TARGET_DIR)/product/aosp_base_telephony.mk) $(call inherit-product, $(SRC_TARGET_DIR)/board/generic/device.mk) include $(SRC_TARGET_DIR)/product/emulator.mk # Overrides PRODUCT_NAME := full PRODUCT_DEVICE := generic PRODUCT_BRAND := Android PRODUCT_MODEL := AOSP on ARM Emulator aosp_base_telephony.mk和device.mk包含了一些比较通用的信息 inherit-product的定义为: # # $(1): product to inherit # # Does three things: # 1. Inherits all of the variables from $1. # 2. Records the inheritance in the .INHERITS_FROM variable # 3. Records that we've visited this node, in ALL_PRODUCTS # define inherit-product $(if $(findstring ../,$(1)),\ $(eval np := $(call normalize-paths,$(1))),\ $(eval np := $(strip $(1))))\ $(foreach v,$(_product_var_list), \ $(eval $(v) := $($(v)) $(INHERIT_TAG)$(np))) \ $(eval inherit_var := \ PRODUCTS.$(strip $(word 1,$(_include_stack))).INHERITS_FROM) \ $(eval $(inherit_var) := $(sort $($(inherit_var)) $(np))) \ $(eval inherit_var:=) \ $(eval ALL_PRODUCTS := $(sort $(ALL_PRODUCTS) $(word 1,$(_include_stack)))) endef 这几行是用于处理路径问题的,不需要关心 $(if $(findstring ../,$(1)),\ $(eval np := $(call normalize-paths,$(1))),\ $(eval np := $(strip $(1))))\ 对于product的每一个信息,记录下信息需要从哪些文件继承 $(foreach v,$(_product_var_list), \ $(eval $(v) := $($(v)) $(INHERIT_TAG)$(np))) \ 经过上述for循环的处理后,Makefile中包含了这样的代码 PRODUCT_PACKAGES :=@inherit:<filename1.mk> @inherit:<filename2.mk> @inherit:<filename3.mk> @inherit:<filename4.mk> PRODUCT_PROPERTY_OVERRIDES:=@inherit:<filename1.mk> @inherit:<filename2.mk> @inherit:<filename3.mk> @inherit:<filename4.mk> ...... 这里的@inherit只是一个标记,目前还没有起到什么作用。所以inherit-product命令是用来打一些标记,记录继承关系的,在这里并不会执行被继承的.mk文件 这些@inherit的标记是在import-products中进行处理的 后面的几行是定义了一些变量,用于记录一些信息,可以在调试函数中打印出来 三、import-products 我们需要找到android源码中所有product所对应的Makefile,而product对应的Makefile是记录在AndroidProducts.mk文件中的,比如build/target/product/AndroidProducts.mk: PRODUCT_MAKEFILES := \ $(LOCAL_DIR)/aosp_arm.mk \ $(LOCAL_DIR)/full.mk \ $(LOCAL_DIR)/generic_armv5.mk \ $(LOCAL_DIR)/emulator_phone.mk \ $(LOCAL_DIR)/full_x86.mk \ $(LOCAL_DIR)/aosp_mips.mk \ $(LOCAL_DIR)/full_mips.mk \ $(LOCAL_DIR)/aosp_arm64.mk \ $(LOCAL_DIR)/aosp_mips64.mk \ $(LOCAL_DIR)/aosp_x86_64.mk 所以,我们需要先找到android源码中所有的AndroidProducts.mk,调用_find-android-products-files函数,即可在device(推荐位置),vendor(旧的位置),product(旧的位置)目录下搜索AndroidProducts.mk,再加上一个默认的$(SRC_TARGET_DIR)/product/AndroidProducts.mk # # Functions for including AndroidProducts.mk files # PRODUCT_MAKEFILES is set up in AndroidProducts.mks. # Format of PRODUCT_MAKEFILES: # <product_name>:<path_to_the_product_makefile> # If the <product_name> is the same as the base file name (without dir # and the .mk suffix) of the product makefile, "<product_name>:" can be # omitted. # Search for AndroidProducts.mks in the given dir. # $(1): the path to the dir define _search-android-products-files-in-dir $(sort $(shell test -d $(1) && find -L $(1) \ -maxdepth 6 \ -name .git -prune \ -o -name AndroidProducts.mk -print)) endef # # Returns the list of all AndroidProducts.mk files. # $(call ) isn't necessary. # define _find-android-products-files $(foreach d, device vendor product,$(call _search-android-products-files-in-dir,$(d))) \ $(SRC_TARGET_DIR)/product/AndroidProducts.mk endef 找到所有的AndroidProducts.mk之后,调用get-all-product-makefiles函数,可以得到所有的AndroidProducts.mk中定义的PRODUCT_MAKEFILES,也就可以得到所有product的Makefile # # Returns the sorted concatenation of PRODUCT_MAKEFILES # variables set in the given AndroidProducts.mk files. # $(1): the list of AndroidProducts.mk files. # define get-product-makefiles $(sort \ $(foreach f,$(1), \ $(eval PRODUCT_MAKEFILES :=) \ $(eval LOCAL_DIR := $(patsubst %/,%,$(dir $(f)))) \ $(eval include $(f)) \ $(PRODUCT_MAKEFILES) \ ) \ $(eval PRODUCT_MAKEFILES :=) \ $(eval LOCAL_DIR :=) \ ) endef # # Returns the sorted concatenation of all PRODUCT_MAKEFILES # variables set in all AndroidProducts.mk files. # $(call ) isn't necessary. # define get-all-product-makefiles $(call get-product-makefiles,$(_find-android-products-files)) endef 然后调用import-products,导入所有产品的信息 # # $(1): product makefile list # #TODO: check to make sure that products have all the necessary vars defined define import-products $(call import-nodes,PRODUCTS,$(1),$(_product_var_list)) endef import-nodes函数的第一个参数是一个前缀,第二个参数是所有prodcut的Makefile,第三个参数是_product_var_list # # $(1): output list variable name, like "PRODUCTS" or "DEVICES" # $(2): list of makefiles representing nodes to import # $(3): list of node variable names # define import-nodes $(if \ $(foreach _in,$(2), \ $(eval _node_import_context := _nic.$(1).[[$(_in)]]) \ $(if $(_include_stack),$(eval $(error ASSERTION FAILED: _include_stack \ should be empty here: $(_include_stack))),) \ $(eval _include_stack := ) \ $(call _import-nodes-inner,$(_node_import_context),$(_in),$(3)) \ $(call move-var-list,$(_node_import_context).$(_in),$(1).$(_in),$(3)) \ $(eval _node_import_context :=) \ $(eval $(1) := $($(1)) $(_in)) \ $(if $(_include_stack),$(eval $(error ASSERTION FAILED: _include_stack \ should be empty here: $(_include_stack))),) \ ) \ ,) endef if是检查,可以先不看 _node_import_context有点像结构体,和一个product对应。 _node_import_context.<filename>也有点像结构体,描述该产品的某个.mk(product的主Makefile或者inherit的某个.mk)的信息,如 $(_node_import_context). <filename>.PRODUCT_PACKAGES就是某.mk中的PRODUCT_PACKAGES的值 $(_node_import_context).<filename>.inherited记录了<filename>继承了哪些.mk 在_import-node函数中,会真正得去include某个.mk,得到该.mk中PRODUCT_PACKAGES这样的变量的值,并通过copy-var-list,保存为$(_node_import_context).<filename>.PRODUCT_PACKAGES这样的变量。 然后在$(_node_import_context).<filename>.inherited中保存继承关系。 由于继承可以有多层,所以会有递归调用_import-node和_import-nodes-inner。 通过_expand-inherited-values函数,使用真实值替换每一层的@inherit:标记。 最终将得到某个product完整的信息,记录在product的主Makefile对应的$(_node_import_context).<filename>中。 import-nodes函数最后,通过move-var-list函数,将记录在product的主Makefile对应的$(_node_import_context).<filename>中的完整的信息,拷贝为PRODUCTS.<product的主Makefile>.<product var> := <product value>格式的数据。 # # $(1): context prefix # $(2): makefile representing this node # $(3): list of node variable names # # _include_stack contains the list of included files, with the most recent files first. define _import-node $(eval _include_stack := $(2) $$(_include_stack)) $(call clear-var-list, $(3)) $(eval LOCAL_PATH := $(patsubst %/,%,$(dir $(2)))) $(eval MAKEFILE_LIST :=) $(eval include $(2)) $(eval _included := $(filter-out $(2),$(MAKEFILE_LIST))) $(eval MAKEFILE_LIST :=) $(eval LOCAL_PATH :=) $(call copy-var-list, $(1).$(2), $(3)) $(call clear-var-list, $(3)) $(eval $(1).$(2).inherited := \ $(call get-inherited-nodes,$(1).$(2),$(3))) $(call _import-nodes-inner,$(1),$($(1).$(2).inherited),$(3)) $(call _expand-inherited-values,$(1),$(2),$(3)) $(eval $(1).$(2).inherited :=) $(eval _include_stack := $(wordlist 2,9999,$$(_include_stack))) endef # # This will generate a warning for _included above # $(if $(_included), \ # $(eval $(warning product spec file: $(2)))\ # $(foreach _inc,$(_included),$(eval $(warning $(space)$(space)$(space)includes: $(_inc)))),) # # # $(1): context prefix # $(2): list of makefiles representing nodes to import # $(3): list of node variable names # #TODO: Make the "does not exist" message more helpful; # should print out the name of the file trying to include it. define _import-nodes-inner $(foreach _in,$(2), \ $(if $(wildcard $(_in)), \ $(if $($(1).$(_in).seen), \ $(eval ### "skipping already-imported $(_in)") \ , \ $(eval $(1).$(_in).seen := true) \ $(call _import-node,$(1),$(strip $(_in)),$(3)) \ ) \ , \ $(error $(1): "$(_in)" does not exist) \ ) \ ) endef 如何解析之前的PRODUCT_PACKAGES :=@inherit:<filename1.mk> @inherit:<filename2.mk> @inherit:<filename3.mk> @inherit:<filename4.mk>,得到继承关系?由get-inherited-nodes函数实现 INHERIT_TAG := @inherit: # # Walks through the list of variables, each qualified by the prefix, # and finds instances of words beginning with INHERIT_TAG. Scrape # off INHERIT_TAG from each matching word, and return the sorted, # unique set of those words. # # E.g., given # PREFIX.A := A $(INHERIT_TAG)aaa B C # PREFIX.B := B $(INHERIT_TAG)aaa C $(INHERIT_TAG)bbb D E # Then # $(call get-inherited-nodes,PREFIX,A B) # returns # aaa bbb # # $(1): variable prefix # $(2): list of variables to check # define get-inherited-nodes $(sort \ $(subst $(INHERIT_TAG),, \ $(filter $(INHERIT_TAG)%, \ $(foreach v,$(2),$($(1).$(v))) \ ))) endef 基于$(_node_import_context).<filename>.PRODUCT_PACKAGES这样的变量和$(_node_import_context).<filename>.inherited中保存继承关系 PRODUCT_PACKAGES :=@inherit:<filename1.mk> @inherit:<filename2.mk> @inherit:<filename3.mk> @inherit:<filename4.mk>中的@inherit:<filename1.mk>会被filename1.mk中定义的PRODUCT_PACKAGES所替换,@inherit:<filename2.mk>会被filename2.mk中定义的PRODUCT_PACKAGES所替换 递归的每一层处理一次,最终将替换所有的@inherit:标记,得到最终的值,记录在product的主Makefile对应的$(_node_import_context).<filename>中。 # # for each variable ( (prefix + name) * vars ): # get list of inherited words; if not empty: # for each inherit: # replace the first occurrence with (prefix + inherited + var) # clear the source var so we can't inherit the value twice # # $(1): context prefix # $(2): name of this node # $(3): list of variable names # define _expand-inherited-values $(foreach v,$(3), \ $(eval ### "Shorthand for the name of the target variable") \ $(eval _eiv_tv := $(1).$(2).$(v)) \ $(eval ### "Get the list of nodes that this variable inherits") \ $(eval _eiv_i := \ $(sort \ $(patsubst $(INHERIT_TAG)%,%, \ $(filter $(INHERIT_TAG)%, $($(_eiv_tv)) \ )))) \ $(foreach i,$(_eiv_i), \ $(eval ### "Make sure that this inherit appears only once") \ $(eval $(_eiv_tv) := \ $(call uniq-word,$($(_eiv_tv)),$(INHERIT_TAG)$(i))) \ $(eval ### "Expand the inherit tag") \ $(eval $(_eiv_tv) := \ $(strip \ $(patsubst $(INHERIT_TAG)$(i),$($(1).$(i).$(v)), \ $($(_eiv_tv))))) \ $(eval ### "Clear the child so DAGs don't create duplicate entries" ) \ $(eval $(1).$(i).$(v) :=) \ $(eval ### "If we just inherited ourselves, it's a cycle.") \ $(if $(filter $(INHERIT_TAG)$(2),$($(_eiv_tv))), \ $(warning Cycle detected between "$(2)" and "$(i)" for context "$(1)") \ $(error import of "$(2)" failed) \ ) \ ) \ ) \ $(eval _eiv_tv :=) \ $(eval _eiv_i :=) endef 四、lunch 执行source build/envsetup.sh时,会定义lunch函数和add_lunch_combo函数。执行lunch时,可以列出所有的产品,可以进行选择。add_lunch_combo是用来添加新的产品的,否则是无法在lunch时看到的。 在build/envsetup.sh的最后,有如下代码,搜索每一款产品所对应的vendorsetup.sh并执行,vendorsetup.sh中就包含了add_lunch_combo的调用,将本产品添加到lunch菜单中 # Execute the contents of any vendorsetup.sh files we can find. for f in `test -d device && find -L device -maxdepth 4 -name 'vendorsetup.sh' 2> /dev/null | sort` \ `test -d vendor && find -L vendor -maxdepth 4 -name 'vendorsetup.sh' 2> /dev/null | sort` \ `test -d product && find -L product -maxdepth 4 -name 'vendorsetup.sh' 2> /dev/null | sort` do echo "including $f" . $f done 当我们lunch aosp_arm-eng时,lunch函数中会分隔得到TARGET_PRODUCT=aosp_arm,TARGET_BUILD_VARIANT=eng 五、使用product特定的信息 以PRODUCT_PACKAGES为例,如何得知aosp_arm-eng这个产品,需要编译哪些模块呢? build/core/main.mk文件中的product_MODULES是需要编译的所有的模块,通过如下代码赋值 product_MODULES := $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_PACKAGES) INTERNAL_PRODUCT应当是build/target/product/aosp_arm.mk,从而我们可以使用之前得到的PRODUCTS.build/target/product/aosp_arm.mk.PRODUCT_PACKAGES变量 但是如何得知INTERNAL_PRODUCT是build/target/product/aosp_arm.mk呢,通过遍历所以产品的Makefile,找到PRODUCT_NAME和TARGET_PRODUCT相等的那个产品的Makefile即可 INTERNAL_PRODUCT := $(call resolve-short-product-name, $(TARGET_PRODUCT)) # # Returns the product makefile path for the product with the provided name # # $(1): short product name like "generic" # define _resolve-short-product-name $(eval pn := $(strip $(1))) $(eval p := \ $(foreach p,$(PRODUCTS), \ $(if $(filter $(pn),$(PRODUCTS.$(p).PRODUCT_NAME)), \ $(p) \ )) \ ) $(eval p := $(sort $(p))) $(if $(filter 1,$(words $(p))), \ $(p), \ $(if $(filter 0,$(words $(p))), \ $(error No matches for product "$(pn)"), \ $(error Product "$(pn)" ambiguous: matches $(p)) \ ) \ ) endef define resolve-short-product-name $(strip $(call _resolve-short-product-name,$(1))) endef 这样,android build system就完成了product的继承,所有product的载入,以及根据lunch的product,找到需要编译的product的信息 参考: http://baohaojun.github.io/blog/2012/09/17/Android-Product-Makefiles.html

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

用户登录
用户注册