UI的绘制流程和事件分发,属于Android里面的重点内容,在做自定义UI的时候,更是应该了解UI的绘制流程是如何的,此篇文章就是说明UI的绘制流程,事件分发前面已经详细讲过了

UI绘制流程探索

这里分析Activity,而不是AppCompatActivity,后者做了兼容处理,前者更容易理清逻辑

要知道UI的绘制流程,就需要有一个入手点,而这个入手点就是onCreate(),也就是下面这句代码

setContentView(R.layout.activity_main);

接下来找到了这个方法

public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}

这里先看``,有下面的源代码发现,这里只是设置了ActionBar,并没有UI的绘制

private void initWindowDecorActionBar() {
Window window = getWindow(); // Initializing the window decor can change window feature flags.
// Make sure that we have the correct set before performing the test below.
window.getDecorView(); if (isChild() || !window.hasFeature(Window.FEATURE_ACTION_BAR) || mActionBar != null) {
return;
} mActionBar = new WindowDecorActionBar(this);
mActionBar.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp); mWindow.setDefaultIcon(mActivityInfo.getIconResource());
mWindow.setDefaultLogo(mActivityInfo.getLogoResource());
}

那么其关注的重点就放在了第一个设置上了

getWindow()返回的是mWindow,仔细查找发现,mWindow实际上就是一个Windows的实现类:PhoneWindow

mWindow = new PhoneWindow(this, window);

那么也就是说要在PhoneWindow中寻找setContentView()方法

PhoneWindowInternal API,只能通过源代码查看(com.android.internal.policy)

在源代码中找到了相关代码

@Override
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
} if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}

这里的关注点在于installDecor()方法,这个方法

private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
···
}

这里又指向了generateDecor(),继续查看

protected DecorView generateDecor(int featureId) {
// System process doesn't have application context and in that case we need to directly use
// the context we have. Otherwise we want the application context, so we don't cling to the
// activity.
Context context;
if (mUseDecorContext) {
Context applicationContext = getContext().getApplicationContext();
if (applicationContext == null) {
context = getContext();
} else {
context = new DecorContext(applicationContext, getContext().getResources());
if (mTheme != -1) {
context.setTheme(mTheme);
}
}
} else {
context = getContext();
}
return new DecorView(context, featureId, this, getAttributes());
}

这里返回了一个DecorView,那么DecorView是什么呢,他是一个类继承了FrameLayout

/** @hide */
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
···
}

那么回到installDecor(),在下面有这样的语句

private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
···
}
···
}

而这里的generateLayout()有一句注释

protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
···
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
}

其作用是用来设置当前的主题数据,其中的上面一段代码很重要,在这个方法里面,渲染了一个根布局

void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
mStackId = getStackId(); if (mBackdropFrameRenderer != null) {
loadBackgroundDrawablesIfNeeded();
mBackdropFrameRenderer.onResourcesLoaded(
this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable,
mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState),
getCurrentColor(mNavigationColorViewState));
} mDecorCaptionView = createDecorCaptionView(inflater);
final View root = inflater.inflate(layoutResource, null);
if (mDecorCaptionView != null) {
if (mDecorCaptionView.getParent() == null) {
addView(mDecorCaptionView,
new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mDecorCaptionView.addView(root,
new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
} else { // Put it below the color views.
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mContentRoot = (ViewGroup) root;
initializeElevation();
}

在根布局里面才是我们的用户布局,那么可以得出整个屏幕的层次结构应该是这样的



那么再回到setContentView(),下面有一段重要代码

mLayoutInflater.inflate(layoutResID, mContentParent);

这便是通过资源ID去寻找布局,然后进行用户布局的渲染

用户布局绘制

上面介绍完了外层布局的绘制,那么在用户布局这里是怎么进行绘制的呢,其绘制方法在View.java

向上面一样,这里首先要寻找的也是其布局绘制的入口,而这个绘制入口就是requestLayout()

@CallSuper
public void requestLayout() {
if (mMeasureCache != null) mMeasureCache.clear(); if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
// Only trigger request-during-layout logic if this is the view requesting it,
// not the views in its parent hierarchy
ViewRootImpl viewRoot = getViewRootImpl();
if (viewRoot != null && viewRoot.isInLayout()) {
if (!viewRoot.requestLayoutDuringLayout(this)) {
return;
}
}
mAttachInfo.mViewRequestingLayout = this;
} mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED; if (mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout();
}
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
mAttachInfo.mViewRequestingLayout = null;
}
}

这里会不断地递归寻找父容器,直到找到DecorView

而在这之后,会执行到ViewRootImp类的performTranversal()方法

最后在performTranversal()方法中,会调用performMeasure(),performLayout()performDraw(),而这三个方法又会进一步调用,最终会调用到View的measure(测量),layout(摆放)draw(绘制)

其控制过程如下图

measure的测量过程

测量过程如下图



因为在Android中,其屏幕规格尺寸千差万别,为了做自适应,就需要测量,measure过程会使用先序遍历遍历整颗View树,然后依次测量每一个View的真实的尺寸

在measure当中,有一个很重要的参数:MeasureSpec(测量规格)

这个参数是用int表示的,其前两位代表mode,后三十位代表值

mode:EXACTLY(精确值)AT_MOST(最值)UNSPECIFIED(不确定值)

value:宽高的值

经过大量测量以后,最终确定了自己的宽高,才会调用setMeasuredDimension(w,h),从而真正确定宽高值,只有这时候调用宽高获取,其获取到的值才是有效的

写自定义控件的时候,必须先经过measure,才能获得到宽高,不是getWidth(),而是getMeasuredWidth(),也就是当我们重写onMeasure()的时候,我们需要在里面调用child.measure()才能获取child的宽高

从规格当中获取mode和value

int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);

