GVKun编程网logo

Java Android 二进制文件读写(java实现二进制文件读写功能)

3

在本文中,我们将为您详细介绍JavaAndroid二进制文件读写的相关知识,并且为您解答关于java实现二进制文件读写功能的疑问,此外,我们还会提供一些关于Android-致命异常:由Percel.j

在本文中,我们将为您详细介绍Java Android 二进制文件读写的相关知识,并且为您解答关于java实现二进制文件读写功能的疑问,此外,我们还会提供一些关于Android - 致命异常:由 Percel.java 上的 java.lang.SecurityException 引起的 java.lang.RuntimeException、Android N 将从专属 Java API 转向 OpenJDK,openjdk 是用 java 开发的吗?还是用 java 来开发 app 吗?、Android Studio,java.lang.ClassCastException:java.lang.String 无法转换为 java.lang.Long、Android View 的绘制流程之 Layout 和 Draw 过程详解 (二) Android View 绘制流程之 DecorView 与 ViewRootImplAndroid View 的绘制流程之 Measure 过程详解 (一)Android View 的绘制流程之 Layout 和 Draw 过程详解 (二)Android View 的事件分发原理解析Android 自定义 View 详解Android View 的绘制流程之 Measure 过程详解 (一)的有用信息。

本文目录一览:

Java Android 二进制文件读写(java实现二进制文件读写功能)

Java Android 二进制文件读写(java实现二进制文件读写功能)

https://blog.csdn.net/u012734708/article/details/88354539

1.读取android工程中本地二进制文件
Android studio工程目录中有二进制文件abcd.raw 。
二进制文件所放目录 app/src/main/assets/abcd.raw

1.1一次性读取二进制文件
private byte[] readLocalFile() throws IOException {
String fileName = "abcd.raw";
InputStream inputStream = getAssets().open(fileName);
byte[] data = toByteArray(inputStream);
inputStream.close();
return data;
}
private byte[] toByteArray(InputStream in) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buffer = new byte[1024 * 4];
int n = 0;
while ((n = in.read(buffer)) != -1) {
out.write(buffer, 0, n);
}
return out.toByteArray();
}

1.2 分段读取二进制文件,一次读取1024个字节
byte[] buffer = new byte[1024];
private void readLocalFile() throws IOException {
String fileName = "abcd.raw";
InputStream inputStream = getAssets().open(fileName);
int n = -1;
while ((n = inputStream.read(buffer,0,1024)) != -1) {
//buffer为读出来的二进制数据,长度1024,最后一段数据小于1024
}
inputStream.close();
}
2.分段读取手机目录中本地二进制文件
手机目录中有二进制文件abcd.raw 。
二进制文件所在手机目录 /sdcard/abcd.raw

private void readLocalFile() {
FileInputStream inputStream = null;
File file = new File("/sdcard/abcd.raw");
try {
inputStream = new FileInputStream(file);
byte buffer[] = new byte[1024];
int len = 0;
while ((len = inputStream.read(buffer,0,buffer.length))>0) {
//buffer为读出来的二进制数据,长度1024,最后一段数据小于1024
}
} catch (IOException e) {
e.printstacktrace();
} finally {
if (inputStream!=null) {
try {
inputStream.close();
} catch (IOException e) {
e.printstacktrace();
}
}
}
}
3.写入手机目录二进制文件
写入到手机目录中有二进制文件/sdcard/aaa.raw 。

FileOutputStream fos = null;
private void openPCMfile(byte[] bytes) {
File f = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator+ "aaa.raw");
if (!f.exists()) {
File parentFile = f.getParentFile();
if (!parentFile.exists()) {
parentFile.mkdirs();
}
try {
f.createNewFile();
} catch (IOException e) {
e.printstacktrace();
}
}
try {
fos = new FileOutputStream(f);
fos.write(bytes, 0, bytes.length);
fos.flush();
fos.close();
} catch (Exception e) {
e.printstacktrace();
}
}

Android - 致命异常:由 Percel.java 上的 java.lang.SecurityException 引起的 java.lang.RuntimeException

Android - 致命异常:由 Percel.java 上的 java.lang.SecurityException 引起的 java.lang.RuntimeException

如何解决Android - 致命异常:由 Percel.java 上的 java.lang.SecurityException 引起的 java.lang.RuntimeException

我收到了很多崩溃报告,如下所示:

Fatal Exception: java.lang.RuntimeException caused by java.lang.SecurityException

堆栈跟踪:

  1. Caused by java.lang.SecurityException: Permission Denial: reading com.android.providers.media.MediaProvider uri content://media/external/file from pid=28628,uid=10263 requires android.permission.READ_EXTERNAL_STORAGE,or grantUriPermission()
  2. at android.os.Parcel.createException(Parcel.java:1966)
  3. at android.os.Parcel.readException(Parcel.java:1934)
  4. at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:183)
  5. at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:135)
  6. at android.content.ContentProviderProxy.query(ContentProviderProxy.java:418)
  7. at android.content.ContentResolver.query(ContentResolver.java:809)
  8. at android.content.ContentResolver.query(ContentResolver.java:759)
  9. at android.content.CursorLoader.loadInBackground(CursorLoader.java:68)
  10. at android.content.CursorLoader.loadInBackground(CursorLoader.java:45)
  11. at android.content.AsyncTaskLoader.onLoadInBackground(AsyncTaskLoader.java:319)
  12. at android.content.AsyncTaskLoader$LoadTask.doInBackground(AsyncTaskLoader.java:73)
  13. at android.content.AsyncTaskLoader$LoadTask.doInBackground(AsyncTaskLoader.java:61)
  14. at android.os.AsyncTask$2.call(AsyncTask.java:333)
  15. at java.util.concurrent.FutureTask.run(FutureTask.java:266)
  16. at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
  17. at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
  18. at java.lang.Thread.run(Thread.java:764)

