Android 手把手带你玩转自己定义相机

本文已授权微信公众号《鸿洋》原创首发,转载请务必注明出处。

概述

相机差点儿是每一个APP都要用到的功能,万一老板让你定制相机方不方?反正我是有点方。

关于相机的两天奋斗总结免费送给你。

  Intent intent = new Intent();  intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);  startActivity(intent);

或者指定返回图片的名称mCurrentPhotoFile

  Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);intent.putExtra(MediaStore.EXTRA_OUTPUT,Uri.fromFile(mCurrentPhotoFile));startActivityForResult(intent, CAMERA_WITH_DATA);

2.自己定义启动相机。

今天以另外一种为例。

效果图例如以下
demo

自己定义相机的一般步骤

  1. 创建显示相机画面的布局。Android已经为我们选定好SurfaceView
  2. 通过SurfaceView#getHolder()获得链接CameraSurfaceViewSurfaceHolder
  3. Camame.open()打开相机
  4. 通过SurfaceHolder链接CameraurfaceView

一般步骤的代码演示

public class CameraSurfaceView extends SurfaceView implements SurfaceHolder.Callback, Camera.AutoFocusCallback {private static final String TAG = "CameraSurfaceView";private Context mContext;private SurfaceHolder holder;private Camera mCamera;private int mScreenWidth;private int mScreenHeight;public CameraSurfaceView(Context context) {this(context, null);}public CameraSurfaceView(Context context, AttributeSet attrs) {this(context, attrs, 0);}public CameraSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);mContext = context;getScreenMetrix(context);initView();}private void getScreenMetrix(Context context) {WindowManager WM = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);DisplayMetrics outMetrics = new DisplayMetrics();WM.getDefaultDisplay().getMetrics(outMetrics);mScreenWidth = outMetrics.widthPixels;mScreenHeight = outMetrics.heightPixels;}private void initView() {holder = getHolder();//获得surfaceHolder引用holder.addCallback(this);holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);//设置类型}@Overridepublic void surfaceCreated(SurfaceHolder holder) {Log.i(TAG, "surfaceCreated");if (mCamera == null) {mCamera = Camera.open();//开启相机try {mCamera.setPreviewDisplay(holder);//摄像头画面显示在Surface上} catch (IOException e) {e.printStackTrace();}}}@Overridepublic void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {Log.i(TAG, "surfaceChanged");mCamera.startPreview();}@Overridepublic void surfaceDestroyed(SurfaceHolder holder) {Log.i(TAG, "surfaceDestroyed");mCamera.stopPreview();//停止预览mCamera.release();//释放相机资源mCamera = null;holder = null;}@Overridepublic void onAutoFocus(boolean success, Camera Camera) {if (success) {Log.i(TAG, "onAutoFocus success="+success);}}
}

加入相机和自己主动聚焦限权

<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera.autofocus" />