反过来将mode和value合成一个规格

MeasureSpec.makeMeasureSpec(resultSize, resultMode);

自定义控件的测量问题

  • 继承自View的子类

    只需要重写onMeasure测量好自己的宽高就可以了

    最终调用setMeasuredDimension()保存好自己的测量宽高。
int mode = MeasureSpec.getMode(widthMeasureSpec);
int size = MeasureSpec.getSize(widthMeasureSpec);
int viewSize = 0;
switch(mode){
case MeasureSpec.EXACTLY:
viewSize = size;//当前view的尺寸就为父容器的尺寸
break;
case MeasureSpec.AT_MOST:
viewSize = Math.min(size, getContentSize());//当前view的尺寸就为内容尺寸和费容器尺寸当中的最小值。
break;
case MeasureSpec.UNSPECIFIED:
viewSize = getContentSize();//内容有多大,久设置多大尺寸。
break;
default:
break;
}
//setMeasuredDimension(width, height);
setMeasuredDimension(size);
  • 继承自ViewGroup的子类:

    不但需要重写onMeasure测量自己,还要测量子控件的规格大小

    可以直接使用ViewGroup的工具方法来测量里面的子控件,也可以自己来实现这一套子控件的测量(比如:RelativeLayout)
//1.测量自己的尺寸
ViewGroup.onMeasure();
//1.1 为每一个child计算测量规格信息(MeasureSpec)
ViewGroup.getChildMeasureSpec();
//1.2 将上面测量后的结果,传给每一个子View,子view测量自己的尺寸
child.measure();
//1.3 子View测量完,ViewGroup就可以拿到这个子View的测量后的尺寸了
child.getChildMeasuredSize();//child.getMeasuredWidth()和child.getMeasuredHeight()
//1.4ViewGroup自己就可以根据自身的情况(Padding等等),来计算自己的尺寸
ViewGroup.calculateSelfSize();
//2.保存自己的尺寸
ViewGroup.setMeasuredDimension(size);

理解实例

自定义ViewGroup

public class CustomView extends ViewGroup {

    private static final int OFFSET = 80;

    public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
} @Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int left = 0;
int right = 0;
int top = 0;
int bottom = 0;
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
left = i * OFFSET;
right = left + child.getMeasuredWidth();
bottom = top + child.getMeasuredHeight();
child.layout(left, top, right, bottom);
top += child.getMeasuredHeight();
}
} @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width = 0;
int height = 0;
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
LayoutParams lp = child.getLayoutParams();
int childWidthSpec = getChildMeasureSpec(widthMeasureSpec, 0, lp.width);
int childHeightSpec = getChildMeasureSpec(heightMeasureSpec, 0, lp.height);
child.measure(childWidthSpec, childHeightSpec);
}
switch (widthMode) {
case MeasureSpec.EXACTLY:
width = widthSize;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.UNSPECIFIED:
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
int widthAndOffset = i * OFFSET + child.getMeasuredWidth();
width = Math.max(width, widthAndOffset);
}
break;
default:
break;
}
switch (heightMode) {
case MeasureSpec.EXACTLY:
height = heightSize;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.UNSPECIFIED:
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
height += child.getMeasuredHeight();
}
break;
default:
break;
}
setMeasuredDimension(width, height);
}
}

布局

<?xml version="1.0" encoding="utf-8"?>
<com.cj5785.testuimld.CustomView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"> <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="第一行文本" /> <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="第二行文本" /> <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="第三行文本" /> <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="第四行文本" />
</com.cj5785.testuimld.CustomView>