我已经声明了清单文件的 WRITE_EXTERNAL_STORAGEREAD_EXTERNAL_STORAGE 权限,并在运行时从用户那里获得了权限。我还缺少什么?

注意:任务异步运行。

解决方法

在读取外部存储之前,您需要将 android.permission.READ_EXTERNAL_STORAGE 添加到您的 android 清单文件或请求用户许可 (READ_EXTERNAL_STORAGE)。

Android N 将从专属 Java API 转向 OpenJDK,openjdk 是用 java 开发的吗?还是用 java 来开发 app 吗?

Android N 将从专属 Java API 转向 OpenJDK,openjdk 是用 java 开发的吗?还是用 java 来开发 app 吗?

Android N 将从专属 Java API 转向 OpenJDK,openjdk 是用 java 开发的吗?还是用 java 来开发 app 吗?

Android Studio,java.lang.ClassCastException:java.lang.String 无法转换为 java.lang.Long

Android Studio,java.lang.ClassCastException:java.lang.String 无法转换为 java.lang.Long

如何解决Android Studio,java.lang.ClassCastException:java.lang.String 无法转换为 java.lang.Long

我不断收到此错误消息

java.lang.classCastException: java.lang.String 不能转换为 java.lang.Long

我正在尝试使用共享首选项在活动之间传递一个长值,这是我的第一个活动的代码

  1. SharedPreferences sp = getApplicationContext().getSharedPreferences("userdata",Context.MODE_PRIVATE);
  2. Calendar cal = Calendar.getInstance();
  3. SharedPreferences.Editor editor = sp.edit();
  4. editor.putLong("time"+i,cal.getTimeInMillis());
  5. editor.commit();

这是我的第二个活动的代码。

  1. ArrayList<Long> time = new ArrayList<Long>();
  2. sp = getSharedPreferences("userdata",Context.MODE_PRIVATE);
  3. time.add(sp.getLong("time"+i,0)); // here is where the error occurs

“time”末尾的 +i 的原因是这些 put 和 receive 消息在 for 循环中运行,因为有多个 long 正在传递并且顺序很重要。请告诉我是否还有其他方法可以执行此操作或如何解决此异常。

谢谢

Android View 的绘制流程之 Layout 和 Draw 过程详解 (二) Android View 绘制流程之 DecorView 与 ViewRootImplAndroid View 的绘制流程之 Measure 过程详解 (一)Android View 的绘制流程之 Layout 和 Draw 过程详解 (二)Android View 的事件分发原理解析Android 自定义 View 详解Android View 的绘制流程之 Measure 过程详解 (一)

Android View 的绘制流程之 Layout 和 Draw 过程详解 (二) Android View 绘制流程之 DecorView 与 ViewRootImplAndroid View 的绘制流程之 Measure 过程详解 (一)Android View 的绘制流程之 Layout 和 Draw 过程详解 (二)Android View 的事件分发原理解析Android 自定义 View 详解Android View 的绘制流程之 Measure 过程详解 (一)

View 的绘制系列文章:

  • Android View 绘制流程之 DecorView 与 ViewRootImpl

  • Android View 的绘制流程之 Measure 过程详解 (一)

  • Android View 的绘制流程之 Layout 和 Draw 过程详解 (二)

  • Android View 的事件分发原理解析

  • Android 自定义 View 详解

 

在上一篇 Android View 的绘制流程之 Measure 过程详解 (一),已经详细的分析了 DecorView 和其子 View 的测量过程,接下去就要开始讲  layout 和 draw 流程。下面开始进入分析:

DecorView Layout 阶段

在 ViewRootImpl 中,调用 performlayout 方法来确定 DecorView 在屏幕中的位置,下面看下具体的代码逻辑:

// ViewRootImpl 
private void performlayout(WindowManager.LayoutParams lp,int desiredWindowWidth, desiredWindowHeight) { mLayoutRequested = false; mScrollMayChange = true; mInLayout = ; final View host = mView; if (host == null) { return; } if (DEBUG_ORIENTATION || DEBUG_LAYOUT) { Log.v(mTag,"Laying out " + host + " to (" + host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ")"); } Trace.traceBegin(Trace.TRACE_TAG_VIEW,"layout"); try {
       // 根据测量结果进行绘制 host.layout(
0,0,host.getMeasuredWidth(),host.getMeasuredHeight()); mInLayout = ; int numViewsRequestingLayout = mLayoutRequesters.size(); if (numViewsRequestingLayout > 0) { // requestLayout() was called during layout. If no layout-request flags are set on the requesting views,there is no problem. If some requests are still pending,then we need to clear those flags and do a full request/measure/layout pass to handle this situation. ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,1)">); if (validLayoutRequesters != ) { Set this flag to indicate that any further requests are happening during the second pass,which may result in posting those requests to the next frame instead mHandlingLayoutInLayoutRequest = ; Process fresh layout requests,then measure and layout int numValidRequests = validLayoutRequesters.size(); for (int i = 0; i < numValidRequests; ++i) { final View view = validLayoutRequesters.get(i); Log.w("View","requestLayout() improperly called by " + view + " during layout: running second layout pass"); view.requestLayout(); } measureHierarchy(host,lp,mView.getContext().getResources(),desiredWindowWidth,desiredWindowHeight); mInLayout = true; host.layout(0,host.getMeasuredHeight()); mHandlingLayoutInLayoutRequest = Check the valid requests again,this time without checking/clearing the layout flags,since requests happening during the second pass get noop''d validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,1)">); ) { final ArrayList<View> finalRequesters = validLayoutRequesters; Post second-pass requests to the next frame getRunQueue().post(new Runnable() { @Override public void run() { finalRequesters.size(); i) { finalRequesters.get(i); Log.w("View","requestLayout() improperly called by " + view + " during second layout pass: posting in next frame"); view.requestLayout(); } } }); } } } } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } mInLayout = ; }

当在 layout 绘制过程中,收到了关于重新 layout 的请求,会先判断这些请求里面哪些是有效的,如果是有效的,那么就必须先处理,清除 layout-request flags (View.PFLAG_FORCE_LAYOUT)这些标记,再做一次彻底的重绘工作:重新测量,layout。

那么对于 DecorView 来说,调用 layout 方法,就是对它自身进行布局,注意到传递的参数分别是 0,0, host.getMeasuredWidth,host.getMeasuredHeigh,它们分别代表了一个  View 的上下左右四个位置,显然,DecorView 的左上位置为 0,然后宽高为它的测量宽高,下面来看 layout 的具体代码:

 ViewGroup 
final void layout(int l,1)">int t,1)">int r,1)"> b) {
        if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
            if (mTransition != ) {
                mTransition.layoutChange(this);
            }
            super.layout(l,t,r,b);
        } else {
             record the fact that we noop''d it; request layout when transition finishes
            mLayoutCalledWhileSuppressed = ;
        }
    }

 由于 ViewGroup 的 layout 方法是 final 类型,子类不能重写,这里调用了父类的  View#layout 方法,下面看看该方法是如何操作的:

// View   
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) { onMeasure(mOldWidthMeasureSpec,mOldHeightMeasureSpec); mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; } int oldL = mLeft; int oldT = mTop; int oldB = mBottom; int oldR = mRight;      // 设置界面的显示大小 boolean changed = isLayoutModeOptical(mParent) ? setopticalFrame(l,b) : setFrame(l,b);      // 发生了改变,或者是需要重新 layout,那么就会进入 onLayout 逻辑 if (changed || (mPrivateFlags & PFLAG_LAYOUT_required) == PFLAG_LAYOUT_required) {
// 开始 onLayout(changed,l,b);
if (shouldDrawRoundScrollbar()) { if(mRoundScrollbarRenderer == ) { mRoundScrollbarRenderer = new RoundScrollbarRenderer(); } } { mRoundScrollbarRenderer = ; }        // 清除请求 layout 的标记 mPrivateFlags &= ~PFLAG_LAYOUT_required; ListenerInfo li = mListenerInfo; if (li != null && li.mOnLayoutchangelisteners != ) { ArrayList<OnLayoutchangelistener> listenerscopy = (ArrayList<OnLayoutchangelistener>)li.mOnLayoutchangelisteners.clone(); int numListeners = listenerscopy.size(); int i = 0; i < numListeners; ++i) { listenerscopy.get(i).onLayoutChange(boolean wasLayoutValid = isLayoutValid(); mPrivateFlags &= ~PFLAG_FORCE_LAYOUT; mPrivateFlags3 |= PFLAG3_IS_LAID_OUT; if (!wasLayoutValid && isFocused()) { mPrivateFlags &= ~PFLAG_WANTS_FOCUS; (canTakeFocus()) { We have a robust focus,so parents should no longer be wanting focus. clearParentsWantFocus(); } else if (getViewRootImpl() == getViewRootImpl().isInLayout()) { This is a weird case. Most-likely the user,rather than ViewRootImpl,called layout. In this case,there''s no guarantee that parent layouts will be evaluated and thus the safest action is to clear focus here. clearFocusInternal(null,1)">/* propagate */ true,1)"> refocus ); clearParentsWantFocus(); } if (!hasParentWantsFocus()) { original requestFocus was likely on this view directly,so just clear focus clearFocusInternal( otherwise,we let parents handle re-assigning focus during their layout passes. } if ((mPrivateFlags & PFLAG_WANTS_FOCUS) != 0) { mPrivateFlags &= ~PFLAG_WANTS_FOCUS; View focused = findFocus(); if (focused != Try to restore focus as close as possible to our starting focus. if (!restoreDefaultFocus() && !hasParentWantsFocus()) { Give up and clear focus once we''ve reached the top-most parent which wants focus. focused.clearFocusInternal(); } } } if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) { mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT; notifyEnterOrExitForAutoFillIfNeeded(); } }

 调用了 setFrame 方法,并把四个位置信息传递进去,这个方法用于确定 View 的四个顶点的位置,看下具体的代码:

