Android UI绘制流程源码详细讲解Draw(Canvas canvas)
前言
在上一篇我们了解了Activity的构成后,接下来我们开始了解一下View的工作流程,就是measure、layout和draw。measure用来测量View的宽高,layout用来确定View的位置,draw则用来绘制View。接下来我们来看看具体绘制的流程以及,paint和Canvas在这中间所扮演的角色。
1.绘制流程
上一篇我们提到了在performTraversals当中一次调用了performMeasure,performLayout,performDraw方法。 接下来我们了解一下draw具体干嘛,那么我们看到ViewRootImpl. performDraw方法看下他是如何完成具体绘制的。
performTraversals
// Remember if we must report the next draw. if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) { mReportNextDraw = true; } boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() ||viewVisibility != View.VISIBLE;if (!cancelDraw && !newSurface) { if (!skipDraw || mReportNextDraw) { if (mPendingTransitions != null && mPendingTransitions.size() > 0) { for (int i = 0; i < mPendingTransitions.size(); ++i) { mPendingTransitions.get(i).startChangingAnimations();} mPendingTransitions.clear();} performDraw();} } else { if (viewVisibility == View.VISIBLE) { // Try again onMeasure会调用两次的原因 scheduleTraversals();} else if (mPendingTransitions != null && mPendingTransitions.size() > 0) { for (int i = 0; i < mPendingTransitions.size(); ++i) { mPendingTransitions.get(i).endChangingAnimations();} mPendingTransitions.clear();} } mIsInTraversal = false; |
接着看performDraw具体做了什么:
private void performDraw() { if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) { return;} final boolean fullRedrawNeeded = mFullRedrawNeeded;mFullRedrawNeeded = false;mIsDrawing = true;Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");try { draw(fullRedrawNeeded);} finally { mIsDrawing = false;Trace.traceEnd(Trace.TRACE_TAG_VIEW);} // For whatever reason we didn't create a HardwareRenderer, end any // hardware animations that are now dangling if (mAttachInfo.mPendingAnimatingRenderNodes != null) { final int count = mAttachInfo.mPendingAnimatingRenderNodes.size();for (int i = 0; i < count; i++) { mAttachInfo.mPendingAnimatingRenderNodes.get(i).endAllAnimators();} mAttachInfo.mPendingAnimatingRenderNodes.clear();} if (mReportNextDraw) { mReportNextDraw = false;if (mAttachInfo.mHardwareRenderer != null) { mAttachInfo.mHardwareRenderer.fence();} if (LOCAL_LOGV) { Log.v(TAG, "FINISHED DRAWING: " + mWindowAttributes.getTitle());} if (mSurfaceHolder != null && mSurface.isValid()) { mSurfaceHolderCallback.surfaceRedrawNeeded(mSurfaceHolder);SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();if (callbacks != null) { for (SurfaceHolder.Callback c : callbacks) { if (c instanceof SurfaceHolder.Callback2) { ((SurfaceHolder.Callback2)c).surfaceRedrawNeeded( mSurfaceHolder);} } } } try { mWindowSession.finishDrawing(mWindow);} catch (RemoteException e) { } } } |
private void draw(boolean fullRedrawNeeded) { Surface surface = mSurface;if (!surface.isValid()) { return;} if (DEBUG_FPS) { trackFPS();} if (!sFirstDrawComplete) { synchronized (sFirstDrawHandlers) { sFirstDrawComplete = true;final int count = sFirstDrawHandlers.size();for (int i = 0; i< count; i++) { mHandler.post(sFirstDrawHandlers.get(i));} } } scrollToRectOrFocus(null, false);if (mAttachInfo.mViewScrollChanged) { mAttachInfo.mViewScrollChanged = false;mAttachInfo.mTreeObserver.dispatchOnScrollChanged();} boolean animating = mScroller != null && mScroller.computeScrollOffset();final int curScrollY;if (animating) { curScrollY = mScroller.getCurrY();} else { curScrollY = mScrollY;} if (mCurScrollY != curScrollY) { mCurScrollY = curScrollY;fullRedrawNeeded = true;if (mView instanceof RootViewSurfaceTaker) { ((RootViewSurfaceTaker) mView).onRootViewScrollYChanged(mCurScrollY);} } final float appScale = mAttachInfo.mApplicationScale;final boolean scalingRequired = mAttachInfo.mScalingRequired;int resizeAlpha = 0;if (mResizeBuffer != null) { long deltaTime = SystemClock.uptimeMillis() - mResizeBufferStartTime;if (deltaTime < mResizeBufferDuration) { float amt = deltaTime/(float) mResizeBufferDuration;amt = mResizeInterpolator.getInterpolation(amt);animating = true;resizeAlpha = 255 - (int)(amt*255);} else { disposeResizeBuffer();} } //获取mDirty,该值表示需要重绘的区域 final Rect dirty = mDirty;if (mSurfaceHolder != null) { // The app owns the surface, we won't draw. dirty.setEmpty();if (animating) { if (mScroller != null) { mScroller.abortAnimation();} disposeResizeBuffer();} return;} //如果fullRedrawNeeded为真,则把dirty区域置为整个屏幕,表示整个视图都需要绘制 //第一次绘制流程,需要绘制所有视图 if (fullRedrawNeeded) { mAttachInfo.mIgnoreDirtyState = true;dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f)); } if (DEBUG_ORIENTATION || DEBUG_DRAW) { Log.v(TAG, "Draw " + mView + "/" + mWindowAttributes.getTitle() + ": dirty={" + dirty.left + "," + dirty.top + "," + dirty.right + "," + dirty.bottom + "} surface=" + surface + " surface.isValid()=" + surface.isValid() + ", appScale:" +appScale + ", width=" + mWidth + ", height=" + mHeight); } mAttachInfo.mTreeObserver.dispatchOnDraw();int xOffset = 0; int yOffset = curScrollY; final WindowManager.LayoutParams params = mWindowAttributes; final Rect surfaceInsets = params != null ? params.surfaceInsets : null; if (surfaceInsets != null) { xOffset -= surfaceInsets.left;yOffset -= surfaceInsets.top;// Offset dirty rect for surface insets. dirty.offset(surfaceInsets.left, surfaceInsets.right); } boolean accessibilityFocusDirty = false; final Drawable drawable = mAttachInfo.mAccessibilityFocusDrawable; if (drawable != null) { final Rect bounds = mAttachInfo.mTmpInvalRect;final boolean hasFocus = getAccessibilityFocusedRect(bounds);if (!hasFocus) { bounds.setEmpty();} if (!bounds.equals(drawable.getBounds())) { accessibilityFocusDirty = true;} } mAttachInfo.mDrawingTime =mChoreographer.getFrameTimeNanos() / TimeUtils.NANOS_PER_MS;if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) { if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) { // If accessibility focus moved, always invalidate the root. boolean invalidateRoot = accessibilityFocusDirty;// Draw with hardware renderer. mIsAnimating = false;if (mHardwareYOffset != yOffset || mHardwareXOffset != xOffset) { mHardwareYOffset = yOffset;mHardwareXOffset = xOffset;invalidateRoot = true;} mResizeAlpha = resizeAlpha;if (invalidateRoot) { mAttachInfo.mHardwareRenderer.invalidateRoot();} dirty.setEmpty();mBlockResizeBuffer = false;mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this);} else { // If we get here with a disabled & requested hardware renderer, something went // wrong (an invalidate posted right before we destroyed the hardware surface // for instance) so we should just bail out. Locking the surface with software // rendering at this point would lock it forever and prevent hardware renderer // from doing its job when it comes back. // Before we request a new frame we must however attempt to reinitiliaze the // hardware renderer if it's in requested state. This would happen after an // eglTerminate() for instance. if (mAttachInfo.mHardwareRenderer != null &&!mAttachInfo.mHardwareRenderer.isEnabled() &&mAttachInfo.mHardwareRenderer.isRequested()) { try { mAttachInfo.mHardwareRenderer.initializeIfNeeded( mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets);} catch (OutOfResourcesException e) { handleOutOfResourcesException(e);return;} mFullRedrawNeeded = true;scheduleTraversals();return;} if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) { return;} } } if (animating) { mFullRedrawNeeded = true;scheduleTraversals(); } |
/** * @return true if drawing was successful, false if an error occurred * 如果绘图成功则为true,如果出现错误,则为false */ private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,boolean scalingRequired, Rect dirty) { // Draw with software renderer. final Canvas canvas;try { final int left = dirty.left;final int top = dirty.top;final int right = dirty.right;final int bottom = dirty.bottom; //锁定canvas区域,由dirty区域决定 canvas = mSurface.lockCanvas(dirty); // The dirty rectangle can be modified by Surface.lockCanvas() //noinspection ConstantConditions if (left != dirty.left || top != dirty.top || right != dirty.right || bottom != dirty.bottom) { attachInfo.mIgnoreDirtyState = true;} // TODO: Do this in native canvas.setDensity(mDensity);} catch (Surface.OutOfResourcesException e) { handleOutOfResourcesException(e);return false;} catch (IllegalArgumentException e) { Log.e(TAG, "Could not lock surface", e);// Don't assume this is due to out of memory, it could be // something else, and if it is something else then we could // kill stuff (or ourself) for no reason. mLayoutRequested = true; // ask wm for a new surface next time. return false;} try { if (DEBUG_ORIENTATION || DEBUG_DRAW) { Log.v(TAG, "Surface " + surface + " drawing to bitmap w=" + canvas.getWidth() + ", h=" + canvas.getHeight());//canvas.drawARGB(255, 255, 0, 0); } // If this bitmap's format includes an alpha channel, we // need to clear it before drawing so that the child will // properly re-composite its drawing on a transparent // background. This automatically respects the clip/dirty region // or // If we are applying an offset, we need to clear the area // where the offset doesn't appear to avoid having garbage // left in the blank areas. if (!canvas.isOpaque() || yoff != 0 || xoff != 0) { canvas.drawColor(0, PorterDuff.Mode.CLEAR);} dirty.setEmpty();mIsAnimating = false;mView.mPrivateFlags |= View.PFLAG_DRAWN;if (DEBUG_DRAW) { Context cxt = mView.getContext();Log.i(TAG, "Drawing: package:" + cxt.getPackageName() +", metrics=" + cxt.getResources().getDisplayMetrics() +", compatibilityInfo=" + cxt.getResources().getCompatibilityInfo());} try { canvas.translate(-xoff, -yoff);if (mTranslator != null) { mTranslator.translateCanvas(canvas);} canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);attachInfo.mSetIgnoreDirtyState = false; //正式开始绘制 mView.draw(canvas); drawAccessibilityFocusedDrawableIfNeeded(canvas);} finally { if (!attachInfo.mSetIgnoreDirtyState) { // Only clear the flag if it was not set during the mView.draw() call attachInfo.mIgnoreDirtyState = false;} } } finally { try { surface.unlockCanvasAndPost(canvas);} catch (IllegalArgumentException e) { Log.e(TAG, "Could not unlock surface", e);mLayoutRequested = true; // ask wm for a new surface next time. //noinspection ReturnInsideFinallyBlock return false;} if (LOCAL_LOGV) { Log.v(TAG, "Surface " + surface + " unlockCanvasAndPost");} } return true; } |
View的绘制
由于ViewGroup没有重写draw方法,因此所有的View都是调用View----->draw方法,因此,我们直接看它的源码:
/** * Manually render this view (and all of its children) to the given Canvas. * The view must have already done a full layout before this function is * called. When implementing a view, implement * {@link #onDraw(android.graphics.Canvas)} instead of overriding this method. * If you do need to override this method, call the superclass version. *@param 画布是视图呈现的画布。 * @param canvas The Canvas to which the View is rendered. * Opaque 不透明物 */ @CallSuper public void draw(Canvas canvas) { final int privateFlags = mPrivateFlags;final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;/* * Draw traversal performs several drawing steps which must be executed * in the appropriate order: * * 1. Draw the background 对View的背景进行绘制 * 2. If necessary, save the canvas' layers to prepare for fading 保存当前的图层信息 * 3. Draw view's content 绘制View的内容 * 4. Draw children 对View的子View进行绘制(如果有子View) * 5. If necessary, draw the fading edges and restore layers 绘制View的褪色的边缘,类似于阴影效果 * 6. Draw decorations (scrollbars for instance) 绘制View的装饰(例如:滚动条) */ // Step 1, draw the background, if needed int saveCount;if (!dirtyOpaque) { drawBackground(canvas);} // skip step 2 & 5 if possible (common case) final int viewFlags = mViewFlags;boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;if (!verticalEdges && !horizontalEdges) { // Step 3, draw the content if (!dirtyOpaque) onDraw(canvas);// Step 4, draw the children dispatchDraw(canvas);// Overlay is part of the content and draws beneath Foreground if (mOverlay != null && !mOverlay.isEmpty()) { mOverlay.getOverlayView().dispatchDraw(canvas);} // Step 6, draw decorations (foreground, scrollbars) onDrawForeground(canvas);// we're done... return;} ................. ................. |
可以看到,draw过程比较复杂,但是逻辑十分清晰,而官方注释也清楚地说明了每一步的做法。我们首先来看一开始的标记位dirtyOpaque,该标记位的作用是判断当前View是否是透明的,如果View是透明的,那么根据下面的逻辑可以看出,将不会执行一些步骤,比如绘制背景、绘制内容等。这样很容易理解,因为一个View既然是透明的,那就没必要绘制它了。接着是绘制流程的六个步骤,接下来讲。
绘制流程的六个步骤:上面红色注释已经标记过
1.绘制背景:
/** * 将背景画在指定的画布上 onto:在什么......之上 * Draws the background onto the specified canvas. * * @param canvas Canvas on which to draw the background */ private void drawBackground(Canvas canvas) { final Drawable background = mBackground;if (background == null) { return;} setBackgroundBounds();// Attempt to use a display list if requested. if (canvas.isHardwareAccelerated() && mAttachInfo != null && mAttachInfo.mHardwareRenderer != null) { mBackgroundRenderNode = getDrawableRenderNode(background, mBackgroundRenderNode);final RenderNode renderNode = mBackgroundRenderNode;if (renderNode != null && renderNode.isValid()) { setBackgroundRenderNodeProperties(renderNode);((DisplayListCanvas) canvas).drawRenderNode(renderNode);return;} } final int scrollX = mScrollX;final int scrollY = mScrollY;if ((scrollX | scrollY) == 0) { background.draw(canvas);} else { canvas.translate(scrollX, scrollY);background.draw(canvas);canvas.translate(-scrollX, -scrollY);} } |
// skip step 2 & 5 if possible (common case) final int viewFlags = mViewFlags; boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0; boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
2.绘制内容:
/** * Implement this to do your drawing. * * @param canvas the canvas on which the background will be drawn */ protected void onDraw(Canvas canvas) { } |
View中该方法是一个空实现,因为不同的View有着不同的内容,这需要我们自己去实现,即在自定义View中重写该方法来实现。
3.绘制子view:
如果当前的View是一个ViewGroup类型,那么就需要绘制它的子View,这里调用了dispatchDraw,而View中该方法是空实现,实际是ViewGroup重写了这个方法,那么我们来看看,ViewGroup------->dispatchDraw源码:
/** * {@inheritDoc} */ @Override protected void dispatchDraw(Canvas canvas) { boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);final int childrenCount = mChildrenCount;final View[] children = mChildren;int flags = mGroupFlags;if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) { final boolean buildCache = !isHardwareAccelerated();for (int i = 0; i < childrenCount; i++) { final View child = children[i];if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) { final LayoutParams params = child.getLayoutParams();attachLayoutAnimationParameters(child, params, i, childrenCount);bindLayoutAnimation(child);} } final LayoutAnimationController controller = mLayoutAnimationController;if (controller.willOverlap()) { mGroupFlags |= FLAG_OPTIMIZE_INVALIDATE;} controller.start();mGroupFlags &= ~FLAG_RUN_ANIMATION;mGroupFlags &= ~FLAG_ANIMATION_DONE;if (mAnimationListener != null) { mAnimationListener.onAnimationStart(controller.getAnimation());} } int clipSaveCount = 0;final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;if (clipToPadding) { clipSaveCount = canvas.save();canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop,mScrollX + mRight - mLeft - mPaddingRight,mScrollY + mBottom - mTop - mPaddingBottom);} // We will draw our child's animation, let's reset the flag mPrivateFlags &= ~PFLAG_DRAW_ANIMATION;mGroupFlags &= ~FLAG_INVALIDATE_REQUIRED;boolean more = false;final long drawingTime = getDrawingTime();if (usingRenderNodeProperties) canvas.insertReorderBarrier();final int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size();int transientIndex = transientCount != 0 ? 0 : -1;// Only use the preordered list if not HW accelerated, since the HW pipeline will do the // draw reordering internally final ArrayList<View> preorderedList = usingRenderNodeProperties? null : buildOrderedChildList();final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled();for (int i = 0; i < childrenCount; i++) { while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) { final View transientChild = mTransientViews.get(transientIndex);if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||transientChild.getAnimation() != null) { more |= drawChild(canvas, transientChild, drawingTime);} transientIndex++;if (transientIndex >= transientCount) { transientIndex = -1;} } int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;final View child = (preorderedList == null) ? children[childIndex] : preorderedList.get(childIndex);if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { more |= drawChild(canvas, child, drawingTime);} } while (transientIndex >= 0) { // there may be additional transient views after the normal views final View transientChild = mTransientViews.get(transientIndex);if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||transientChild.getAnimation() != null) { more |= drawChild(canvas, transientChild, drawingTime);} transientIndex++;if (transientIndex >= transientCount) { break;} } if (preorderedList != null) preorderedList.clear();// Draw any disappearing views that have animations if (mDisappearingChildren != null) { final ArrayList<View> disappearingChildren = mDisappearingChildren;final int disappearingCount = disappearingChildren.size() - 1;// Go backwards -- we may delete as animations finish for (int i = disappearingCount; i >= 0; i--) { final View child = disappearingChildren.get(i);more |= drawChild(canvas, child, drawingTime);} } if (usingRenderNodeProperties) canvas.insertInorderBarrier();if (debugDraw()) { onDebugDraw(canvas);} if (clipToPadding) { canvas.restoreToCount(clipSaveCount);} // mGroupFlags might have been updated by drawChild() flags = mGroupFlags;if ((flags & FLAG_INVALIDATE_REQUIRED) == FLAG_INVALIDATE_REQUIRED) { invalidate(true);} if ((flags & FLAG_ANIMATION_DONE) == 0 && (flags & FLAG_NOTIFY_ANIMATION_LISTENER) == 0 &&mLayoutAnimationController.isDone() && !more) { // We want to erase the drawing cache and notify the listener after the // next frame is drawn because one extra invalidate() is caused by // drawChild() after the animation is over mGroupFlags |= FLAG_NOTIFY_ANIMATION_LISTENER;final Runnable end = new Runnable() { public void run() { notifyAnimationListener();} };post(end);} } |
里面的代码主要遍历了所有子View,每个子View都调用了drawChild这个方法,我们找到这个方法:
/** * Draw one child of this View Group. This method is responsible for getting * the canvas in the right state. This includes clipping, translating so * that the child's scrolled origin is at 0, 0, and applying any animation * transformations. *画一个这个视图组的孩子。这个方法负责 获得画布在正确的状态。这包括剪切,转化 孩子的滚动原点在0 0,应用任何动画 *转换。 * @param canvas The canvas on which to draw the child * @param child Who to draw * @param drawingTime The time at which draw is occurring * @return True if an invalidate() was issued */ protected boolean drawChild(Canvas canvas, View child, long drawingTime) { return child.draw(canvas, this, drawingTime); } |
这里开始调用了子view的draw,
同样开始向下遍历
那么此时,其实同理于我门之前的测量和布局,父亲取得所有子控件开始遍历,调用子控件让子控件自己调用自己的draw开始绘制自己
逻辑很清晰,都是先设定绘制区域,然后利用canvas进行绘制。
4.绘制装饰:
/** * Draw any foreground content for this view. * * <p>Foreground content may consist of scroll bars, a {@link #setForeground foreground} * drawable or other view-specific decorations. The foreground is drawn on top of the * primary view content.</p> * * @param canvas canvas to draw into */ public void onDrawForeground(Canvas canvas) { onDrawScrollIndicators(canvas);onDrawScrollBars(canvas);final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;if (foreground != null) { if (mForegroundInfo.mBoundsChanged) { mForegroundInfo.mBoundsChanged = false;final Rect selfBounds = mForegroundInfo.mSelfBounds;final Rect overlayBounds = mForegroundInfo.mOverlayBounds;if (mForegroundInfo.mInsidePadding) { selfBounds.set(0, 0, getWidth(), getHeight());} else { selfBounds.set(getPaddingLeft(), getPaddingTop(),getWidth() - getPaddingRight(), getHeight() - getPaddingBottom());} final int ld = getLayoutDirection();Gravity.apply(mForegroundInfo.mGravity, foreground.getIntrinsicWidth(),foreground.getIntrinsicHeight(), selfBounds, overlayBounds, ld);foreground.setBounds(overlayBounds);} foreground.draw(canvas);} } 注:所谓的绘制装饰,就是指View除了背景、内容、子View的其余部分,绘制例如滚动条,滚动指示器等 |
那么,到目前为止,View的绘制流程也讲述完毕了
Android UI绘制流程源码详细讲解Draw(Canvas canvas)相关推荐
- Doris FE启动流程源码详细解析
Doris FE启动流程源码详细解析 一.简介 Apache Doris是一个现代化的MPP分析型数据库产品.仅需亚秒级响应时间即可获得查询结果,有效地支持实时数据分析.Apache Doris的分布 ...
- Android之View绘制流程源码分析
版权声明:本文出自汪磊的博客,转载请务必注明出处. 对于稍有自定义View经验的安卓开发者来说,onMeasure,onLayout,onDraw这三个方法都不会陌生,起码多少都有所接触吧. 在安卓中 ...
- Android电话拨打流程源码分析
前面分析了电话拨号界面及电话呼叫界面,由于Android的电话Phone设计的很复杂,因此先从UI层入手分析.想要了解Android的电话拨号UI,请查看Android电话拨号UI分析,电话拨号UI在 ...
- Android 电话博大流程源码分析
前面分析了电话拨号界面及电话呼叫界面,由于Android的电话Phone设计的很复杂,因此先从UI层入手分析.想要了解Android的电话拨号UI,请查看Android电话拨号UI分析,电话拨号UI在 ...
- Android平台类加载流程源码分析
前言 其实大家都知道的Android是使用Java作为开发语言,但是他使用的虚拟机却并不是传统的JVM,在4.4以前Android使用Dalvik虚拟机,之后使用ART(Android Runtime ...
- Android Launcher启动应用程序流程源码解析
带着问题看源码 点击桌面Launcher图标后做了哪些工作? 应用程序什么时候被创建的? Application和MainActivity的onCreate()方法什么时候被调用的? 概述 在Andr ...
- Activity启动流程源码分析(基于Android N)
Activity启动流程源码分析 一个Activity启动分为两种启动方式,一种是从Launcher界面上的图标点击启动,另一种是从一个Activity中设置按钮点击启动另外一个Activity.这里 ...
- android系统加载主题的流程,详解Android布局加载流程源码
一.首先看布局层次 看这么几张图 我们会发现DecorView里面包裹的内容可能会随着不同的情况而变化,但是在Decor之前的层次关系都是固定的.即Activity包裹PhoneWindow,Phon ...
- Android音频框架之二 用户录音启动流程源码走读
前言 此篇是对<Android音频框架之一 详解audioPolicy流程及HAL驱动加载>的延续,此系列博文是记录在Android7.1系统即以后版本实现 内录音功能. 当用户使用 Au ...
- Activity启动流程源码分析-浅析生命周期函数
源码分析 接着上一篇 Activity启动流程源码分析-setContentView源码阅读 的讲解,本节介绍一下Activity的生命周期函数何时被调用 要看Activity的生命周期函数何时被调用 ...
最新文章
- 解决YUM下Loaded plugins: fastestmirror Determining fastest mirrors 的问题
- 查看本地计算机已安装HOTFIX 几种方法
- val什么意思vb中的属性值_老司机带你探索Mysql中int(1)、int(10)、int(11)的区别是什么?...
- sql统计系统时间那一个月数量_关于BE00007图书借阅管理系统bug修复总结
- Oracle案例:一次非常艰难的drop多个PDB的恢复
- spring-retry小结
- Mac技巧,更改Mac桌面图片
- 可视化编辑json数据——json editor
- Word设置标题以及自动编号——保姆级教程
- 计算机科学论文生成器,软件自动生成假论文:满篇废话
- 利用R语言画简单时间序列图
- 修改计算机参数,缺氧参数怎么修改 游戏内参数修改方法解答
- 四轴飞行器的串级PID参数整定经验
- [免费专栏] Android安全之Android Fragment注入
- 嵌入式软件工程师就业方向有哪些呢?
- sqrt()函数详解
- 三生三世十里桃花用计算机怎么弄,三生三世十里桃花ios如何用电脑玩 三生三世十里桃花ios模拟器教程...
- Android实现记事本功能
- 使用QT设计师界面类创建2个界面,通过按键从界面1切换到界面2
- 如何清除谷歌浏览器缓存
热门文章
- 服务器装了无线网卡失败,.NET Core Runtime安装失败0x80070005Error报错服务器原因
- php 0x80070005,FastCGI Error Number: 5 (0x80070005)解决方法
- windows mingw 64,SDL ,devil,glfw,opengl,qt环境搭建
- 孩子给产品经理的一堂课
- 怎么保存html,怎么保存整个网页,教你一个妙招就可以搞定!
- 时间漩涡的世界 (一)
- 搜狗皮肤.php,搜狗皮肤制作
- numpy中的revel和flatten
- java 临时文件_在Java中使用临时文件/文件夹
- python的encode()和decode()的用法及实例