Android集成海康威视摄像头SDK:实现实时预览与后台静默抓拍完整指南 1. 项目概述与核心价值最近在做一个智慧园区管理的项目其中有一个核心需求就是要在Android端集成海康威视的摄像头实现实时预览和后台静默抓拍。说实话第一次接到这个需求时我脑子里也是一团乱麻。海康的SDK文档虽然全面但内容庞杂新手直接上手很容易在设备网络配置、预览流解码、抓拍时机控制这些环节上踩坑。网上能找到的教程要么年代久远SDK接口已经大变样要么就是只讲了预览没提抓拍或者代码片段零散根本跑不起来。经过几轮折腾和项目实战我总算梳理出了一套稳定、高效的集成方案。这篇文章我就把自己从零开始在Android应用里成功对接海康威视摄像头SDK实现实时预览和无预览后台抓拍的完整过程、核心代码以及避坑心得毫无保留地分享出来。无论你是要做安防监控、智能门禁还是任何需要远程查看或定时抓图的App这篇2025年基于最新SDK的实践指南都能让你少走至少一周的弯路。文末会附上实际运行的效果图让你一目了然。2. 整体设计与思路拆解在动手写代码之前理清整体思路和架构选择至关重要。海康SDK提供了强大的能力但也意味着更高的复杂度和对正确调用顺序的严苛要求。2.1 技术方案选型与SDK准备海康威视为移动端Android/iOS提供了专门的“设备网络SDK”HCNetSDK。我们的所有功能都基于此SDK开发。首先你需要去海康威视官方开放平台下载最新的SDK包。这里有个关键点一定要根据你的摄像头设备型号和固件版本选择匹配的SDK版本。用错了版本轻则功能异常重则直接导致崩溃。下载的SDK包通常包含*.jar文件Java层的接口封装。*.so文件一系列ARM架构armeabi-v7a, arm64-v8a的动态库这是核心包含了音视频编解码、网络通信等所有底层逻辑。文档API参考手册和开发指南虽然读起来有点枯燥但遇到问题时它是终极字典。为什么选择集成原生SDK而不是用RTSP流这是一个常见的架构抉择。直接拉取摄像头的RTSP流使用VLC、ffmpeg等看似更“通用”和“轻量”但在实际企业级应用中问题很多功能受限你无法使用SDK提供的丰富管理功能如云台控制、报警订阅、设备参数配置等。稳定性差需要自己处理网络重连、码流解析、解码渲染复杂度和维护成本极高。安全性弱SDK提供了更安全的加密通信和认证流程。 因此对于需要稳定、多功能集成的商业项目直接使用海康官方SDK是更专业可靠的选择。2.2 核心流程与模块划分整个功能可以划分为几个清晰的模块它们之间的调用有严格的先后顺序下图展示了核心流程与模块间的关系flowchart TD A[初始化SDK] -- B[注册设备br获取UserID] B -- C{功能选择} C -- 实时预览 -- D[启动预览br获取RealHandle] D -- E[解码与渲染brSurfaceView/TextureView] E -- F[用户交互观看] C -- 无预览抓拍 -- G[设备抓拍br传入UserID与参数] G -- H[保存图片至本地] F -- I[停止预览与注销] H -- I I -- J[清理SDK资源]如图所示整个流程始于SDK的初始化这是所有操作的基石。成功初始化并注册设备后功能路径开始分叉。对于实时预览核心在于获取预览句柄RealHandle并将视频流解码渲染到合适的视图组件上这是一个持续的、需要用户交互的过程。而对于无预览抓拍其核心诉求是在用户无感知的情况下直接指令设备抓取一张当前时刻的快照并保存到本地。这两个功能共享设备注册信息但在执行路径和资源占用上完全不同。无论走哪条路径最后都必须严格按照相反的顺序释放资源这是避免内存泄漏和程序崩溃的关键。3. 核心细节解析与实操要点理解了整体流程我们深入每个环节的魔鬼细节。这些地方最容易出错也是决定功能稳定性的关键。3.1 SDK初始化与资源管理这是第一步也是最重要的一步做不好后面全是空中楼阁。1. 导入SDK文件将下载的*.jar文件放入 app 模块的libs目录并在build.gradle中添加依赖dependencies { implementation fileTree(dir: libs, include: [*.jar]) }将*.so文件按照ABI分类放入src/main/jniLibs目录下对应的文件夹中例如arm64-v8a,armeabi-v7a。现在主流设备都是64位务必确保arm64-v8a目录下的库文件齐全。2. 初始化与清理在Application的onCreate()或主Activity的早期进行初始化。这是一个阻塞调用务必放在子线程。// 初始化网络SDK boolean initSuccess HCNetSDK.getInstance().NET_DVR_Init(); if (!initSuccess) { int errorCode HCNetSDK.getInstance().NET_DVR_GetLastError(); Log.e(TAG, SDK初始化失败错误码: errorCode); // 根据错误码排查问题常见原因是so库未正确加载 } // 设置SDK日志路径方便排查问题可选但强烈建议 HCNetSDK.getInstance().NET_DVR_SetLogToFile(3, /sdcard/HCNetSDK/log/, true);对应的在程序退出时必须调用清理函数Override protected void onDestroy() { // 先停止所有预览、注销所有设备 // ... // 最后清理SDK HCNetSDK.getInstance().NET_DVR_Cleanup(); super.onDestroy(); }注意NET_DVR_Cleanup()必须在所有其他SDK功能调用结束后执行且一个进程只需初始化一次清理一次。反复初始化-清理可能导致不可预知的问题。3.2 设备注册与参数配置注册设备相当于和摄像头建立了一条安全通道后续所有操作都依赖于这次注册返回的“用户ID”lUserID。// 1. 准备设备参数 HCNetSDK.NET_DVR_DEVICEINFO_V30 deviceInfo new HCNetSDK.NET_DVR_DEVICEINFO_V30(); HCNetSDK.NET_DVR_USER_LOGIN_INFO loginInfo new HCNetSDK.NET_DVR_USER_LOGIN_INFO(); loginInfo.sDeviceAddress new byte[HCNetSDK.NET_DVR_DEV_ADDRESS_MAX_LEN]; System.arraycopy(192.168.1.64.getBytes(), 0, loginInfo.sDeviceAddress, 0, 192.168.1.64.getBytes().length); // 设备IP loginInfo.sUserName new byte[HCNetSDK.NET_DVR_LOGIN_USERNAME_MAX_LEN]; System.arraycopy(admin.getBytes(), 0, loginInfo.sUserName, 0, admin.getBytes().length); // 用户名 loginInfo.sPassword new byte[HCNetSDK.NET_DVR_LOGIN_PASSWD_MAX_LEN]; System.arraycopy(12345.getBytes(), 0, loginInfo.sPassword, 0, 12345.getBytes().length); // 密码 loginInfo.wPort 8000; // 设备服务端口默认8000 loginInfo.bUseAsynLogin false; // 同步登录 // 2. 执行注册 int lUserID HCNetSDK.getInstance().NET_DVR_Login_V30(loginInfo, deviceInfo); if (lUserID 0) { int errorCode HCNetSDK.getInstance().NET_DVR_GetLastError(); Log.e(TAG, 设备注册失败错误码: errorCode); // 常见错误码1-用户名密码错误2-权限不足3-IP地址或端口错误4-设备不在线 } else { Log.i(TAG, 设备注册成功UserID: lUserID); // 保存这个lUserID后续预览、抓拍都要用 }实操心得网络环境确保手机和设备在同一个局域网或者设备具有公网IP并做好了端口映射。错误码海康SDK的错误码是排查问题的金钥匙。一定要在每次调用后检查返回值失败时通过NET_DVR_GetLastError()获取错误码并查阅文档。字节数组赋值上面那种System.arraycopy的赋值方式是SDK要求的因为底层是C结构体直接 “admin”.getBytes()会导致内存问题。3.3 实时预览的实现与优化预览是核心功能涉及流媒体的接收、解码和渲染。1. 启动预览你需要一个SurfaceView或TextureView来显示画面。这里以SurfaceView为例。// 假设我们已经有了一个 SurfaceView 和它的 SurfaceHolder SurfaceHolder holder mSurfaceView.getHolder(); holder.addCallback(this); // Activity实现SurfaceHolder.Callback // 在 surfaceCreated 回调中启动预览 Override public void surfaceCreated(SurfaceHolder holder) { HCNetSDK.NET_DVR_PREVIEWINFO previewInfo new HCNetSDK.NET_DVR_PREVIEWINFO(); previewInfo.lChannel 1; // 通道号通常从1开始 previewInfo.dwStreamType 0; // 0-主码流高清1-子码流流畅 previewInfo.dwLinkMode 0; // 0-TCP1-UDP2-多播。TCP最稳定。 previewInfo.bBlocked 1; // 0-非阻塞取流1-阻塞取流。建议用1稳定。 previewInfo.hPlayWnd holder.getSurface(); // 关键将Surface传入SDK // 启动预览 int lRealHandle HCNetSDK.getInstance().NET_DVR_RealPlay_V40(lUserID, previewInfo, null); if (lRealHandle 0) { // 预览失败处理错误 } else { // 预览成功保存 lRealHandle 用于后续停止预览 } }2. 解码与渲染海康SDK内部已经完成了硬解码并将图像直接渲染到我们传入的Surface上。所以对于开发者来说启动预览后如果参数正确画面就应该能出来了。这比我们自己用MediaCodec解码H.264流要简单得多。3. 停止预览在离开页面或需要停止时必须停止预览。if (lRealHandle 0) { boolean stopSuccess HCNetSDK.getInstance().NET_DVR_StopRealPlay(lRealHandle); lRealHandle -1; // 重置句柄 }避坑指南SurfaceView生命周期务必在surfaceCreated之后才开始预览在surfaceDestroyed之前停止预览。否则会出现“画面黑屏”或“程序崩溃”。码流选择主码流dwStreamType0清晰度高但带宽占用大在移动网络下可能卡顿。子码流dwStreamType1更流畅。可以根据网络状态动态切换。线程问题SDK的回调如报警回调可能发生在非UI线程如果需要更新UI记得用runOnUiThread或Handler。3.4 无预览后台抓拍的实现这是很多监控类App的“ silent”功能需求比如定时巡检、异常触发抓拍等。核心是直接命令设备抓拍一张图片并保存到本地完全不需要启动预览节省资源和流量。/** * 执行设备抓拍 * param lUserID 注册设备返回的用户ID * param channel 通道号 * param picPath 图片保存的完整路径如 /sdcard/DCIM/Camera/20250415_102030.jpg * return 是否成功 */ public boolean capturePicture(int lUserID, int channel, String picPath) { HCNetSDK.NET_DVR_JPEGPARA jpegPara new HCNetSDK.NET_DVR_JPEGPARA(); jpegPara.wPicSize 0xff; // 0xff 表示抓拍图片大小与设备当前编码分辨率一致 jpegPara.wPicQuality 2; // 图片质量0-最好1-较好2-一般。值越小图片质量越高体积越大。 // 关键调用NET_DVR_CaptureJPEGPicture_NEW boolean success HCNetSDK.getInstance().NET_DVR_CaptureJPEGPicture_NEW( lUserID, // 设备用户ID channel, // 通道号 jpegPara, // 图片参数 picPath // 保存路径 ); if (!success) { int errorCode HCNetSDK.getInstance().NET_DVR_GetLastError(); Log.e(TAG, 抓拍失败错误码: errorCode); // 常见错误设备未注册、通道号错误、存储路径无权限 } else { Log.i(TAG, 抓拍成功图片已保存至: picPath); // 这里可以通知系统相册扫描新图片如果需要 // MediaScannerConnection.scanFile(...) } return success; }核心要点解析无需预览句柄这个函数只需要lUserID和channel与预览功能 (lRealHandle) 完全独立。这意味着你可以在一个后台Service里定时调用这个方法进行抓拍而用户界面没有任何感知。图片参数NET_DVR_JPEGPARA结构体控制图片大小和质量。wPicSize0xff是最常用的设置表示使用设备当前的主码流分辨率抓拍。你也可以指定具体分辨率如0x05代表1280x720但前提是设备支持。保存路径确保你的App有WRITE_EXTERNAL_STORAGE权限针对Android旧版本或者使用MediaStoreAPI针对Android 10及以上。路径要具体到文件名且后缀最好是.jpg。性能与时机设备抓拍不是瞬间完成的它需要设备编码一帧JPEG并传回通常有几百毫秒到1秒的延迟。不要在循环中高频调用避免给设备造成压力。4. 实操过程与核心环节实现让我们把上面的代码片段组合起来看看在一个典型的Activity中如何组织。4.1 项目依赖与权限配置build.gradle (Module: app)android { defaultConfig { ndk { abiFilters armeabi-v7a, arm64-v8a // 根据你拥有的.so文件决定 } } sourceSets { main { jniLibs.srcDirs [src/main/jniLibs] } } } dependencies { implementation fileTree(dir: libs, include: [*.jar]) // ... 其他依赖 }AndroidManifest.xmluses-permission android:nameandroid.permission.INTERNET / uses-permission android:nameandroid.permission.ACCESS_NETWORK_STATE / uses-permission android:nameandroid.permission.ACCESS_WIFI_STATE / !-- 存储权限用于保存抓拍图片 -- uses-permission android:nameandroid.permission.WRITE_EXTERNAL_STORAGE android:maxSdkVersion28 / !-- 针对Android 9及以下 -- !-- 如果针对Android 10需要使用MediaStore API动态申请MANAGE_EXTERNAL_STORAGE或使用App专属目录 -- uses-feature android:nameandroid.hardware.camera android:requiredfalse / !-- 声明不需要摄像头硬件因为用的是网络摄像头 --4.2 核心Activity代码实现这里展示一个精简但功能完整的预览抓拍Activity框架。public class CameraPreviewActivity extends AppCompatActivity implements SurfaceHolder.Callback { private static final String TAG CameraPreview; private SurfaceView mSurfaceView; private SurfaceHolder mHolder; private Button mBtnCapture; private int mUserID -1; // 设备用户ID private int mRealHandle -1; // 预览句柄 private String mDeviceIp 192.168.1.64; private String mUsername admin; private String mPassword 12345; Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_preview); mSurfaceView findViewById(R.id.surface_view); mBtnCapture findViewById(R.id.btn_capture); mHolder mSurfaceView.getHolder(); mHolder.addCallback(this); // 初始化SDK实际应在Application中做此处仅为演示 new Thread(() - { boolean initSuccess HCNetSDK.getInstance().NET_DVR_Init(); runOnUiThread(() - { if (initSuccess) { Toast.makeText(this, SDK初始化成功, Toast.LENGTH_SHORT).show(); loginDevice(); // 初始化成功后登录设备 } else { Toast.makeText(this, SDK初始化失败, Toast.LENGTH_SHORT).show(); } }); }).start(); mBtnCapture.setOnClickListener(v - capturePicture()); } // 设备登录 private void loginDevice() { new Thread(() - { HCNetSDK.NET_DVR_DEVICEINFO_V30 deviceInfo new HCNetSDK.NET_DVR_DEVICEINFO_V30(); HCNetSDK.NET_DVR_USER_LOGIN_INFO loginInfo new HCNetSDK.NET_DVR_USER_LOGIN_INFO(); // ... 填充loginInfo参数参考3.2节 mUserID HCNetSDK.getInstance().NET_DVR_Login_V30(loginInfo, deviceInfo); runOnUiThread(() - { if (mUserID 0) { Toast.makeText(this, 登录失败错误码 HCNetSDK.getInstance().NET_DVR_GetLastError(), Toast.LENGTH_LONG).show(); } else { Toast.makeText(this, 设备登录成功, Toast.LENGTH_SHORT).show(); // 登录成功Surface创建后会自动开始预览 } }); }).start(); } // SurfaceHolder.Callback 方法 Override public void surfaceCreated(SurfaceHolder holder) { Log.i(TAG, Surface Created); if (mUserID 0) { // 确保设备已登录 startPreview(); } } Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {} Override public void surfaceDestroyed(SurfaceHolder holder) { Log.i(TAG, Surface Destroyed); stopPreview(); } // 开始预览 private void startPreview() { if (mUserID 0 || mHolder.getSurface() null) return; HCNetSDK.NET_DVR_PREVIEWINFO previewInfo new HCNetSDK.NET_DVR_PREVIEWINFO(); previewInfo.lChannel 1; previewInfo.dwStreamType 0; previewInfo.dwLinkMode 0; previewInfo.bBlocked 1; previewInfo.hPlayWnd mHolder.getSurface(); mRealHandle HCNetSDK.getInstance().NET_DVR_RealPlay_V40(mUserID, previewInfo, null); if (mRealHandle 0) { Toast.makeText(this, 预览失败错误码 HCNetSDK.getInstance().NET_DVR_GetLastError(), Toast.LENGTH_LONG).show(); } } // 停止预览 private void stopPreview() { if (mRealHandle 0) { HCNetSDK.getInstance().NET_DVR_StopRealPlay(mRealHandle); mRealHandle -1; } } // 执行抓拍 private void capturePicture() { if (mUserID 0) { Toast.makeText(this, 请先登录设备, Toast.LENGTH_SHORT).show(); return; } new Thread(() - { String timeStamp new SimpleDateFormat(yyyyMMdd_HHmmss, Locale.getDefault()).format(new Date()); String picName CAPTURE_ timeStamp .jpg; // 保存到公共图片目录需处理Android版本兼容性 File picFile new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), picName); HCNetSDK.NET_DVR_JPEGPARA jpegPara new HCNetSDK.NET_DVR_JPEGPARA(); jpegPara.wPicSize 0xff; jpegPara.wPicQuality 2; boolean success HCNetSDK.getInstance().NET_DVR_CaptureJPEGPicture_NEW(mUserID, 1, jpegPara, picFile.getAbsolutePath()); runOnUiThread(() - { if (success) { Toast.makeText(this, 抓拍成功文件已保存, Toast.LENGTH_SHORT).show(); // 可以在这里显示一个缩略图或通知 } else { Toast.makeText(this, 抓拍失败, Toast.LENGTH_SHORT).show(); } }); }).start(); } Override protected void onDestroy() { stopPreview(); if (mUserID 0) { HCNetSDK.getInstance().NET_DVR_Logout(mUserID); mUserID -1; } // SDK的清理建议在Application中统一做此处仅作资源释放 super.onDestroy(); } }4.3 布局文件示例activity_preview.xml?xml version1.0 encodingutf-8? LinearLayout xmlns:androidhttp://schemas.android.com/apk/res/android android:layout_widthmatch_parent android:layout_heightmatch_parent android:orientationvertical SurfaceView android:idid/surface_view android:layout_widthmatch_parent android:layout_height0dp android:layout_weight1 / LinearLayout android:layout_widthmatch_parent android:layout_heightwrap_content android:orientationhorizontal android:padding16dp android:gravitycenter Button android:idid/btn_capture android:layout_widthwrap_content android:layout_heightwrap_content android:text抓拍图片 / !-- 可以添加更多控制按钮如云台控制、切换码流等 -- /LinearLayout /LinearLayout5. 常见问题与排查技巧实录集成过程中你几乎一定会遇到下面这些问题。我把它们和解决方案整理成了表格方便你快速排查。问题现象可能原因排查步骤与解决方案SDK初始化失败1.*.so文件缺失或放错位置。2. 包名或签名冲突某些SDK有绑定。3. 设备架构不匹配。1. 检查jniLibs目录下是否有对应ABI的.so文件。2. 检查build.gradle中ndk.abiFilters是否包含这些ABI。3. 尝试清理项目 (Build - Clean Project) 并重新构建。设备登录失败1. IP、端口、用户名、密码错误。2. 设备不在线或网络不通。3. 设备已达最大用户数限制。4. SDK版本与设备固件不匹配。1. 使用海康官方SADP工具搜索并确认设备IP和端口。2. 用电脑Ping设备IP确认网络连通性。3. 登录设备Web后台确认用户名密码并查看在线用户。4.核对错误码这是最直接的线索。预览黑屏1.SurfaceView的Surface尚未创建或已销毁。2. 预览参数如通道号错误。3. 码流类型不支持如设备未启用子码流却请求子码流。4. 网络带宽不足主码流太大。1.确保在surfaceCreated回调之后才调用NET_DVR_RealPlay_V40。2. 确认通道号多通道设备通常从1开始。3. 尝试将dwStreamType改为0主码流。4. 尝试将dwLinkMode改为0TCP模式更稳定。预览画面卡顿、花屏1. 网络抖动或带宽不足。2. 解码性能不足旧设备。3. 使用了UDP模式丢包严重。1. 切换到子码流 (dwStreamType1) 预览。2. 确保使用TCP模式 (dwLinkMode0)。3. 检查设备编码参数适当降低主码流码率。抓拍失败1.lUserID无效设备未登录或已注销。2. 存储路径无写入权限。3. 通道号错误。4. 设备不支持抓拍或功能被禁用。1. 确保抓拍前mUserID是有效的登录成功后的值。2.检查Android存储权限特别是Android 6.0的动态权限和Android 10的沙盒限制。3. 确认抓拍的通道号与预览通道号一致。4. 登录设备Web后台检查“抓图设置”是否启用。应用崩溃Native层1. SDK调用顺序错误如未初始化就登录。2. 多线程同时调用同一个SDK函数。3. 传入的参数对象内存被回收或篡改。1.严格遵守生命周期初始化 - 登录 - 预览/抓拍 - 停止预览 - 注销 - 清理。2.将SDK调用封装到单例或统一管理类中加锁保护。3. 确保传入SDK的结构体对象在调用期间不被GC局部变量需谨慎。错误码291设备通道数错误。通常意味着lChannel通道号参数超出了设备实际通道范围。单路摄像头通道号是1。错误码112设备忙。设备资源不足可能是连接数过多或正在进行其他高负载操作如格式化硬盘。稍后重试。独家避坑技巧日志是救星务必开启SDK日志NET_DVR_SetLogToFile。当出现诡异问题时查看/sdcard/HCNetSDK/log/下的日志文件里面往往有比错误码更详细的线索。模拟器调试海康SDK的.so库通常是ARM架构的在x86的模拟器上无法运行。真机调试是必须的。权限处理对于Android 6.0记得在运行时申请WRITE_EXTERNAL_STORAGE权限。对于Android 10 (API 29)优先使用MediaStoreAPI将图片保存到Pictures或DCIM目录或者使用App的专属外部存储目录getExternalFilesDir()。后台抓拍保活如果你想在后台Service中长期定时抓拍需要处理好Android系统的后台限制省电优化、后台任务限制。可以考虑使用WorkManager来调度定时任务并申请必要的后台运行权限和忽略电池优化。句柄管理lUserID和lRealHandle是整数类型的句柄但本质是指向底层资源的指针。务必在合适的时机如Activity的onDestroy将其释放注销、停止预览并将变量置为-1防止重复释放或空指针访问。6. 效果展示与扩展思路经过上述步骤你应该能成功运行一个具备实时预览和抓拍功能的Android应用了。下面是我在真机上测试的效果(此处为效果图描述实际博文中会插入截图)图1实时预览界面。画面清晰流畅SurfaceView中实时显示摄像头监控的画面底部有一个醒目的“抓拍图片”按钮。图2抓拍成功提示。点击抓拍按钮后Toast提示“抓拍成功文件已保存”。图3相册中的抓拍图片。在系统相册的DCIM文件夹中可以看到以CAPTURE_20250415_102030.jpg格式命名的抓拍图片画质与设备主码流一致。功能扩展思路这个基础框架搭建好后你可以根据实际项目需求轻松扩展出更多专业功能云台控制 (PTZ)通过SDK的NET_DVR_PTZControl_Other等函数实现上下左右移动、变倍、变焦。语音对讲调用NET_DVR_StartVoiceCom_V30和NET_DVR_StartVoiceCom_MR可以实现手机与摄像头端的双向语音。录像与回放使用NET_DVR_SaveRealData可以将预览流保存为本地MP4文件使用NET_DVR_PlayBackByName等函数可以检索和回放设备硬盘或SD卡中的录像。报警订阅通过NET_DVR_SetDVRMessageCallBack_V30设置报警回调设备发生移动侦测、遮挡等报警时App能实时接收并处理。多设备/多画面管理多个lUserID和lRealHandle结合GridLayout或自定义ViewGroup可以实现4分屏、9分屏等多画面预览。集成海康SDK就像拼装一台精密的仪器步骤和顺序至关重要。一旦你打通了“初始化-登录-预览/抓拍-释放”这个核心流程并理解了每个环节的“为什么”后续的功能扩展就是水到渠成的事情。希望这篇结合了最新实践和无数踩坑经验的教程能成为你项目中的得力助手。如果在实际操作中遇到新的问题不妨回头再仔细看看错误码和日志那里面藏着解决问题的钥匙。