效果

layout摆放过程

layout摆放子空间,其过程和measure十分类似,这里就不赘述了

其摆放位置只关注父容器,父容器又专注父容器···

一般不会重写layout方法,不然就需要专注分发问题

附上layout的过程

draw绘制过程

draw用于绘制,在这个方法里面,有一段简明的注释

/*
* 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)
*/

这段注释就说明了绘制的流程,其过程也类似于measure与layout

ViewGroup的onDraw方法默认是不会调用的,因为在ViewGroup构造方法里面就默认设置了

setFlags(WILL_NOT_DRAW, DRAW_MASK);

原因是因为ViewGroup本来就没东西显示,除了设置了背景,这样就是为了效率,如果需要它执行onDraw,可以设置背景或者setWillNotDraw(false);

自定义ViewGroup实现热门标签

自定义ViewGroup

public class HotTagLayout extends ViewGroup {
//记录每一行的高度
private List<Integer> lineHeights = new ArrayList<>();
private List<List<View>> views = new ArrayList<>(); public HotTagLayout(Context context, AttributeSet attrs) {
super(context, attrs);
} @Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
views.clear();
lineHeights.clear();
//该行有多少个列数据
List<View> lineViews = new ArrayList<>();
//1.计算
int width = getMeasuredWidth();//容器宽
int lineWidth = 0;
int lineHeight = 0;
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
int childWidth = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight();
if (childWidth + lp.leftMargin + lp.rightMargin + lineWidth > width) {
//换行
lineHeights.add(lineHeight);
views.add(lineViews);
lineWidth = 0;
lineViews = new ArrayList<>();
}
lineWidth += childWidth + lp.leftMargin + lp.rightMargin;
lineHeight = Math.max(lineHeight, childHeight + lp.topMargin + lp.bottomMargin);
lineViews.add(child);
}
lineHeights.add(lineHeight);
views.add(lineViews);
//2.摆放
int left = 0;
int top = 0;
int size = views.size();
for (int i = 0; i < size; i++) {
lineViews = views.get(i);
lineHeight = lineHeights.get(i);
for (int j = 0; j < lineViews.size(); j++) {
//遍历一行所有child
View child = lineViews.get(j);
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
int lc = left + lp.leftMargin;
int tc = top + lp.topMargin;
int rc = lc + child.getMeasuredWidth();
int bc = tc + child.getMeasuredHeight();
child.layout(lc, tc, rc, bc);
left += child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
}
left = 0;
top += lineHeight;
}
} @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
int width = 0; //所有行最宽的一行
int height = 0; //所有行的高度相加
int lineWidth = 0;
int lineHeight = 0;
//1.测量所有子控件大小
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
measureChild(child, widthMeasureSpec, heightMeasureSpec);
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
//计算子控件真实占用的宽高
int childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
int childHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
//当一行放不下时候换行
if (lineWidth + childWidth > sizeWidth) {
width = Math.max(lineWidth, width);
lineWidth = childWidth;
height += lineHeight;
lineHeight = childHeight;
} else {
lineWidth += childWidth;
lineHeight = Math.max(lineHeight, childHeight);
}
//最后得到宽高
if (i == childCount - 1) {
width = Math.max(width, lineWidth);
height += lineHeight;
}
}
//2.测量并定义自身大小
int measuredWidth = (modeWidth == MeasureSpec.EXACTLY) ? sizeWidth : width;
int measuredHeight = (modeHeight == MeasureSpec.EXACTLY) ? sizeHeight : height;
setMeasuredDimension(measuredWidth, measuredHeight);
} @Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}
}

布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"> <com.cj5785.testuimld.HotTagLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/darker_gray"> <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:background="@drawable/tag_style"
android:text="哈哈"
android:textColor="@android:color/holo_orange_light"
android:textSize="32sp" /> <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:background="@drawable/tag_style"
android:text="太好笑了"
android:textColor="@android:color/holo_blue_light"
android:textSize="36sp" /> <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:background="@drawable/tag_style"
android:text="这也行"
android:textColor="@android:color/holo_green_light"
android:textSize="24sp" /> <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:background="@drawable/tag_style"
android:text="hiahiahia~~~"
android:textColor="@android:color/holo_red_light"
android:textSize="18sp" /> </com.cj5785.testuimld.HotTagLayout> </LinearLayout>

背景

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#AAC07F" />
<corners android:radius="5dp" />
<padding
android:bottom="2dp"
android:left="10dp"
android:right="10dp"
android:top="2dp" />
</shape>

效果

