Cam APP-HAL流程追踪之demo梳理

news2024/11/17 8:49:26

一、基础知识

1、Google官网的Cam流程如下图1

Google 流程图-图1

2、Cam的预览、拍照、录像是分开的

Cam的预览、拍照、录像是各自独立的-换句话说可以不开启预览拍照或者录像–后面代码会详细介绍;市场上的成品Cam应用,打开Cam后直接打开了预览,然后可以拍照或者录视频是为了更好的排出效果;

之前电商能找出很多的针孔摄像头专门搞偷拍的-没有预览直接录视频或者拍照,由于出事太多”背后的灰色产业链!”,此类产品全部下架。

二、demo分析

1、权限

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-feature android:name="android.hardware.camera2.full" />

2、UI布局

  • 设置一个TextureView 用于 预览图片;
  • 设置一个ImageView 用于 显示拍照结果;
  • 设置一个 Button 用于触发拍照功能;

这里介绍一下 为什么用TextureView,而不用SurfaceView、SurfaceTexture、GLSurfaceView或者其他view?

  • View:显示视图,内置画布,提供图形绘制函数、触屏事件、按键事件函数等;必须在UI主线程内更新画面,速度较慢
  • SurfaceView:基于view视图进行拓展的视图类,更适合2D游戏的开发;是view的子类,类似使用双缓机制,在新的线程中更新画面,所以刷新界面速度比view快。其缺点是不能做变形和动画,也不能随屏幕的变化而变化,另外不能在其上面覆盖其它的SurfaceView
  • GLSurfaceView:是surfaceview的子类,在其基础上封装了egl环境管理,以及render线程。专用于3D游戏开发的视图,OpenGL ES专用。
  • TextureView:它也是继承自View,只能运行中硬件加速窗口。它的功能类似于SurfaceView + SurfaceTexture,它内部包含一个SurfaceTexture,它可以让Camera的数据和显示分离,比如需要做二次处理时,如Camera吧采集的数据发送给SurfaceTexture(比如做个美颜),SurfaceTexture处理后传给TextureView显示。TextureView可以做view的变形和动画。一般它是在主线程上做处理(在Android 5.0引入渲染线程后,它是在渲染线程中做的)。
  • https://source.android.google.cn/devices/graphics/arch-st?hl=zh-cn
    SurfaceTexture 是 Surface 和 OpenGL ES (GLES) 纹理的组合。SurfaceTexture 实例用于提供输出到 GLES 纹理的接口。
    SurfaceTexture 包含一个以应用为使用方的 BufferQueue 实例。当生产方将新的缓冲区排入队列时,onFrameAvailable() 回调会通知应用。然后,应用调用 updateTexImage(),这会释放先前占用的缓冲区,从队列中获取新缓冲区并执行 EGL 调用,从而使 GLES 可将此缓冲区作为外部纹理使用。

3、代码

总体流程本来打算自己画的,偶然看到了这个
流程图就直接拿来作参考了原图地址

1、 view初始化

       textureView = findViewById(R.id.texture_view_camera2);
       imageViewThumb = findViewById(R.id.thumb_iv);
       findViewById(R.id.shutter_button).setOnClickListener(this);

2、获取CameraManger、给TextureView设置状态监听

        cameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
        textureView.setSurfaceTextureListener(this);