protected boolean setFrame(int left,1)">int top,1)">int right,1)"> bottom) {
        boolean changed =  (DBG) {
            Log.d(VIEW_LOG_TAG,1)">this + " View.setFrame(" + left + "," + top + ","
                    + right + "," + bottom + ")");
        }
     // 有一个不一样说明发生了改变
        if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
            changed = ;

             Remember our drawn bit
            int drawn = mPrivateFlags & PFLAG_DRAWN;

            int oldWidth = mRight - mLeft;
            int oldHeight = mBottom - mTop;
            int newWidth = right - left;
            int newHeight = bottom - top;
            boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);

             Invalidate our old position
            invalidate(sizeChanged);

            mLeft = left;
            mTop = top;
            mRight = right;
            mBottom = bottom;
            mRenderNode.setLeftTopRightBottom(mLeft,mTop,mRight,mBottom);
       // 做好标记
            mPrivateFlags |= PFLAG_HAS_BOUNDS;

       // size 改变的回调
             (sizeChanged) {
                sizeChange(newWidth,newHeight,oldWidth,oldHeight);
            }

            if ((mViewFlags & VISIBILITY_MASK) == VISIBLE || mGhostView !=  If we are visible,force the DRAWN bit to on so that
                 this invalidate will go through (at least to our parent).
                 This is because someone may have invalidated this view
                 before this call to setFrame came in,thereby clearing
                 the DRAWN bit.
                mPrivateFlags |= PFLAG_DRAWN;
                invalidate(sizeChanged);
                 parent display list may need to be recreated based on a change in the bounds
                 of any child
                invalidateParentCaches();
            }

             Reset drawn bit to original value (invalidate turns it off)
            mPrivateFlags |= drawn;

            mBackgroundSizeChanged = ;
            mDefaultFocusHighlightSizeChanged = if (mForegroundInfo != ) {
                mForegroundInfo.mBoundsChanged = ;
            }

            notifySubtreeAccessibilityStateChangedIfNeeded();
        }
         changed;
    }
这里我们看到它对 mLeft、mTop、mRight、mBottom 这四个值进行了重新赋值,对于每一个View,包括 ViewGroup 来说,以上四个值保存了 Viwe 的位置信息,所以这四个值是最终宽高,也即是说,如果要得到 View 的位置信息,那么就应该在 layout 方法完成后调用 getLeft()、getTop() 等方法来取得最终宽高,如果是在此之前调用相应的方法,只能得到 0 的结果。当初始化完毕后,ViewGroup 的布局流程也就完成了。

赋值后,前后对比大小,如果发生了改变,就会调用了 sizeChange()方法,最终会回调 onSizeChanged,标明 view 的尺寸发生了变化,第一次 laout 的时候也会调用。在自定义view 的时候,可以在这里获取控件的宽和高度。

  void sizeChange(int newWidth,1)">int newHeight,1)">int oldWidth,1)"> oldHeight) {
        onSizeChanged(newWidth,oldHeight);
      ......
}

子 View Layout 流程

当 DecorView 确定好了自己的位置之后,开始调用 onLayout 来确定子 view 的位置。对于 onLayout 方法,View 和 ViewGroup 类是空实现,接下来看 FrameLayout 的实现:

 FrameLayout 
void onLayout(boolean changed,1)"> bottom) {
        layoutChildren(left,top,right,bottom,1)">false  no force left gravity */);
    }

 可以看到,该方法调用了 layoutChildren 来确定子 view 的位置。

 FrameLaout   
void layoutChildren(int bottom,1)">boolean forceLeftGravity) { int count = getChildCount();      // 获取 DecoverView 剩余的空间范围 int parentLeft = getPaddingLeftWithForeground(); int parentRight = right - left - getPaddingRightWithForeground(); int parentTop = getPaddingTopWithForeground(); int parentBottom = bottom - top - getPaddingBottomWithForeground(); int i = 0; i < count; i++final View child = getChildAt(i); if (child.getVisibility() != GONE) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); int width = child.getMeasuredWidth(); int height = child.getMeasuredHeight(); childLeft; childTop;
          // DEFAULT_CHILD_GraviTY = Gravity.TOP | Gravity.START 默认是在左上角
int gravity = lp.gravity;
                if (gravity == -1) {
                    gravity = DEFAULT_CHILD_GraviTY;
                }

                int layoutDirection = getLayoutDirection();
                int absoluteGravity = Gravity.getAbsoluteGravity(gravity,layoutDirection);
                int verticalGravity = gravity & Gravity.VERTICAL_GraviTY_MASK;
          // 确定左边起始点
                switch (absoluteGravity & Gravity.HORIZONTAL_GraviTY_MASK) {
                    case Gravity.CENTER_HORIZONTAL:
                        childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
                        lp.leftMargin - lp.rightMargin;
                        break;
                     Gravity.RIGHT:
                        forceLeftGravity) {
                            childLeft = parentRight - width - lp.rightMargin;
                            ;
                        }
                     Gravity.LEFT:
                    default:
                        childLeft = parentLeft + lp.leftMargin;
                }
          // 确定上边起始点
                switch (verticalGravity) {
                     Gravity.TOP:
                        childTop = parentTop + lp.topMargin;
                         Gravity.CENTER_VERTICAL:
                        childTop = parentTop + (parentBottom - parentTop - height) / 2 +
                        lp.topMargin - lp.bottomMargin;
                         Gravity.BottOM:
                        childTop = parentBottom - height -:
                        childTop = parentTop + lp.topMargin;
                }

                child.layout(childLeft,childTop,childLeft + width,childTop + height);
            }
        }
    }