高级UI-UI绘制流程的更多相关文章

  1. 一篇文章教你读懂UI绘制流程

    最近有好多人问我Android没信心去深造了,找不到好的工作,其实我以一个他们进行回复,发现他们主要是内心比较浮躁,要知道技术行业永远缺少的是高手.建议先阅读浅谈Android发展趋势分析,在工作中, ...

  2. Android UI绘制流程及原理

    一.绘制流程源码路径 1.Activity加载ViewRootImpl ActivityThread.handleResumeActivity() --> WindowManagerImpl.a ...

  3. UI设计(流程/界面)设计规范

    1.UI设计基本概念与流程 1.1 目的 规范公司UI设计流程,使UI设计师参与到产品设计整个环节中来,对产品的易用性进行全流程负责,使UI设计的流程规范化,保证UI设计流程的可操作性. 1.2范围  ...

  4. 在 Snoop 中使用 PowerShell 脚本进行更高级的 UI 调试

    原文:在 Snoop 中使用 PowerShell 脚本进行更高级的 UI 调试 版权声明:本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可.欢迎转载.使用.重新发布, ...

  5. 深入理解 Android 之 View 的绘制流程

    概述 本篇文章会从源码(基于Android 6.0)角度分析Android中View的绘制流程,侧重于对整体流程的分析,对一些难以理解的点加以重点阐述,目的是把View绘制的整个流程把握好,而对于特定 ...

  6. Android应用层View绘制流程与源码分析

    1  背景 还记得前面<Android应用setContentView与LayoutInflater加载解析机制源码分析>这篇文章吗?我们有分析到Activity中界面加载显示的基本流程原 ...

  7. Android中View绘制流程以及invalidate()等相关方法分析

    [原文]http://blog.csdn.net/qinjuning 整个View树的绘图流程是在ViewRoot.java类的performTraversals()函数展开的,该函数做的执行过程可简 ...

  8. Android View绘制流程

    框架分析 在之前的下拉刷新中,小结过触屏消息先到WindowManagerService(Wms)然后顺次传递给ViewRoot(派生自Handler),经decor view到Activity再传递 ...

  9. Android绘制流程

    一.前言 1.1.C++界面库 MFC.WTL.DuiLib.QT.Skia.OpenGL.Android里面的画图分为2D和3D两种: 2D是由Skia 来实现的,3D部分是由OpenGL实现的. ...

随机推荐

  1. pipy配置镜像源

    新电脑第一次使用使用pip命令下载贼慢 我们需要使用国内pipy镜像,参考如下 https://mirrors.tuna.tsinghua.edu.cn/help/pypi/ 所以只要设置一下就行了: ...

  2. python——使用xlwing库进行Excel操作

    Excel是现在比不可少的数据处理软件,python有很多支持Excel操作的库,xlwing就是其中之一. xlwings的安装 xlwings库使用pip安装: 在控制台输入 pip instal ...

  3. CodeForces - 76A:Gift (最小生成树 解决单调性问题是思想)

    题意:给定N点M边的无向连通图,每条边有两个权值(g,s). 给定G,S. 让你给出一组(g0,s0)使得图中仅留下g<=g0, s<=s0的边之后,依然连通,并求Gg0+Ss0的最小值. ...

  4. siameseNet网络以及信号分类识别应用

    初学siameseNet网络,希望可以用于信号的识别分类应用.此文为不间断更新的笔记. siameseNet简介 全连接孪生网络(siamese network)是一种相似性度量方法,适用于类别数目多 ...

  5. lxml_time_代理

    import requests from pyquery import PyQuery as pq import json import jsonpath from lxml import etree ...

  6. 如何抓取微信小程序的源码?

    一.引言: 在工作中我们会想把别人的代码直接拿过来进行参考,当然这个更多的是前端代码的进行获取. 那么微信小程序的代码怎么样获取呢?  参考 https://blog.csdn.net/qq_4113 ...

  7. 搭建的一套vue打包方案,方便记录一下

    package.json 配置如下: { "name": "rise-vue", "version": "1.0.0", ...

  8. mysql 左联结与右联结

    mysql> select * from test; +----+------------+-------+-----------+ | id | name | score | subject ...

  9. DML 语言

    数据操纵语言(Data Manipulation Language, DML)是SQL语言中,负责对数据库对象运行数据访问工作的指令集. 以INSERT.UPDATE.DELETE三种指令为核心,分别 ...

  10. hive 整合ranger

    一.安装hive插件 1.解压安装 #  tar zxvf ranger-2.0.0-SNAPSHOT-hive-plugin.tar.gz -C /data1/hadoop/ 2.修改install ...