3、获取当前机器支持的size的list 然后找到设定比例中的最大值 设置为默认size(一般都封装成util)

    private int firstWidthRatio = 3;//画幅比例宽度比值分子
    private int firstHeightRatio = 4;//画幅比例高度比值分母
    private int firstDeviceWidth;//设备宽度
    private int firstDeviceHeight;//设备高度
    private Size previewSizeStandard;//预览尺寸标准
    private Size photoSizeStandard;//照片尺寸标准


    private void initPictureSize() {
        try {
            for (String camId : cameraManager.getCameraIdList()) {
                cameraId = camId;
                CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraId);
                StreamConfigurationMap streamConfigurationMap = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);//输出流配置
                Size[] previewSizeList = streamConfigurationMap.getOutputSizes(SurfaceTexture.class);//获取当前相机支持的所有尺寸列表
                previewSizeStandard = SizeUtil.getSuitableSize(previewSizeList, firstWidthRatio, firstHeightRatio, firstDeviceWidth, firstDeviceHeight);

                /*根据画幅比例和设备宽高在支持的所有尺寸列表中过滤出尺寸标准用作照片(ImageReader)(如果获取了多个照片尺寸,选择尺寸最大的(排序))(在设置ImageReader和缩略图的时候使用)(尚未生效)*/
                Size[] photoSizeList = streamConfigurationMap.getOutputSizes(ImageFormat.JPEG);//获取兼容JPEG格式的所有尺寸列表
                photoSizeStandard = SizeUtil.getSuitableSizePhoto(photoSizeList, firstWidthRatio, firstHeightRatio, firstDeviceWidth, firstDeviceHeight);
                /*
                 * 获取到最佳预览尺寸的标准后,我们就可以修改自定义的TextureView的尺寸,避免画面拉伸的情况
                 */
                textureView.setAspectRation(previewSizeStandard.getHeight(), previewSizeStandard.getWidth());
                Log.d(TAG, "previewSizeStandard:=" + previewSizeStandard);
            }
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }
    
    /* 返回采样率InSampleSize(int)
     * 不断将尺寸减半,直到小于缩略图的尺寸*/
    public static int getInSampleSize(Size picSize, int reqHeight, int reqWidth) {

        int height = picSize.getWidth();
        int width = picSize.getHeight();
        int inSampleSize = 1;
        if (height > reqHeight || width > reqWidth) {
            int halfHeight = height / 2;
            int halfWidth = width / 2;
            while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) {
                inSampleSize *= 2;
            }
        }
        return inSampleSize;
    }

4、初始化ImageReader

    private void initImageReader() {
        imageReader = ImageReader.newInstance(photoSizeStandard.getWidth(), photoSizeStandard.getHeight(), ImageFormat.JPEG, 2);
        imageReader.setOnImageAvailableListener(this, null);
        Log.d(TAG, "setOnImageAvailableListener X");
        captureSurface = imageReader.getSurface();
    }