先梳理一下以上逻辑:

  • 首先先获取父容器的 padding 值,得到 DecorView 的可用于显示的空间范围。

  • 然后遍历其每一个子 View,根据子 View 的 layout_gravity 属性、子 View 的测量宽高、父容器的 padding 值、来确定子 View 的左上角的坐标位置

  • 然后调用 child.layout 方法,参数是左上角坐标和自身宽高结合起来的,这样就可以确定子 View 的位置。

最终调用 layout 是 View#layout,前面已经分析过,就不在分析了。

可以看到子 View 的布局流程也很简单,如果子 View 是一个 ViewGroup,那么就会重复以上步骤,如果是一个 View,那么会直接调用 View#layout 方法,根据以上分析,在该方法内部会设置 view 的四个布局参数,接着调用 onLayout 方法 :

 bottom) {

}

此方法是一个空方法,也就是说需要子类去实现此方法,不同的 View 实现方式不同,这里就不分析了。

layout阶段的基本思想也是由根View开始,递归地完成整个控件树的布局(layout)工作。 

对于宽高的获取这里在总结下:

 

 

 这里需要注意一下,在非一般情况下,也就是通过人为设置,重写View的layout()强行设置,这种情况下,测量的值与最终的值是不一样的。

Layout 整体的流程图

上面分别从 View 和 ViewGroup 的角度讲解了布局流程,这里再以流程图的形式归纳一下整个 Layout 过程,便于加深记忆:

 

DecorView Draw 流程

Draw 的入口也是在 ViewRootImpl 中,执行 ViewRootImpl#performTraversals 中会执行 ViewRootIml#performDraw:

 performDraw() {
...
fullRedrawNeeded,它的作用是判断是否需要重新绘制全部视图
draw(fullRedrawNeeded);
...
}

然后会执行到 ViewRootImpl#draw:

void draw( fullRedrawNeeded) {
 ...
 获取mDirty,该值表示需要重绘的区域
 final Rect dirty = mDirty;
 if (mSurfaceHolder != ) {
   The app owns the surface,we won''t draw.
  dirty.setEmpty();
   (animating) {
   if (mScroller != ) {
    mScroller.abortAnimation();
   }
   disposeResizeBuffer();
  }
  ;
 }

 如果fullRedrawNeeded为真,则把dirty区域置为整个屏幕,表示整个视图都需要绘制
 第一次绘制流程,需要绘制所有视图
  (fullRedrawNeeded) {
  mAttachInfo.mIgnoreDirtyState = ;
  dirty.set(0,(int) (mWidth * appScale + 0.5f),1)">int) (mHeight * appScale + 0.5f));
 }
 ...
 drawSoftware(surface,mAttachInfo,xOffset,yOffset,scalingrequired,dirty)) {
    ;
  }
}

 接着会执行到 ViewRootIml#drawSoftware,然后在 ViewRootIml#drawSoftware 会执行到 mView.draw(canvas)。