CameraSurfaceView放在布局文件里,这里建议最外层为FrameLayout,后面会用到。如此。我们便有了一个没有照相功能的相机。初次之外,细致观察相机显示画面,图片是不是变形严重?那是由于我们还没有为相机设置各种參数。在预览前要设置摄像头的分辨率、预览分辨率和图片分辨率的宽高比保持一致。这样图片才不会变形。这是个比較难以理解的部分,想深刻理解还需读者自己动手去实践。

   private void setCameraParams(Camera camera, int width, int height) {Log.i(TAG,"setCameraParams  width="+width+"  height="+height);Camera.Parameters parameters = mCamera.getParameters();// 获取摄像头支持的PictureSize列表List<Camera.Size> pictureSizeList = parameters.getSupportedPictureSizes();for (Camera.Size size : pictureSizeList) {Log.i(TAG, "pictureSizeList size.width=" + size.width + "  size.height=" + size.height);}/**从列表中选取合适的分辨率*/Camera.Size picSize = getProperSize(pictureSizeList, ((float) height / width));if (null == picSize) {Log.i(TAG, "null == picSize");picSize = parameters.getPictureSize();}Log.i(TAG, "picSize.width=" + picSize.width + "  picSize.height=" + picSize.height);// 依据选出的PictureSize又一次设置SurfaceView大小float w = picSize.width;float h = picSize.height;parameters.setPictureSize(picSize.width,picSize.height);this.setLayoutParams(new FrameLayout.LayoutParams((int) (height*(h/w)), height));// 获取摄像头支持的PreviewSize列表List<Camera.Size> previewSizeList = parameters.getSupportedPreviewSizes();for (Camera.Size size : previewSizeList) {Log.i(TAG, "previewSizeList size.width=" + size.width + "  size.height=" + size.height);}Camera.Size preSize = getProperSize(previewSizeList, ((float) height) / width);if (null != preSize) {Log.i(TAG, "preSize.width=" + preSize.width + "  preSize.height=" + preSize.height);parameters.setPreviewSize(preSize.width, preSize.height);}parameters.setJpegQuality(100); // 设置照片质量if (parameters.getSupportedFocusModes().contains(android.hardware.Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {parameters.setFocusMode(android.hardware.Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);// 连续对焦模式}mCamera.cancelAutoFocus();//自己主动对焦。

// 设置PreviewDisplay的方向。效果就是将捕获的画面旋转多少度显示 // TODO 这里直接设置90°不严谨。详细见https://developer.android.com/reference/android/hardware/Camera.html#setPreviewDisplay%28android.view.SurfaceHolder%29 mCamera.setDisplayOrientation(90); mCamera.setParameters(parameters); } /** * 从列表中选取合适的分辨率 * 默认w:h = 4:3 * <p>tip:这里的w相应屏幕的height * h相应屏幕的width<p/> */ private Camera.Size getProperSize(List<Camera.Size> pictureSizeList, float screenRatio) { Log.i(TAG, "screenRatio=" + screenRatio); Camera.Size result = null; for (Camera.Size size : pictureSizeList) { float currentRatio = ((float) size.width) / size.height; if (currentRatio - screenRatio == 0) { result = size; break; } } if (null == result) { for (Camera.Size size : pictureSizeList) { float curRatio = ((float) size.width) / size.height; if (curRatio == 4f / 3) {// 默认w:h = 4:3 result = size; break; } } } return result; }

进去的是屏幕宽高。出来的是调整好了的參数。在surfaceChanged方法中运行mCamera.startPreview(); 前调用setCameraParams(mCamera, mScreenWidth, mScreenHeight); 就能够了。最后要在AndroidManifest.xml里设置activity的方向android:screenOrientation="portrait"代码里有非常多凝视,当中也有我自己调试时候的Log,大家能够自己调试下。看看不同參数的效果。昨天调參数搞到一点多,都在折腾这个函数。

唉,一把辛酸泪。
身为一个相机,竟然不能照相?真是太丢脸了!

以下给我们的相机加入上照相的功能。照相核心代码就一句:mCamera.takePicture(null, null, jpeg);
能够看到takePicture方法有三个參数,各自是ShutterCallbackPictureCallbackPictureCallback。这里我们仅仅用了PictureCallback

    // 拍照瞬间调用private Camera.ShutterCallback shutter = new Camera.ShutterCallback() {@Overridepublic void onShutter() {Log.i(TAG,"shutter");}};// 获得没有压缩过的图片数据private Camera.PictureCallback raw = new Camera.PictureCallback() {@Overridepublic void onPictureTaken(byte[] data, Camera Camera) {Log.i(TAG, "raw");}};//创建jpeg图片回调数据对象private Camera.PictureCallback jpeg = new Camera.PictureCallback() {@Overridepublic void onPictureTaken(byte[] data, Camera Camera) {BufferedOutputStream bos = null;Bitmap bm = null;try {// 获得图片bm = BitmapFactory.decodeByteArray(data, 0, data.length);if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {Log.i(TAG, "Environment.getExternalStorageDirectory()="+Environment.getExternalStorageDirectory());String filePath = "/sdcard/dyk"+System.currentTimeMillis()+".jpg";//照片保存路径File file = new File(filePath);if (!file.exists()){file.createNewFile();}bos = new BufferedOutputStream(new FileOutputStream(file));bm.compress(Bitmap.CompressFormat.JPEG, 100, bos);//将图片压缩到流中}else{Toast.makeText(mContext,"没有检測到内存卡", Toast.LENGTH_SHORT).show();}} catch (Exception e) {e.printStackTrace();} finally {try {bos.flush();//输出bos.close();//关闭bm.recycle();// 回收bitmap空间mCamera.stopPreview();// 关闭预览mCamera.startPreview();// 开启预览} catch (IOException e) {e.printStackTrace();}}}};

jpeg#onPictureTaken()里。我们将存储照片信息的byte[] data解析成bitmap,然后转换成JPG格式的图片保存在SD卡中。注意finally中最后两句mCamera.stopPreview();// 关闭预览 mCamera.startPreview();// 开启预览 上文也提到:当调用camera.takePiture()方法后。camera关闭了预览。这时须要调用startPreview()来又一次开启预览。假设不再次开启预览。则会一直停留在拍摄照片画面。为了方便外部调用拍照。这里我暴露了一个方法供外部拍照。

    public void takePicture(){//设置參数,并拍照setCameraParams(mCamera, mScreenWidth, mScreenHeight);// 当调用camera.takePiture方法后,camera关闭了预览,这时须要调用startPreview()来又一次开启预览mCamera.takePicture(null, null, jpeg);}

在布局文件里加入一个Button,点击Button运行takePicture()方法。

不要忘了加入写SD卡限权

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

至此。一个具有照相并保存拍摄图片功能的相机就做出来了。

But,我们就此满足了吗?要是为了这些简单的功能我也不会写这篇博客。这仅仅是个開始

真正的開始

别人APP在照相的时候。屏幕上竟然能够显示像效果图那样的框框啦、辅助点啦、图片bulabulabula~。

在网上搜索一番实现方式,再加上一些自己的理解,构成了这篇博客。
上文布局文件一直没有贴。如今贴出来大家先扫一眼,有些控件会在接下来展示

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"><com.dyk.cameratest.view.CameraSurfaceView
        android:id="@+id/cameraSurfaceView"android:layout_width="match_parent"android:layout_height="match_parent" /><com.dyk.cameratest.view.RectOnCamera
        android:layout_width="match_parent"android:layout_height="match_parent" /><RelativeLayout
        android:layout_width="match_parent"android:layout_height="match_parent"><Button
            android:layout_alignParentBottom="true"android:layout_centerHorizontal="true"android:layout_marginBottom="20dp"android:id="@+id/takePic"android:layout_width="80dp"android:layout_height="50dp"android:background="#88427ac7"android:text="拍照"android:textColor="#aaa" /></RelativeLayout>
</FrameLayout>

布局文件的最外层是个FrameLayout。我们知道FrameLayout是自带覆盖效果的。由来这个思路接下来就非常easy了。

编程重要的是思想,思想有了,其余的就剩详细的实现细节。

自己定义边边框框

为了和CameraSurfaceView区分开,再自己定义一个RectOnCamera专门用来画边边框框这些东西。这样做还一个优点是方便维护,不至于将所有东西都放在一个View中。

RectOnCamera

package com.dyk.cameratest.view;
...
/*** Created by 一口仨馍 on 2016/4/7.*/
public class RectOnCamera extends View {private static final String TAG = "CameraSurfaceView";private int mScreenWidth;private int mScreenHeight;private Paint mPaint;private RectF mRectF;// 圆private Point centerPoint;private int radio;public RectOnCamera(Context context) {this(context, null);}public RectOnCamera(Context context, AttributeSet attrs) {this(context, attrs, 0);}public RectOnCamera(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);getScreenMetrix(context);initView(context);}private void getScreenMetrix(Context context) {WindowManager WM = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);DisplayMetrics outMetrics = new DisplayMetrics();WM.getDefaultDisplay().getMetrics(outMetrics);mScreenWidth = outMetrics.widthPixels;mScreenHeight = outMetrics.heightPixels;}private void initView(Context context) {mPaint = new Paint();mPaint.setAntiAlias(true);// 抗锯齿mPaint.setDither(true);// 防抖动mPaint.setColor(Color.RED);mPaint.setStrokeWidth(5);mPaint.setStyle(Paint.Style.STROKE);// 空心int marginLeft = (int) (mScreenWidth*0.15);int marginTop = (int) (mScreenHeight * 0.25);mRectF = new RectF(marginLeft, marginTop, mScreenWidth - marginLeft, mScreenHeight - marginTop);centerPoint = new Point(mScreenWidth/2, mScreenHeight/2);radio = (int) (mScreenWidth*0.1);}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);mPaint.setColor(Color.RED);canvas.drawRect(mRectF, mPaint);mPaint.setColor(Color.WHITE);Log.i(TAG, "onDraw");canvas.drawCircle(centerPoint.x,centerPoint.y, radio,mPaint);// 外圆canvas.drawCircle(centerPoint.x,centerPoint.y, radio - 20,mPaint); // 内圆}
}

这里简单的画了一个相似二维码扫描的框框。另一个相似聚焦的内外圆。那么问题来了,聚焦的内外圆要随着手指滑而改变位置,并且要有聚焦的效果。可又和具有聚焦功能的CameraSurfaceView不是同一个类。不仅如此聚焦内外圆还全然覆盖了CameraSurfaceView。要处理这样的问题,须要接口回调。这就是思想以下的细节。如今尽管确定接口回调。但另一个问题,CameraSurfaceView类和RectOnCamera类中都没有对方的对象或者引用。没错,通过共同持有RectOnCameraCameraSurfaceViewActivity能够实现此功能。以下是详细的实现方法

动起来

首先。想要随着手指的滑动而改变RectOnCamera的位置肯定是要复写onTouchEvent()方法

    @Overridepublic boolean onTouchEvent(MotionEvent event) {switch (event.getAction()){case MotionEvent.ACTION_DOWN:case MotionEvent.ACTION_MOVE:case MotionEvent.ACTION_UP:int x = (int) event.getX();int y = (int) event.getY();centerPoint = new Point(x, y);invalidate();return true;}return true;}

其次,定义回调接口

 private IAutoFocus mIAutoFocus;/** 聚焦的回调接口 */public interface  IAutoFocus{void autoFocus();}public void setIAutoFocus(IAutoFocus mIAutoFocus) {this.mIAutoFocus = mIAutoFocus;}

onTouchEvent()return前加入

  if (mIAutoFocus != null){mIAutoFocus.autoFocus();}

至此我们的回调接口已经定义好了。此时还须要CameraSurfaceView暴露一个聚焦方法。以便Activity调用

    public void setAutoFocus(){mCamera.autoFocus(this);}

准备工作已经所有完毕。以下请看Activity的详细实现:

public class MainActivity extends Activity implements View.OnClickListener,RectOnCamera.IAutoFocus{private CameraSurfaceView mCameraSurfaceView;private RectOnCamera mRectOnCamera;private Button takePicBtn;private boolean isClicked;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);requestWindowFeature(Window.FEATURE_NO_TITLE);// 全屏显示     getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);setContentView(R.layout.activity_main);mCameraSurfaceView = (CameraSurfaceView) findViewById(R.id.cameraSurfaceView);mRectOnCamera = (RectOnCamera) findViewById(R.id.rectOnCamera);takePicBtn= (Button) findViewById(R.id.takePic);mRectOnCamera.setIAutoFocus(this);takePicBtn.setOnClickListener(this);}@Overridepublic void onClick(View v) {switch (v.getId()){case R.id.takePic:mCameraSurfaceView.takePicture();break;default:break;}}@Overridepublic void autoFocus() {mCameraSurfaceView.setAutoFocus();}
}

能够看到,MainActivity实现了IAutoFocus接口,并且在复写的IAutoFocus#autoFocus()方法中。调用了CameraSurfaceView暴露出来的方法setAutoFocus()。至此,在RectOnCamera每次的滑动过程中都会改变聚焦内外圆的位置,还会添加聚焦功能。

一心二用甚至一心多用岂不是更好。

结束语

在经历两次断电没保存和一次CSDNserver错误内容丢失之后终究还是完毕了这篇博客,实属不易。十分感谢能听我啰嗦到结尾~

PS:Demo界面并没有做的非常精致,仅仅是提供了一种思路。依照此思路能做出比較华丽的效果,授人以鱼不如授人以渔。

2016.10.12 在经历了上述种种磨难之后。最终发表了这篇博文。然而发表没几天,被我自己覆盖了。这下博文是真的丢了。心塞ing。今天没事百度下自己CSDN昵称“一口仨馍”,发现其它站点爬过这篇博文。随后我复制了自己原创的博文,再次发表。感谢那些爬我博文还不署名的站点。

谢谢你全家。

源代码下载

http://download.csdn.net/detail/qq_17250009/9484160

转载于:https://www.cnblogs.com/yfceshi/p/7382534.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/391629.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

100米队伍,从队伍后到前_我们的队伍

100米队伍,从队伍后到前The last twelve months have brought us a presidential impeachment trial, the coronavirus pandemic, sweeping racial justice protests triggered by the death of George Floyd, and a critical presidential election. News coverage of these e…

idea使用 git 撤销commit

2019独角兽企业重金招聘Python工程师标准>>> 填写commit的id 就可以取消这一次的commit 转载于:https://my.oschina.net/u/3559695/blog/1596669

mongodb数据可视化_使用MongoDB实时可视化开放数据

mongodb数据可视化Using Python to connect to Taiwan Government PM2.5 open data API, and schedule to update data in real time to MongoDB — Part 2使用Python连接到台湾政府PM2.5开放数据API&#xff0c;并计划将数据实时更新到MongoDB —第2部分 目标 (Goal) This ti…

4.kafka的安装部署

为了安装过程对一些参数的理解&#xff0c;我先在这里提一下kafka一些重点概念,topic,broker,producer,consumer,message,partition,依赖于zookeeper, kafka是一种消息队列,他的服务端是由若干个broker组成的&#xff0c;broker会向zookeeper&#xff0c;producer生成者对应一个…

ecshop 前台个人中心修改侧边栏 和 侧边栏显示不全 或 导航现实不全

怎么给个人中心侧边栏加项或者减项 在模板文件default/user_menu.lbi 文件里添加或者修改,一般看到页面都会知道怎么加,怎么删,这里就不啰嗦了 添加一个栏目以后,这个地址跳的页面怎么写 这是最基本的一个包括左侧个人信息,头部导航栏 <!DOCTYPE html PUBLIC "-//W3C//…

面向对象编程思想-观察者模式

一、引言 相信猿友都大大小小经历过一些面试&#xff0c;其中有道经典题目&#xff0c;场景是猫咪叫了一声&#xff0c;老鼠跑了&#xff0c;主人被惊醒&#xff08;设计有扩展性的可加分&#xff09;。对于初学者来说&#xff0c;可能一脸懵逼&#xff0c;这啥跟啥啊是&#x…

Python:在Pandas数据框中查找缺失值

How to find Missing values in a data frame using Python/Pandas如何使用Python / Pandas查找数据框中的缺失值 介绍&#xff1a; (Introduction:) When you start working on any data science project the data you are provided is never clean. One of the most common …

监督学习-回归分析

一、数学建模概述 监督学习&#xff1a;通过已有的训练样本进行训练得到一个最优模型&#xff0c;再利用这个模型将所有的输入映射为相应的输出。监督学习根据输出数据又分为回归问题&#xff08;regression&#xff09;和分类问题&#xff08;classfication&#xff09;&#…

微服务架构技能

2019独角兽企业重金招聘Python工程师标准>>> 微服务架构技能 博客分类&#xff1a; 架构 &#xff08;StuQ 微服务技能图谱&#xff09; 2课程简介 本课程分为基础篇和高级篇两部分&#xff0c;旨在通过完整的案例&#xff0c;呈现微服务的开发、测试、构建、部署、…

Tableau Desktop认证:为什么要关心以及如何通过

Woah, Tableau!哇&#xff0c;Tableau&#xff01; By now, almost everyone’s heard of the data visualization software that brought visual analytics to the public. Its intuitive drag and drop interface makes connecting to data, creating graphs, and sharing d…

约束布局constraint-layout导入失败的解决方案 - 转

今天有同事用到了约束布局&#xff0c;但是导入我的工程出现错误 **提示错误&#xff1a; Could not find com.Android.support.constraint:constraint-layout:1.0.0-alpha3** 我网上查了一下资料&#xff0c;都说是因为我的androidStudio版本是最新的稳定版导入这个包就会报这…

算法复习:冒泡排序

思想&#xff1a;对于一个列表,每个数都是一个"气泡 "&#xff0c;数字越大表示"越重 "&#xff0c;最重的气泡移动到列表最后一位&#xff0c;冒泡排序后的结果就是“气泡”按照它们的重量依次移动到列表中它们相应的位置。 算法&#xff1a;搜索整个列表…

前端基础进阶(七):函数与函数式编程

纵观JavaScript中所有必须需要掌握的重点知识中&#xff0c;函数是我们在初学的时候最容易忽视的一个知识点。在学习的过程中&#xff0c;可能会有很多人、很多文章告诉你面向对象很重要&#xff0c;原型很重要&#xff0c;可是却很少有人告诉你&#xff0c;面向对象中所有的重…

显示与删除使用工具

右击工具菜单栏中的空白处选择自定义 在弹出的自定义菜单中选择命令选项在选择想要往里面添加工具的菜单&#xff0c;之后在选择要添加的工具 若想要删除工具栏中的某个工具&#xff0c;在打开自定义菜单后&#xff0c;按住鼠标左键拖动要删除工具到空白处 例如 转载于:https:/…

js值的拷贝和值的引用_到达P值的底部:直观的解释

js值的拷贝和值的引用介绍 (Introduction) Welcome to this lesson on calculating p-values.欢迎参加有关计算p值的课程。 Before we jump into how to calculate a p-value, it’s important to think about what the p-value is really for.在我们开始计算p值之前&#xff…

监督学习-KNN最邻近分类算法

分类&#xff08;Classification&#xff09;指的是从数据中选出已经分好类的训练集&#xff0c;在该训练集上运用数据挖掘分类的技术建立分类模型&#xff0c;从而对没有分类的数据进行分类的分析方法。 分类问题的应用场景&#xff1a;用于将事物打上一个标签&#xff0c;通常…

无监督学习-主成分分析和聚类分析

聚类分析&#xff08;cluster analysis&#xff09;是将一组研究对象分为相对同质的群组&#xff08;clusters&#xff09;的统计分析技术&#xff0c;即将观测对象的群体按照相似性和相异性进行不同群组的划分&#xff0c;划分后每个群组内部各对象相似度很高&#xff0c;而不…

struts实现分页_在TensorFlow中实现点Struts

struts实现分页If you want to get started on 3D Object Detection and more specifically on Point Pillars, I have a series of posts written on it just for that purpose. Here’s the link. Also, going through the Point Pillars paper directly will be really help…

MySQL-InnoDB索引实现

联合索引提高查询效率的原理 MySQL会为InnoDB的每个表建立聚簇索引&#xff0c;如果表有索引会建立二级索引。聚簇索引以主键建立索引&#xff0c;如果没有主键以表中的唯一键建立&#xff0c;唯一键也没会以隐式的创建一个自增的列来建立。聚簇索引和二级索引都是一个b树&…

钉钉设置jira机器人_这是当您机器学习JIRA票证时发生的事情

钉钉设置jira机器人For software developers, one of the most-debated and maybe even most-hated questions is “…and how long will it take?”. I’ve experienced those discussions myself, which oftentimes lacked precise information on the requirements. What I…