5、打开相机和各种回调

    //    申请相机权限
    private void checkPermission() {
        Log.d(TAG, "checkPermission");
        // 检查是否申请了权限
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
            if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)) {
                openCamera();
            } else {
                ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, 1);
            }
        }
    }


    private void openCamera() {
        Log.d(TAG, "openCamera");
        // 4 检查相机权限
        checkPermission();
        // 5 开启相机(传入:要开启的相机ID,和状态回调对象)
        try {
            initCamStateCallBack();
            cameraManager.openCamera(cameraId, camStateCallback, mHandler);
            Log.d(TAG, "openCamera X");
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    private void initCamStateCallBack() {
        camStateCallback = new CameraDevice.StateCallback() {
            @Override
            public void onOpened(@NonNull CameraDevice camera) {
                Log.d(TAG, "openCamera onOpened");
                cameraDevice = camera;
                try {
                    SurfaceTexture sTexture = textureView.getSurfaceTexture();
                    sTexture.setDefaultBufferSize(previewSizeStandard.getWidth(), previewSizeStandard.getHeight());
                    previewSurface = new Surface(sTexture);
                    Log.d(TAG, "previewSizeStandard:=" + previewSizeStandard);
                    // 2.2 构建请求对象(设置预览参数,和输出对象)
                    previewRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); // 设置参数:预览
                    previewRequestBuilder.addTarget(previewSurface); // 设置参数:目标容器
                    captureRequest = previewRequestBuilder.build();
                    CamCaptureSessionStateCallback();
                    // 2.3 创建会话
                 //如果些成cameraDevice.createCaptureSession(Arrays.asList(previewSurface), camCaptureSessionStateCallback, mHandler);虽然也不会报错,但不会走setOnImageAvailableListener的回调
                    cameraDevice.createCaptureSession(Arrays.asList(previewSurface, imageReader.getSurface()), camCaptureSessionStateCallback, mHandler);
                } catch (CameraAccessException e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void onDisconnected(@NonNull CameraDevice camera) {
                Log.d(TAG, "openCamera onOpened");
            }

            @Override
            public void onError(@NonNull CameraDevice camera, int error) {
                Log.d(TAG, "openCamera onError");
            }
        };
    }
    private void CamCaptureSessionStateCallback() {
        camCaptureSessionStateCallback = new CameraCaptureSession.StateCallback() {
            @Override  //2.3.1  会话准备好了,在里面创建 预览或拍照请求
            public void onConfigured(@NonNull CameraCaptureSession session) {
                cameraCaptureSession = session;
                //预览代码,上文中说到,可以不开启预览拍照,如果把此 session.setRepeatingRequest(captureRequest, null, null);注释掉就不会开启预览,且不影响后续拍照
//                try {
//                    Log.d(TAG, "CamCaptureSessionStateCallback onConfigured");
//                    session.setRepeatingRequest(captureRequest, null, null);
//                } catch (CameraAccessException e) {
//                    e.printStackTrace();
//                }
            }

            @Override
            public void onConfigureFailed(@NonNull CameraCaptureSession session) {
                Log.d(TAG, "CamCaptureSessionStateCallback onConfigureFailed");
            }
        };
    }


    //**********************************TextureView.SurfaceTextureListener*****start*****************************
    @Override
    public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surface, int width, int height) {
        Log.d(TAG, "onSurfaceTextureAvailable ");
        previewSurface = new Surface(textureView.getSurfaceTexture());
        openCamera();
    }

    @Override
    public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surface, int width, int height) {
        Log.d(TAG, "onSurfaceTextureSizeChanged ");
    }

    @Override
    public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) {
        Log.d(TAG, "onSurfaceTextureDestroyed ");
        return false;
    }

    @Override
    public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) {
//        Log.d(TAG,"onSurfaceTextureUpdated ");
    }

    //**********************************TextureView.SurfaceTextureListener*****end*****************************


6、按快门拍照

    @Override
    public void onClick(View v) {

        switch (v.getId()) {
            case R.id.shutter_button:
                Log.d(TAG, "onClick shutter_button");

                try {
                    configureCaptureBeforeCapture();
                    cameraCaptureSession.stopRepeating();
                    cameraCaptureSession.capture(captureRequestBuild.build(), captureCallback, mHandler);
                } catch (CameraAccessException e) {
                    e.printStackTrace();
                }
                break;
        }
    }

    private void configureCaptureBeforeCapture() {
        try {
            captureRequestBuild = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
        captureRequestBuild.set(CaptureRequest.JPEG_ORIENTATION, 90);
        captureRequestBuild.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_START);
        //B4.2 配置request的参数 的目标对象
        SurfaceTexture sTexture = textureView.getSurfaceTexture();
        sTexture.setDefaultBufferSize(previewSizeStandard.getWidth(), previewSizeStandard.getHeight());
        captureSurface = new Surface(sTexture);
//        captureRequestBuild.addTarget(captureSurface);//把capture的图片渲染到 TextureView 上  ,如果注释掉此demo不会把图片显示到 TextureView ,
//        captureRequestBuild.addTarget(imageReader.getSurface());//ImageAvailableListener 回调(保存图片用的),注释掉不会走ImageAvailableListener的回调逻辑

    }




    @Override
    public void onImageAvailable(ImageReader reader) {
        Log.d(TAG, "setOnImageAvailableListener onImageAvailable");
        Image image = reader.acquireLatestImage();
        ByteBuffer buffer = image.getPlanes()[0].getBuffer();
        int length = buffer.remaining();
        byte[] bytes = new byte[length];
        buffer.get(bytes);
        image.close();
        BitmapFactory.Options sampler = new BitmapFactory.Options();
        //设置采样率
        sampler.inSampleSize = getInSampleSize(photoSizeStandard, 60, 60);//(缩略图尺寸px转换为int)
        //将byte[]转换为bitmap
        bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, sampler);
        //B2.2 显示图片
        imageViewThumb.setImageBitmap(bitmap);
    }







    private CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback() {
        @Override
        public void onCaptureStarted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, long timestamp, long frameNumber) {
            super.onCaptureStarted(session, request, timestamp, frameNumber);
            Log.d(TAG, "onCaptureStarted");
        }

        @Override
        public void onCaptureProgressed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureResult partialResult) {
            super.onCaptureProgressed(session, request, partialResult);
            Log.d(TAG, "onCaptureProgressed");
        }

        @Override
        public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
            super.onCaptureCompleted(session, request, result);
            Log.d(TAG, "onCaptureCompleted");
            //拍照完成后是否继续预览