boolean drawSoftware(Surface surface,AttachInfo attachInfo,1)">int xoff,1)"> yoff,1)"> scalingrequired,Rect dirty) {
 final Canvas canvas;
  锁定canvas区域,由dirty区域决定
  这个canvas就是我们想在上面绘制东西的画布
  canvas = mSurface.lockCanvas(dirty);
  ...
 画布支持位图的密度,和手机分辨率相关
  canvas.setDensity(mDensity);
 ...
   if (!canvas.isOpaque() || yoff != 0 || xoff != 0) {
                canvas.drawColor(0yoff);
   ...
   正式开始绘制
   mView.draw(canvas);
  ...
 提交需要绘制的东西
  surface.unlockCanvasAndPost(canvas);
}

 mView.draw(canvas) 开始真正的绘制。此处 mView 就是 DecorView,先看 DecorView 中 Draw 的方法:

   draw(Canvas canvas) {
        .draw(canvas);

        if (mMenuBackground != ) {
            mMenuBackground.draw(canvas);
        }
    }

 里面会调用 View#draw:

    int privateFlags = mPrivateFlags;
        boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
                (mAttachInfo == 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
         *      2. If necessary,save the canvas'' layers to prepare for fading
         *      3. Draw view''s content
         *      4. Draw children
         *      5. If necessary,draw the fading edges and restore layers
         *      6. Draw decorations (scrollbars for instance)
         */

         Step 1,draw the background,if needed
         saveCount;

        绘制背景
        dirtyOpaque) {
            drawBackground(canvas);
        }

         如果可以跳过2和5步
        int viewFlags = mViewFlags;
      判断是否有绘制衰退边缘的标示
        boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
        boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
      如果没有绘制衰退边缘只需要3,4,6步
        if (!verticalEdges && !horizontalEdges) {
             Step 3,draw the content
            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...
            ;
        }

        
         * Here we do the full fledged routine...
         * (this is an uncommon case where speed matters less,* this is why we repeat some of the tests that have been
         * done above)
         boolean drawTop = boolean drawBottom = boolean drawLeft = boolean drawRight = float topFadeStrength = 0.0ffloat bottomFadeStrength = 0.0ffloat leftFadeStrength = 0.0ffloat rightFadeStrength = 0.0f Step 2,save the canvas'' layers
        int paddingLeft = mPaddingLeft;

        boolean offsetrequired = isPaddingOffsetrequired();
         (offsetrequired) {
            paddingLeft += getLeftPaddingOffset();
        }

        int left = mScrollX + paddingLeft;
        int right = left + mRight - mLeft - mPaddingRight -int top = mScrollY + getFadetop(offsetrequired);
        int bottom = top + getFadeHeight(offsetrequired);

         (offsetrequired) {
            right += getRightPaddingOffset();
            bottom += getBottomPaddingOffset();
        }

        final ScrollabilityCache scrollabilityCache = mScrollCache;
        float fadeHeight = scrollabilityCache.fadingEdgeLength;
        int length = () fadeHeight;

         clip the fade length if top and bottom fades overlap
         overlapping fades produce odd-looking artifacts
        if (verticalEdges && (top + length > bottom - length)) {
            length = (bottom - top) / 2 also clip horizontal fades if necessary
        if (horizontalEdges && (left + length > right - length)) {
            length = (right - left) / 2 (verticalEdges) {
            topFadeStrength = Math.max(0.0f,Math.min(1.0f;
            bottomFadeStrength = Math.max(0.0f,getBottomFadingEdgeStrength()));
            drawBottom = bottomFadeStrength * fadeHeight > 1.0f (horizontalEdges) {
            leftFadeStrength = Math.max(0.0f,getLeftFadingEdgeStrength()));
            drawLeft = leftFadeStrength * fadeHeight > 1.0f;
            rightFadeStrength = Math.max(0.0f,getRightFadingEdgeStrength()));
            drawRight = rightFadeStrength * fadeHeight > 1.0f;
        }

        saveCount = canvas.getSaveCount();

        int solidColor = getSolidColor();
        if (solidColor == 0int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;

             (drawTop) {
                canvas.saveLayer(left,top + length,flags);
            }

             (drawBottom) {
                canvas.saveLayer(left,bottom - length,1)"> (drawLeft) {
                canvas.saveLayer(left,left + length,1)"> (drawRight) {
                canvas.saveLayer(right - length,flags);
            }
        }  {
            scrollabilityCache.setFadeColor(solidColor);
        }

        dirtyOpaque) onDraw(canvas);

                dispatchDraw(canvas);

         Step 5,draw the fade effect and restore layers
        final Paint p = scrollabilityCache.paint;
        final Matrix matrix = scrollabilityCache.matrix;
        final Shader fade = scrollabilityCache.shader;

         (drawTop) {
            matrix.setScale(1,fadeHeight * topFadeStrength);
            matrix.postTranslate(left,top);
            fade.setLocalMatrix(matrix);
            p.setShader(fade);
            canvas.drawRect(left,top + length,p);
        }

         (drawBottom) {
            matrix.setScale(1,1)"> bottomFadeStrength);
            matrix.postRotate(180);
            matrix.postTranslate(left,bottom);
            fade.setLocalMatrix(matrix);
            p.setShader(fade);
            canvas.drawRect(left,bottom - (drawLeft) {
            matrix.setScale(1,1)"> leftFadeStrength);
            matrix.postRotate(-90 (drawRight) {
            matrix.setScale(1,1)"> rightFadeStrength);
            matrix.postRotate(90);
            matrix.postTranslate(right,top);
            fade.setLocalMatrix(matrix);
            p.setShader(fade);
            canvas.drawRect(right -etoCount(saveCount);

         Overlay is part of the content and draws beneath Foreground
        mOverlay.isEmpty()) {
            mOverlay.getoverlayView().dispatchDraw(canvas);
        }

                onDrawForeground(canvas);
    }

可以看到,draw过程比较复杂,但是逻辑十分清晰,而官方注释也清楚地说明了每一步的做法。我们首先来看一开始的标记位 dirtyOpaque,该标记位的作用是判断当前 View 是否是透明的,如果 View 是透明的,那么根据下面的逻辑可以看出,将不会执行一些步骤,比如绘制背景、绘制内容等。这样很容易理解,因为一个 View 既然是透明的,那就没必要绘制它了。接着是绘制流程的六个步骤,这里先小结这六个步骤分别是什么,然后再展开来讲。绘制流程的六个步骤:

  1. 对 View 的背景进行绘制

  2. 保存当前的图层信息(可跳过)

  3. 绘制 View 的内容

  4. 对 View 的子 View 进行绘制(如果有子 View )

  5. 绘制 View 的褪色的边缘,类似于阴影效果(可跳过)

  6. 绘制 View 的装饰(例如:滚动条)

其中第2步和第5步是可以跳过的,我们这里不做分析,我们重点来分析其它步骤。

ViewGroup子类默认情况下就是不执行 onDraw 方法的,在 ViewGroup 源码中的 initViewGroup() 方法中设置了一个标记,源码如下:

 initViewGroup() {
         ViewGroup doesn''t draw by default
        debugDraw()) {
            setFlags(WILL_NOT_DRAW,DRAW_MASK);
        }
        ......
}

看第二行注释也知道,ViewGroup 默认情况下是不会 draw 的。第四行调用 setFlags 方法设置标记 WILL_NOT_DRAW,在回到 View 中 draw 方法看第2行代码:

1   mPrivateFlags;
2  boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
3      (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);

 

setFlags 方法就是对 View中mPrivateFlags 值进行相应改变,我们设置标记 WILL_NOT_DRAW 那么 dirtyOpaque 得到的值就为 true,从而 if (!dirtyOpaque) 不成立,也就不会执行onDraw 方法。

1. 绘制背景

View#drawBackground

     drawBackground(Canvas canvas) {
       获取背景的Drawable,没有就不需要绘制
        final Drawable background = mBackground;
        if (background == ;
        }
       确定背景Drawable边界
        setBackgroundBounds();
        ...

       如果有偏移量先偏移画布再将drawable绘制上去
        int scrollX = mScrollX;
        int scrollY = mScrollY;
        if ((scrollX | scrollY) == 0) {
            background.draw(canvas);
        }  {
            canvas.translate(scrollX,scrollY);
            此处会执行各种Drawable对应的draw方法
            background.draw(canvas);
            把画布的原点移回去,drawable在屏幕上的位置不动
            canvas.translate(-scrollX,1)">scrollY);
        }
    }

3. 绘制 View 的内容

先跳过第 2 步,是因为不是所有的 View 都需绘制褪色边缘。DecorView#onDraw:

    onDraw(Canvas c) {
        .onDraw(c);

        mBackgroundFallback.draw(
   onDraw(Canvas canvas) {
}

onDraw 是空实现,需要子 View 自己去绘制。对于DecorView 一般也没啥内容,除了需要背景颜色等,所以本身并需要绘制啥。

4. 绘制子View

DecorView 绘制完成后,开始绘制子 View,所以 ViewGroup 的绘制需要绘制子 View,直接看看 ViewGroup#dispatchDraw:

 dispatchDraw(Canvas canvas) {
       boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
       int childrenCount = mChildrenCount;
       final View[] children = mChildren;
        mGroupFlags;

ViewGroup是否有设置子View入场动画,如果有绑定到View
 启动动画控制器
      ...

指定修改区域
       int clipSaveCount = 0;
       boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
    // 不让子view绘制在pandding里面,也就是去除padding
(clipToPadding) { clipSaveCount = canvas.save(); canvas.clipRect(mScrollX + mPaddingLeft,mScrollY + mPaddingTop,mScrollX + mRight - mLeft - mPaddingRight,mScrollY + mBottom - mTop - mPaddingBottom); } ... int i = 0; i < childrenCount; i++) { 先取mTransientViews中的View,mTransientViews中的View通过addTransientView添加,它们只是容器渲染的一个item while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) { final View transientChild = mTransientViews.get(transientIndex); if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE || transientChild.getAnimation() != ) { more |= drawChild(canvas,transientChild,drawingTime); } transientIndex++; if (transientIndex >= transientCount) { transientIndex = -1; } } int childindex = customOrder ? getChildDrawingOrder(childrenCount,i) : i; final View child = (preorderedList == ) ? children[childindex] : preorderedList.get(childindex); if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != ) { more |=dispatchDraw 的流程是先启动第一次加到布局中的动画,然后确定绘制区域,遍历绘制 View,遍历 View 的时候优先绘制渲染的 mTransientViews,绘制 View 调用到ViewGroup#drawChild:

boolean drawChild(Canvas canvas,View child,1)">long drawingTime) {
        View中有两个draw方法
        这个多参数的draw用于view绘制自身内容
        return child.draw(canvas,drawingTime);
    }

 View#draw(canvas,this,drawingTime)

boolean draw(Canvas canvas,ViewGroup parent,1)"> drawingTime) {
 
       boolean drawingWithRenderNode = mAttachInfo != null
                && mAttachInfo.mHardwareAccelerated
                && hardwareAcceleratedCanvas;
      ...

     主要判断是否有绘制缓存,如果有,直接使用缓存,如果没有,调用 draw(canvas)方法
        drawingWithDrawingCache) {
             (drawingWithRenderNode) {
                mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                ((displayListCanvas) canvas).draWrenderNode(renderNode);
            }  {
                 Fast path for layouts with no backgrounds
                if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                    mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                    dispatchDraw(canvas);
                }  {
                    draw(canvas);
                }
            }
        } if (cache != PFLAG_DIRTY_MASK;
            if (layerType == LAYER_TYPE_NONE) {
                 no layer paint,use temporary paint to draw bitmap
                Paint cachePaint = parent.mCachePaint;
                if (cachePaint == ) {
                    cachePaint =  Paint();
                    cachePaint.setDither();
                    parent.mCachePaint = cachePaint;
                }
                cachePaint.setAlpha((int) (alpha * 255));
                canvas.drawBitmap(cache,0.0f,0.0f use layer paint to draw the bitmap,merging the two alphas,but also restore
                int layerPaintAlpha = mLayerPaint.getAlpha();
                mLayerPaint.setAlpha((int) (alpha * layerPaintAlpha));
                canvas.drawBitmap(cache,mLayerPaint);
                mLayerPaint.setAlpha(layerPaintAlpha);
            }
      }
}

首先判断是否已经有缓存,即之前是否已经绘制过一次了,如果没有,则会调用 draw(canvas) 方法,开始正常的绘制,即上面所说的六个步骤,否则利用缓存来显示。

这一步也可以归纳为 ViewGroup 绘制过程,它对子 View 进行了绘制,而子 View 又会调用自身的 draw 方法来绘制自身,这样不断遍历子 View 及子 View 的不断对自身的绘制,从而使得 View 树完成绘制。

对于自定义 View ,如果需要绘制东西的话,直接重新 onDraw 就可以了。

6. 绘制装饰

     onDrawForeground(Canvas canvas) {
     绘制滑动指示
        onDrawScrollIndicators(canvas);
     绘制ScrollBar
        onDrawScrollBars(canvas);
     获取前景色的Drawable,绘制到canvas上
        final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : if (foreground !=  (mForegroundInfo.mBoundsChanged) {
                mForegroundInfo.mBoundsChanged = ;
                final Rect selfBounds = mForegroundInfo.mSelfBounds;
                final Rect overlayBounds = mForegroundInfo.mOverlayBounds;
                 (mForegroundInfo.mInsidePadding) {
                    selfBounds.set(0,0 {
                    selfBounds.set(getPaddingLeft(),getPaddingTop(),getWidth() - getPaddingRight(),getHeight() - getPaddingBottom());
                }
                int ld = getLayoutDirection();
                Gravity.apply(mForegroundInfo.mGravity,foreground.getIntrinsicWidth(),foreground.getIntrinsicHeight(),selfBounds,overlayBounds,ld);
                foreground.setBounds(overlayBounds);
            }
            foreground.draw(canvas);
        }
    }

 2和5.绘制View的褪色边缘

当 horizontalEdges 或者 verticalEdges 有一个 true 的时候,表示需要绘制 View 的褪色边缘:

     boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;

这时候先计算出是否需要绘制上下左右的褪色边缘和它的参数,然后保存视图层:

         mPaddingLeft;
         getLeftPaddingOffset();
        }
         getFadeHeight(offsetrequired);
         getBottomPaddingOffset();
        }
        ) fadeHeight;
        ;
        }
        saveCount = canvas.getSaveCount();
         Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;
             {
            scrollabilityCache.setFadeColor(solidColor);
        }