//            try {
//                cameraCaptureSession.setRepeatingRequest(previewRequestBuilder.build(),null,null);
//            } catch (CameraAccessException e) {
//                e.printStackTrace();
//            }
        }

        @Override
        public void onCaptureFailed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureFailure failure) {
            super.onCaptureFailed(session, request, failure);
            Log.d(TAG, "onCaptureFailed");
        }

        @Override
        public void onCaptureSequenceCompleted(@NonNull CameraCaptureSession session, int sequenceId, long frameNumber) {
            super.onCaptureSequenceCompleted(session, sequenceId, frameNumber);
            Log.d(TAG, "onCaptureSequenceCompleted");
        }

        @Override
        public void onCaptureSequenceAborted(@NonNull CameraCaptureSession session, int sequenceId) {
            super.onCaptureSequenceAborted(session, sequenceId);
            Log.d(TAG, "onCaptureSequenceAborted");
        }

        @Override
        public void onCaptureBufferLost(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull Surface target, long frameNumber) {
            super.onCaptureBufferLost(session, request, target, frameNumber);
            Log.d(TAG, "onCaptureBufferLost");
        }
    };

7、验证预览、拍照是独立的,
方式一:

正常预览时拍照:

05-12 11:43:30.011  8024  8024 D MainActivity: onClick shutter_button
05-12 11:43:30.363  8024  8054 D MainActivity: onCaptureStarted
05-12 11:43:30.376  8024  8054 D MainActivity: onCaptureCompleted
05-12 11:43:30.377  8024  8054 D MainActivity: onCaptureSequenceCompleted
05-12 11:43:30.400  8024  8024 D MainActivity: setOnImageAvailableListener onImageAvailable

当没有onImageAvailable 对调时 不设置 captureRequestBuild.addTarget(imageReader.getSurface());

:~$ alog "runtime|MainActi" -i
05-12 10:36:47.036  6848  6848 D MainActivity: onClick shutter_button
05-12 10:36:47.089  6848  6875 D MainActivity: onCaptureStarted
05-12 10:36:47.093  6848  6875 D MainActivity: onCaptureCompleted
05-12 10:36:47.093  6848  6875 D MainActivity: onCaptureSequenceCompleted

没有 MainActivity: setOnImageAvailableListener onImageAvailable

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

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

相关文章

【数据结构】线性表之栈、队列

前言 前面两篇文章讲述了关于线性表中的顺序表与链表&#xff0c;这篇文章继续讲述线性表中的栈和队列。 这里讲述的两种线性表与前面的线性表不同&#xff0c;只允许在一端入数据&#xff0c;一段出数据&#xff0c;详细内容请看下面的文章。 顺序表与链表两篇文章的链接&…

最新社区论坛小程序源码 含流量主功能+前后端+部署搭建教程

分享一个社区论坛小程序源码&#xff0c;含完整前后端代码包和详细的部署搭建教程&#xff0c;做地方运营很适合&#xff0c;集成了流量主功能&#xff0c;一键轻松开启。 系统功能一览&#xff1a; 广场管理&#xff1a;广场列表&#xff0c;圈子列表&#xff0c;圈子审核 帖…

CE作业(4)

一、准备前提 服务端 客户端 ; 关闭防火墙systemctl stop firewalld 关闭selinux setenforce 0 提供DNS服务的软件叫bind&#xff0c;服务名是named 一、正向解析&#xff1a; 1.装包 yum install bind -y 2.配置服务 vim /etc/named.conf #监听53号端口 #访问的是本…

​数据库原理及应用上机(实验三 SQL数据查询)

✨作者&#xff1a;命运之光 ✨专栏&#xff1a;数据库原理及应用上机实验 ​ 目录 ✨一、实验目的和要求 ✨二、实验内容及步骤 ✨三、实验结果 ✨四、附加练习 ✨五、实验总结 &#x1f353;&#x1f353;前言&#xff1a; 数据库原理及应用上机实验报告的一个简单整理…

用别人的钱创咖啡的业,戴威与陆正耀殊途同归?

文 | 新熔财经 作者 | 和花 近四年来&#xff0c;ofo退押一事没有出现任何明显的转机&#xff0c;但ofo创始人戴威却已经在海外大张旗鼓地开启了名为About Time Coffee的新创业项目。 当“ofo创始人戴威再次创业”的消息登上微博热搜&#xff0c;收获的几乎都是“还钱”的征…

字符串按规则生成字典

带数字的字符串以数字为key倒序生成字典&#xff0c;字符串列表按其元素索引为key倒序生成字典。 【学习的细节是欢悦的历程】 Python 官网&#xff1a;https://www.python.org/ Free&#xff1a;大咖免费“圣经”教程《 python 完全自学教程》&#xff0c;不仅仅是基础那么简…

实验室云检验信息系统(云LIS源码)

一、区域云LIS系统概述&#xff1a; 区域云LIS平台源码&#xff0c;系统完全采用B/S架构模式&#xff0c;扩展性强。整个系统的运行基于WEB层面&#xff0c;只需要在对应的工作台安装一个浏览器软件有外网即可访问。 云LIS系统为实验室服务对象提供检验申请、采集标本、结果查…

【IDEA使用码云教程】

IDEA使用码云教程 一、下载、安装git二、配置Gitee插件三、克隆项目四、上传项目五、推送项目六、更新项目 一、下载、安装git 1.打开git官网&#xff0c;选择你的操作系统 官网下载地址&#xff1a;https://git-scm.com/downloads 2.根据你的系统位数选择相应的版本下载 系统…

frp+nginx+xposed搭建xp模块集群

frpcnginxxposed搭建xp模块集群 前言实现逻辑配置内网穿透实现负载均衡 前言 为了能够稳定的采集一些app的详情页数据&#xff0c;就得借助xposed&#xff0c;xposed跟NanoHTTPD配合使用就可以在手机端开启接口服务&#xff0c;直接调用手机端的接口就能获取我们想要的数据&am…

【机器学习】线性模型

文章目录 第三章&#xff1a;线性模型一、线性回归模型1.1、线性回归模型1.2、求解线性回归模型&#xff08;时刻要分清维度&#xff09;1.3、多输出线性回归模型 二、线性分类模型2.1、判别函数2.2、概率判别模型2.3、概率生成模型 第三章&#xff1a;线性模型 一、线性回归模…

怎么把视频压缩到500m以下?

如何把视频压缩到500m以下&#xff1f;视频文件通常是非常大的&#xff0c;特别是高清视频或超高清视频&#xff0c;因此压缩可以帮助将视频文件大小减小&#xff0c;在有限的存储空间中存储更多的视频文件。较大的视频文件在上传和下载时需要较长时间&#xff0c;而压缩视频文…

Flink学习——Flink中的时间语义和窗口

一、时间语义 1.1 为什么会出现时间语义&#xff1f; flink是一个大数据处理引擎&#xff0c;它的最大特点就是分布式。每一个机器都有自己的时间&#xff0c;那么集群当中的时间应该以什么为准呢&#xff1f; 比如&#xff1a;我们希望统计8-9点的数据时&#xff0c;对并行任…

使用RSD从DEM数据创建用户高程数据层

李国春 SRTM90和Aster DEM的V2/V3是比较常用的免费共享高程数据。用户下载好以后应用到自己的项目时&#xff0c;经常会需要进行拼接合成和投影重采样等。RSD提供了一种创建自己项目的高程数据的方法。 一. 高程图像生成方法 在自己的项目中&#xff0c;选择图1的菜单。 图1…

使用LabVIEW AI视觉工具包快速实现SIFT特征检测(含源码)

‍‍&#x1f3e1;博客主页&#xff1a; virobotics的CSDN博客&#xff1a;LabVIEW深度学习、人工智能博主 &#x1f384;所属专栏&#xff1a;『LabVIEW深度学习实战』 &#x1f37b;上期文章&#xff1a; 使用LabVIEW AI视觉工具包快速实现霍夫圆和霍夫直线检测&#xff08;含…

Jmeter事务控制器聚合报告

Jmeter 事务控制器。 在Jmeter中&#xff0c;默认一个取样器就是一个事务事务控制器控制其子集取样器&#xff0c;合并为一个事务 添加&#xff1a;逻辑控制器/Logic Controller -> 事务控制器/Transaction Controller TPS: 服务器每秒处理的事务数在事务控制器下添加多个…

海康威视iVMS综合安防系统任意文件上传漏洞复现(0day)

0x01 产品简介 海康威视iVMS集中监控应用管理平台&#xff0c;是以安全防范业务应用为导向&#xff0c;以视频图像应用为基础手段&#xff0c;综合视频监控、联网报警、智能分析、运维管理等多种安全防范应用系统&#xff0c;构建的多业务应用综合管理平台。 0x02 漏洞概述 海…

利用CX-ONE搭建omron PLC仿真环境

目录 1 安装参考 2 CX-Simulator 2.1 打开软件 2.2 选择PLC配置文件存放位置 2.3 选择PLC类型 2.4 PLC Unit全部选择 2.5 设置FINS通讯 2.6 设置串口通讯 2.7 建立连接 3 CX-Programmer 3.1 新建工程 3.2 设置PLC型号 3.3 设置网络类型 3.4 设置串口通讯 3.5 设…

chatgpt在哪用?详谈一下gpt的各方面

ChatGPT是一种人工智能技术&#xff0c;它可以通过自然语言交互回答各种问题。这种技术已经被广泛应用于各个领域和场景中&#xff0c;帮助人们更好地获取知识和信息。那么&#xff0c;ChatGPT在哪里使用呢&#xff1f;下面我们来探讨一下。 一.chatgpt在哪用 打开任意的浏览器…

MSP 现场服务管理

什么是现场服务管理 现场服务管理 &#xff08;FSM&#xff09; 是确保有效规划、安排和执行现场服务活动所遵循的做法。它涉及有效利用资源&#xff0c;如人员、技术和材料&#xff0c;以满足客户的服务期限和标准。FSM还涉及现场技术人员&#xff0c;服务交付经理和其他利益…

救命稻草!阿里P8耗时5月打造的架构师速成手册,千金难求

前言 软件行业技术开发从业人员众多&#xff0c;很多程序员朋友在从业2-3年后都会进入一个迷茫期&#xff0c;面对个人发展的瓶颈。即如何从普通开发人员转型成高层次的系统架构师和技术管理人员。对程序员来说&#xff0c;架构师的薪资比较充满诱惑&#xff0c;年薪四五十万对…