绘制褪色边缘,恢复视图层 :

         scrollabilityCache.shader;
        etoCount(saveCount);

所谓的绘制装饰,就是指 View 除了背景、内容、子 View 的其余部分,例如滚动条等。

最后附上 View 的 draw 流程:

 

 

到此,View 的绘制流程就讲完了,下一篇会讲自定义 View。

小结:

  • view 不停找 parent 可以一直找到 DecorView,按理说 DecorView 是顶点了,但是 DecorView 还有个虚拟父 view,ViewRootImpl。 ViewRootImpl 不是一个 View 或者ViewGroup,他有个成员 mView 是 DecorView,所有的操作从 ViewRootImpl 开始自上而下分发

  • view 的 invalidate 不会导致 ViewRootImpl 的 invalidate 被调用,而是递归调用父 view的invalidateChildInParent,直到 ViewRootImpl 的 invalidateChildInParent,然后触发peformTraversals,会导致当前 view 被重绘,由于 mLayoutRequested 为 false,不会导致 onMeasure 和 onLayout 被调用,而 OnDraw 会被调用

  • 一个 view 的 invalidate 会导致本身 PFLAG_INVALIDATED 置 1,导致本身以及父族 viewgroup 的 PFLAG_DRAWING_CACHE_VALID 置 0

  • requestLayout 会直接递归调用父窗口的 requestLayout,直到 ViewRootImpl,然后触发 peformTraversals,由于 mLayoutRequested 为 true,会导致 onMeasure 和onLayout 被调用。不一定会触发 OnDraw

  • requestLayout 触发 onDraw 可能是因为在在 layout 过程中发现 l,b 和以前不一样,那就会触发一次 invalidate,所以触发了onDraw,也可能是因为别的原因导致 mDirty 非空(比如在跑动画)

  • requestLayout 会导致自己以及父族 view 的 PFLAG_FORCE_LAYOUT 和 PFLAG_INVALIDATED 标志被设置。

  • 一般来说,只要刷新的时候就调用 invalidate,需要重新 measure 就调用 requestLayout,后面再跟个 invalidate(为了保证重绘),

 

总结

以上是小编为你收集整理的Android View 的绘制流程之 Layout 和 Draw 过程详解 (二) Android View 绘制流程之 DecorView 与 ViewRootImplAndroid View 的绘制流程之 Measure 过程详解 (一)Android View 的绘制流程之 Layout 和 Draw 过程详解 (二)Android View 的事件分发原理解析Android 自定义 View 详解Android View 的绘制流程之 Measure 过程详解 (一)全部内容。

如果觉得小编网站内容还不错,欢迎将小编网站推荐给好友。

关于Java Android 二进制文件读写java实现二进制文件读写功能的问题我们已经讲解完毕,感谢您的阅读,如果还想了解更多关于Android - 致命异常:由 Percel.java 上的 java.lang.SecurityException 引起的 java.lang.RuntimeException、Android N 将从专属 Java API 转向 OpenJDK,openjdk 是用 java 开发的吗?还是用 java 来开发 app 吗?、Android Studio,java.lang.ClassCastException:java.lang.String 无法转换为 java.lang.Long、Android View 的绘制流程之 Layout 和 Draw 过程详解 (二) Android View 绘制流程之 DecorView 与 ViewRootImplAndroid View 的绘制流程之 Measure 过程详解 (一)Android View 的绘制流程之 Layout 和 Draw 过程详解 (二)Android View 的事件分发原理解析Android 自定义 View 详解Android View 的绘制流程之 Measure 过程详解 (一)等相关内容,可以在本站寻找。

本文标签: