照着写一个简单的portal,后台一直报java.lang.NullPointerException

本文介绍了一个在Liferay 5.1.2版本中遇到的关于Portlet配置导致NullPointerException异常的问题,并提供了解决该问题的具体方法。错误发生在获取资源包的过程中,通过调整portlet-ext.xml文件中<resource-bundle>标签的格式可以避免此异常。

java.lang.NullPointerException
 at com.liferay.portlet.PortletConfigImpl.getResourceBundle(PortletConfigImpl.java:196)
 at com.liferay.portal.util.PortalImpl.getPortletTitle(PortalImpl.java:1636)
 at com.liferay.portal.util.PortalUtil.getPortletTitle(PortalUtil.java:504)
 at com.liferay.portal.util.comparator.PortletTitleComparator.compare(PortletTitleComparator.java:62)
 at com.liferay.portal.util.comparator.PortletTitleComparator.compare(PortletTitleComparator.java:42)
 at java.util.Arrays.mergeSort(Arrays.java:1284)
 at java.util.Arrays.sort(Arrays.java:1223)
 at java.util.Collections.sort(Collections.java:159)
 at org.apache.jsp.html.portlet.layout_005fconfiguration.view_005fcategory_jsp._jspService(view_005fcategory_jsp.java:852)
 at org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:98)
 at javax.servlet.http.HttpServlet.service(HttpServlet.java:803)
 at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:331)
 at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:329)
 at org.apache.jasper.servlet.JspServlet.service(JspServlet.java:265)
 at javax.servlet.http.HttpServlet.service(HttpServlet.java:803)
 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:269)
 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:188)
 at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:679)
 at org.apache.catalina.core.ApplicationDispatcher.doInclude(ApplicationDispatcher.java:584)
 at org.apache.catalina.core.ApplicationDispatcher.include(ApplicationDispatcher.java:497)
 at com.liferay.taglib.util.IncludeTag.doEndTag(IncludeTag.java:67)
 at org.apache.jsp.html.portlet.layout_005fconfiguration.view_jsp._jspx_meth_liferay_002dutil_005finclude_005f0(view_jsp.java:1059)
 at org.apache.jsp.html.portlet.layout_005fconfiguration.view_jsp._jspService(view_jsp.java:921)
 at org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:98)
 at javax.servlet.http.HttpServlet.service(HttpServlet.java:803)
 at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:331)
 at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:329)
 at org.apache.jasper.servlet.JspServlet.service(JspServlet.java:265)
 at javax.servlet.http.HttpServlet.service(HttpServlet.java:803)
 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:269)
 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:188)
 at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:679)
 at org.apache.catalina.core.ApplicationDispatcher.doInclude(ApplicationDispatcher.java:584)
 at org.apache.catalina.core.ApplicationDispatcher.include(ApplicationDispatcher.java:497)
 at com.liferay.taglib.util.IncludeTag.doEndTag(IncludeTag.java:67)
 at org.apache.jsp.html.common.themes.portlet_jsp._jspService(portlet_jsp.java:1622)
 at org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:98)
 at javax.servlet.http.HttpServlet.service(HttpServlet.java:803)
 at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:331)
 at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:329)
 at org.apache.jasper.servlet.JspServlet.service(JspServlet.java:265)
 at javax.servlet.http.HttpServlet.service(HttpServlet.java:803)
 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:269)
 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:188)
 at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:679)
 at org.apache.catalina.core.ApplicationDispatcher.doInclude(ApplicationDispatcher.java:584)
 at org.apache.catalina.core.ApplicationDispatcher.include(ApplicationDispatcher.java:497)
 at com.liferay.portlet.PortletRequestDispatcherImpl.dispatch(PortletRequestDispatcherImpl.java:302)
 at com.liferay.portlet.PortletRequestDispatcherImpl.include(PortletRequestDispatcherImpl.java:115)
 at com.liferay.portal.struts.PortletRequestProcessor.doInclude(PortletRequestProcessor.java:284)
 at com.liferay.portal.struts.PortletRequestProcessor.doForward(PortletRequestProcessor.java:255)
 at org.apache.struts.tiles.TilesRequestProcessor.processTilesDefinition(TilesRequestProcessor.java:239)
 at org.apache.struts.tiles.TilesRequestProcessor.internalModuleRelativeForward(TilesRequestProcessor.java:341)
 at org.apache.struts.action.RequestProcessor.processForward(RequestProcessor.java:572)
 at org.apache.struts.action.RequestProcessor.process(RequestProcessor.java:221)
 at com.liferay.portal.struts.PortletRequestProcessor.process(PortletRequestProcessor.java:235)
 at com.liferay.portlet.StrutsPortlet.include(StrutsPortlet.java:261)
 at com.liferay.portlet.StrutsPortlet.doView(StrutsPortlet.java:156)
 at com.liferay.portal.kernel.portlet.LiferayPortlet.doDispatch(LiferayPortlet.java:115)
 at javax.portlet.GenericPortlet.render(GenericPortlet.java:233)
 at com.sun.portal.portletcontainer.appengine.filter.FilterChainImpl.doFilter(FilterChainImpl.java:121)
 at com.liferay.portal.kernel.portlet.PortletFilterUtil.doFilter(PortletFilterUtil.java:69)
 at com.liferay.portlet.InvokerPortletImpl.invoke(InvokerPortletImpl.java:589)
 at com.liferay.portlet.InvokerPortletImpl.invokeRender(InvokerPortletImpl.java:646)
 at com.liferay.portlet.InvokerPortletImpl.render(InvokerPortletImpl.java:414)
 at org.apache.jsp.html.portal.render_005fportlet_jsp._jspService(render_005fportlet_jsp.java:1378)
 at org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:98)
 at javax.servlet.http.HttpServlet.service(HttpServlet.java:803)
 at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:331)
 at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:329)
 at org.apache.jasper.servlet.JspServlet.service(JspServlet.java:265)
 at javax.servlet.http.HttpServlet.service(HttpServlet.java:803)
 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:269)
 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:188)
 at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:679)
 at org.apache.catalina.core.ApplicationDispatcher.doInclude(ApplicationDispatcher.java:584)
 at org.apache.catalina.core.ApplicationDispatcher.include(ApplicationDispatcher.java:497)
 at com.liferay.portal.util.PortalImpl.renderPortlet(PortalImpl.java:2391)
 at com.liferay.portal.util.PortalImpl.renderPortlet(PortalImpl.java:2341)
 at com.liferay.portal.util.PortalUtil.renderPortlet(PortalUtil.java:773)
 at com.liferay.portal.action.RenderPortletAction.execute(RenderPortletAction.java:96)
 at org.apache.struts.action.RequestProcessor.processActionPerform(RequestProcessor.java:431)
 at org.apache.struts.action.RequestProcessor.process(RequestProcessor.java:236)
 at com.liferay.portal.struts.PortalRequestProcessor.process(PortalRequestProcessor.java:164)
 at org.apache.struts.action.ActionServlet.process(ActionServlet.java:1196)
 at org.apache.struts.action.ActionServlet.doPost(ActionServlet.java:432)
 at javax.servlet.http.HttpServlet.service(HttpServlet.java:710)
 at com.liferay.portal.servlet.MainServlet.callParentService(MainServlet.java:443)
 at com.liferay.portal.servlet.MainServlet.service(MainServlet.java:708)
 at javax.servlet.http.HttpServlet.service(HttpServlet.java:803)
 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:269)
 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:188)
 at com.liferay.portal.kernel.servlet.BaseFilter.processFilter(BaseFilter.java:132)
 at com.liferay.portal.servlet.filters.strip.StripFilter.processFilter(StripFilter.java:117)
 at com.liferay.portal.kernel.servlet.BaseFilter.doFilter(BaseFilter.java:71)
 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:215)
 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:188)
 at com.liferay.portal.kernel.servlet.BaseFilter.processFilter(BaseFilter.java:132)
 at com.liferay.portal.servlet.filters.compression.CompressionFilter.processFilter(CompressionFilter.java:141)
 at com.liferay.portal.kernel.servlet.BaseFilter.doFilter(BaseFilter.java:71)
 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:215)
 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:188)
 at com.liferay.portal.kernel.servlet.BaseFilter.processFilter(BaseFilter.java:132)
 at com.liferay.portal.servlet.filters.secure.SecureFilter.processFilter(SecureFilter.java:282)
 at com.liferay.portal.kernel.servlet.BaseFilter.doFilter(BaseFilter.java:71)
 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:215)
 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:188)
 at com.liferay.portal.kernel.servlet.BaseFilter.processFilter(BaseFilter.java:132)
 at com.liferay.portal.servlet.filters.autologin.AutoLoginFilter.processFilter(AutoLoginFilter.java:199)
 at com.liferay.portal.kernel.servlet.BaseFilter.doFilter(BaseFilter.java:71)
 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:215)
 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:188)
 at com.liferay.portal.kernel.servlet.BaseFilter.processFilter(BaseFilter.java:132)
 at com.liferay.portal.servlet.filters.virtualhost.VirtualHostFilter.doFilter(VirtualHostFilter.java:186)
 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:215)
 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:188)
 at com.liferay.portal.kernel.servlet.BaseFilter.processFilter(BaseFilter.java:132)
 at com.liferay.portal.kernel.servlet.BaseFilter.doFilter(BaseFilter.java:74)
 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:215)
 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:188)
 at org.tuckey.web.filters.urlrewrite.UrlRewriteFilter.doFilter(UrlRewriteFilter.java:738)
 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:215)
 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:188)
 at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:213)
 at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:174)
 at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:433)
 at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
 at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:117)
 at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:108)
 at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:174)
 at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:874)
 at org.apache.coyote.http11.Http11BaseProtocol$Http11ConnectionHandler.processConnection(Http11BaseProtocol.java:665)
 at org.apache.tomcat.util.net.PoolTcpEndpoint.processSocket(PoolTcpEndpoint.java:528)
 at org.apache.tomcat.util.net.LeaderFollowerWorkerThread.runIt(LeaderFollowerWorkerThread.java:81)
 at org.apache.tomcat.util.threads.ThreadPool$ControlRunnable.run(ThreadPool.java:689)
 at java.lang.Thread.run(Thread.java:595)
03:55:52,343 ERROR [IncludeTag:52] Current URL /c/portal/render_portlet generates exception: null

 

我的版本是5.1.2,照着例子删了写,写了删重复N遍都是这样,找了好多资料,才发现原来是portlet-ext.xml的<resource-bundle>搞鬼,疯了。


好像liferay有个bug
把<resource-bundle>标签换两行就报错,不显示。
com.liferay.portlet.StrutsResourceBundle</resource-bundle>
得把整个标签写成一行

狂晕啊~~~~~~~~~

 

public class MainActivity extends AppCompatActivity { private static final String TAG = "camera2api"; private static final int REQUEST_CAMERA_PERMISSION = 100; private static final SparseIntArray ORIENTATIONS = new SparseIntArray(); static { ORIENTATIONS.append(Surface.ROTATION_0, 90); ORIENTATIONS.append(Surface.ROTATION_90, 0); ORIENTATIONS.append(Surface.ROTATION_180, 270); ORIENTATIONS.append(Surface.ROTATION_270, 180); } private TextureView textureView; private Button captureButton; private CameraDevice cameraDevice; private CameraCaptureSession cameraCaptureSession; private CaptureRequest.Builder captureRequestBuilder; private String cameraId; private Size previewSize; private Handler backgroundHandler; private HandlerThread backgroundThread; private ImageReader imageReader; private boolean isTextureAvailable = false; // FIX: 跟踪Surface状态 private CameraManager manager; private volatile boolean isCapturing = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d(TAG, "onCreate: Activity创建"); textureView = findViewById(R.id.textureView); captureButton = findViewById(R.id.btnCapture); // 检查相机权限 // ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA):用于检测this(当前应用)是否有Manifest.permission.CAMERA权限 //有则返回PackageManager.PERMISSION_GRANTED(有权限),没有则返回PackageManager.PERMISSION_DENIED(无权限) if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { Log.i(TAG, "没有相机权限——>开始请求相机权限"); //ActivityCompat.requestPermissions():请求权限的方法 //this:当前应用 //new String[]{权限1,权限2,...}:请求的权限集合 //code:对应编码名,发送请求后会调用回调函数onRequestPermissionsResult(),该回调的第一个参数就是这个code,用于给我们做请求权限后的自定义操作。 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA_PERMISSION); } //注意:以上的权限请求是异步操作 //监听预览 textureView.setSurfaceTextureListener(surfaceTextureListener); captureButton.setOnClickListener(v -> takePicture()); } private final TextureView.SurfaceTextureListener surfaceTextureListener = new TextureView.SurfaceTextureListener() { //触发时机:SurfaceTexture 初始化后 @Override public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surface, int width, int height) { Log.d(TAG, "onSurfaceTextureAvailable: Surface可用 " + width + "x" + height); isTextureAvailable = true; // FIX: 设置可用标志 setupCamera(width, height); openCamera(); } @Override public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surface, int width, int height) { Log.d(TAG, "onSurfaceTextureSizeChanged: " + width + "x" + height); } @Override public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) { Log.d(TAG, "onSurfaceTextureDestroyed"); isTextureAvailable = false; return false; } @Override public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) {} }; private void setupCamera(int width, int height) { Log.d(TAG, "setupCamera: 初始化相机配置"); manager = (CameraManager) getSystemService(CAMERA_SERVICE); try { cameraId = manager.getCameraIdList()[0]; Log.i(TAG, "使用相机ID: " + cameraId); CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId); StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); if (map == null) { Log.e(TAG, "错误: StreamConfigurationMap为空"); return; } previewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class), width, height); Log.i(TAG, "选择预览尺寸: " + previewSize.getWidth() + "x" + previewSize.getHeight()); } catch (CameraAccessException e) { Log.e(TAG, "相机访问异常: " + e.getMessage()); } catch (NullPointerException e) { Log.e(TAG, "NPE: " + e.getMessage()); } } private Size chooseOptimalSize(Size[] choices, int width, int height) { List<Size> bigEnough = new ArrayList<>(); for (Size option : choices) { float ratio = (float) option.getWidth() / option.getHeight(); float viewRatio = (float) width / height; if (Math.abs(ratio - viewRatio) <= 0.1 && option.getWidth() <= width && option.getHeight() <= height) { bigEnough.add(option); } } if (!bigEnough.isEmpty()) { return Collections.max(bigEnough, new CompareSizesByArea()); } Log.w(TAG, "未找到完美匹配尺寸,使用默认"); return choices[0]; } static class CompareSizesByArea implements Comparator<Size> { @Override public int compare(Size lhs, Size rhs) { return Long.signum((long) lhs.getWidth() * lhs.getHeight() - (long) rhs.getWidth() * rhs.getHeight()); } } private void openCamera() { if (!isTextureAvailable) { // FIX: 检查Surface是否可用 Log.w(TAG, "Surface不可用,延迟打开相机"); return; } Log.d(TAG, "openCamera: 尝试打开相机"); manager = (CameraManager) getSystemService(CAMERA_SERVICE); try { if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { Log.i(TAG, "1.打开相机: " + cameraId); manager.openCamera(cameraId, stateCallback, backgroundHandler); } else { Log.w(TAG, "相机权限未授予"); } } catch (CameraAccessException e) { Log.e(TAG, "打开相机失败: " + e.getMessage()); } catch (SecurityException e) { Log.e(TAG, "安全异常: " + e.getMessage()); } } private final CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() { @Override public void onOpened(@NonNull CameraDevice camera) { Log.i(TAG, "相机已打开"); cameraDevice = camera; Log.i(TAG, "2.开始配置预览流"); createCameraPreviewSession(); } @Override public void onDisconnected(@NonNull CameraDevice camera) { Log.w(TAG, "相机断开连接"); cameraDevice.close(); } @Override public void onError(@NonNull CameraDevice camera, int error) { Log.e(TAG, "相机错误: " + error); cameraDevice.close(); cameraDevice = null; } }; private void createCameraPreviewSession() { if (cameraDevice == null || !isTextureAvailable) { // FIX: 双重检查 Log.e(TAG, "创建预览会话失败: 相机或Surface不可用"); return; } try { Log.d(TAG, "创建预览会话"); SurfaceTexture texture = textureView.getSurfaceTexture(); texture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight()); Surface surface = new Surface(texture); captureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); captureRequestBuilder.addTarget(surface); cameraDevice.createCaptureSession(Arrays.asList(surface), new CameraCaptureSession.StateCallback() { @Override public void onConfigured(@NonNull CameraCaptureSession session) { Log.i(TAG, "预览会话配置成功"); if (cameraDevice == null) return; cameraCaptureSession = session; try { captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); Log.i(TAG, "3.开始发送预览请求"); cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, backgroundHandler); Log.d(TAG, "预览已启动"); } catch (CameraAccessException e) { Log.e(TAG, "设置预览请求失败: " + e.getMessage()); } } @Override public void onConfigureFailed(@NonNull CameraCaptureSession session) { Log.e(TAG, "预览会话配置失败"); Toast.makeText(MainActivity.this, "配置失败", Toast.LENGTH_SHORT).show(); } }, backgroundHandler); } catch (CameraAccessException e) { Log.e(TAG, "创建预览会话异常: " + e.getMessage()); } } //=== 必需辅助方法 ===// // 初始化后台Handler private void initBackgroundHandler() { HandlerThread handlerThread = new HandlerThread("CameraBackground"); handlerThread.start(); backgroundHandler = new Handler(handlerThread.getLooper()); } // 优化尺寸选择 private Size chooseOptimalSize(Size[] choices) { // 优先选择16:9的比例 final double ASPECT_RATIO = 16.0/9.0; Size optimalSize = null; for (Size size : choices) { double ratio = (double) size.getWidth() / size.getHeight(); if (Math.abs(ratio - ASPECT_RATIO) <= 0.1) { if (optimalSize == null || size.getWidth() > optimalSize.getWidth()) { optimalSize = size; } } } // 若无匹配则选择最大尺寸 if (optimalSize == null) { for (Size size : choices) { if (optimalSize == null || size.getWidth() > optimalSize.getWidth()) { optimalSize = size; } } } return optimalSize != null ? optimalSize : new Size(1920, 1080); } // 方向映射 private int getOrientation(int rotation) { switch (rotation) { case Surface.ROTATION_0: return 90; case Surface.ROTATION_90: return 0; case Surface.ROTATION_180: return 270; case Surface.ROTATION_270: return 180; default: return 0; } } private void saveImage(byte[] bytes, File file) { Log.d(TAG, "保存图像: " + file.getAbsolutePath()); try (FileOutputStream output = new FileOutputStream(file)) { output.write(bytes); Log.i(TAG, "图像保存成功, 大小: " + bytes.length + " bytes"); } catch (IOException e) { Log.e(TAG, "保存文件失败: " + e.getMessage()); } } //触发时机:用户点击授权后调用 @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); Log.d(TAG, "权限请求结果: " + requestCode); if (requestCode == REQUEST_CAMERA_PERMISSION) { if (grantResults[0] == PackageManager.PERMISSION_DENIED) { Log.w(TAG, "用户拒绝相机权限"); Toast.makeText(this, "需要相机权限", Toast.LENGTH_SHORT).show(); finish(); } else { Log.i(TAG, "用户授予相机权限"); startCamera(); // 重新检查Surface状态 if (textureView.isAvailable()) { //配置预览Size setupCamera(textureView.getWidth(), textureView.getHeight()); } } } } private void startCamera() { Log.d(TAG, "启动相机后台线程"); if (backgroundThread == null) { backgroundThread = new HandlerThread("CameraBackground"); backgroundThread.start(); backgroundHandler = new Handler(backgroundThread.getLooper()); } } @Override protected void onResume() { super.onResume(); Log.d(TAG, "onResume"); startBackgroundThread(); // FIX: 如果Surface已经可用,重新打开相机 if (textureView.isAvailable()) { setupCamera(textureView.getWidth(), textureView.getHeight()); openCamera(); } } @Override protected void onPause() { Log.d(TAG, "onPause"); closeCamera(); stopBackgroundThread(); super.onPause(); } private void startBackgroundThread() { if (backgroundThread == null) { backgroundThread = new HandlerThread("CameraBackground"); backgroundThread.start(); backgroundHandler = new Handler(backgroundThread.getLooper()); Log.d(TAG, "后台线程启动"); } } private void stopBackgroundThread() { if (backgroundThread != null) { Log.d(TAG, "停止后台线程"); backgroundThread.quitSafely(); try { backgroundThread.join(); backgroundThread = null; backgroundHandler = null; } catch (InterruptedException e) { Log.e(TAG, "停止线程失败: " + e.getMessage()); } } } private void closeCamera() { Log.d(TAG, "关闭相机资源"); if (isCapturing) { Log.w(TAG, "正在拍照中,等待完成或取消..."); // 可以尝试等待一段时间或取消请求 try { cameraCaptureSession.abortCaptures(); // 取消所有进行中的捕获 } catch (CameraAccessException e) { throw new RuntimeException(e); } } if (cameraCaptureSession != null) { cameraCaptureSession.close(); cameraCaptureSession = null; } if (cameraDevice != null) { cameraDevice.close(); cameraDevice = null; } if (imageReader != null) { imageReader.close(); imageReader = null; } } private void takePicture() { if (cameraDevice == null) { Log.w(TAG, "拍照失败: 相机未初始化"); return; } Log.i(TAG, "4.开始拍照流程————"); try { // 1. 检查会话有效性 if (cameraCaptureSession == null) { Log.e(TAG, "拍照错误: CameraCaptureSession为空"); return; } // 2. 检查后台Handler if (backgroundHandler == null) { Log.e(TAG, "拍照错误: backgroundHandler未初始化"); initBackgroundHandler(); // 初始化方法见下方 } // 3. 获取相机配置 CameraManager manager = (CameraManager) getSystemService(CAMERA_SERVICE); CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId); StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); if (map == null) { Log.e(TAG, "拍照错误: StreamConfigurationMap为空"); return; } // 4. 优化尺寸选择(兼容性处理) Size[] jpegSizes = map.getOutputSizes(ImageFormat.JPEG); Size captureSize = chooseOptimalSize(jpegSizes); // 自定义方法见下方 Log.i(TAG, "使用拍照尺寸: " + captureSize.getWidth() + "x" + captureSize.getHeight()); // 5. 创建ImageReader if (imageReader != null) { imageReader.close(); } imageReader = ImageReader.newInstance( captureSize.getWidth(), captureSize.getHeight(), ImageFormat.JPEG, 2 // 缓冲区数量 ); // 6. 配置输出表面 List<Surface> outputSurfaces = new ArrayList<>(2); outputSurfaces.add(imageReader.getSurface()); outputSurfaces.add(new Surface(textureView.getSurfaceTexture())); // 7. 创建拍照请求 final CaptureRequest.Builder captureBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); captureBuilder.addTarget(imageReader.getSurface()); captureBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO); // 8. 方向处理(添加完整方向映射) int rotation = getWindowManager().getDefaultDisplay().getRotation(); int orientation = getOrientation(rotation); // 自定义方法见下方 captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, orientation); Log.d(TAG, "设置照片方向: " + orientation); // 9. 创建目标文件 final File file = new File( Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "pic_"+System.currentTimeMillis()+".jpg" ); // 10. 图像可用监听器 imageReader.setOnImageAvailableListener(reader -> { Log.d(TAG, "图像数据可用"); try (Image image = reader.acquireLatestImage()) { if (image != null) { ByteBuffer buffer = image.getPlanes()[0].getBuffer(); byte[] bytes = new byte[buffer.remaining()]; buffer.get(bytes); saveImage(bytes, file); } } catch (Exception e) { Log.e(TAG, "保存图像错误: " + e.getMessage()); } }, backgroundHandler); // 11. 拍照回调 CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback() { @Override public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) { // 处理拍照完成 isCapturing = false; // 可以在这里进行后续操作,但注意检查资源是否仍然有效 super.onCaptureCompleted(session, request, result); Log.i(TAG, "拍照完成,保存至: " + file.getAbsolutePath()); runOnUiThread(() -> Toast.makeText(MainActivity.this, "保存至: " + file, Toast.LENGTH_SHORT).show() ); createCameraPreviewSession(); // 恢复预览 } @Override public void onCaptureFailed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureFailure failure) { super.onCaptureFailed(session, request, failure); Log.e(TAG, "拍照失败: " + failure.getReason()); } }; // 12. 执行拍照(添加会话状态检查) Log.d(TAG, "停止预览"); cameraCaptureSession.stopRepeating(); Log.d(TAG, "cameraCaptureSession.getDevice().getId():"+cameraCaptureSession.getDevice().getId()); Log.d(TAG, "cameraId:"+cameraId); if (cameraCaptureSession.getDevice().getId().equals(cameraId)) { // 状态验证 Log.d(TAG, "4.下发拍照"); isCapturing = true; cameraCaptureSession.capture(captureBuilder.build(), captureCallback, backgroundHandler); } else { Log.e(TAG, "会话与当前相机设备不匹配"); } } catch (CameraAccessException | IllegalStateException | SecurityException e) { Log.e(TAG, "拍照过程异常: " + e.getClass().getSimpleName(), e); } } private void takePicture() { if (cameraDevice == null) { Log.w(TAG, "拍照失败: 相机未初始化"); return; } Log.i(TAG, "4.开始拍照流程————"); try { // 1. 检查会话有效性 if (cameraCaptureSession == null) { Log.e(TAG, "拍照错误: CameraCaptureSession为空"); return; } // 2. 检查后台Handler if (backgroundHandler == null) { Log.e(TAG, "拍照错误: backgroundHandler未初始化"); initBackgroundHandler(); // 初始化方法见下方 } // 3. 获取相机配置 CameraManager manager = (CameraManager) getSystemService(CAMERA_SERVICE); CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId); StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); if (map == null) { Log.e(TAG, "拍照错误: StreamConfigurationMap为空"); return; } // 4. 优化尺寸选择(兼容性处理) Size[] jpegSizes = map.getOutputSizes(ImageFormat.JPEG); Size captureSize = chooseOptimalSize(jpegSizes); // 自定义方法见下方 Log.i(TAG, "使用拍照尺寸: " + captureSize.getWidth() + "x" + captureSize.getHeight()); // 5. 创建ImageReader if (imageReader != null) { imageReader.close(); } imageReader = ImageReader.newInstance( captureSize.getWidth(), captureSize.getHeight(), ImageFormat.JPEG, 2 // 缓冲区数量 ); // 6. 配置输出表面 List<Surface> outputSurfaces = new ArrayList<>(2); outputSurfaces.add(imageReader.getSurface()); outputSurfaces.add(new Surface(textureView.getSurfaceTexture())); // 7. 创建拍照请求 final CaptureRequest.Builder captureBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); captureBuilder.addTarget(imageReader.getSurface()); captureBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO); // 8. 方向处理(添加完整方向映射) int rotation = getWindowManager().getDefaultDisplay().getRotation(); int orientation = getOrientation(rotation); // 自定义方法见下方 captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, orientation); Log.d(TAG, "设置照片方向: " + orientation); // 9. 创建目标文件 final File file = new File( Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "pic_"+System.currentTimeMillis()+".jpg" ); // 10. 图像可用监听器 imageReader.setOnImageAvailableListener(reader -> { Log.d(TAG, "图像数据可用"); try (Image image = reader.acquireLatestImage()) { if (image != null) { ByteBuffer buffer = image.getPlanes()[0].getBuffer(); byte[] bytes = new byte[buffer.remaining()]; buffer.get(bytes); saveImage(bytes, file); } } catch (Exception e) { Log.e(TAG, "保存图像错误: " + e.getMessage()); } }, backgroundHandler); // 11. 拍照回调 CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback() { @Override public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) { // 处理拍照完成 isCapturing = false; // 可以在这里进行后续操作,但注意检查资源是否仍然有效 super.onCaptureCompleted(session, request, result); Log.i(TAG, "拍照完成,保存至: " + file.getAbsolutePath()); runOnUiThread(() -> Toast.makeText(MainActivity.this, "保存至: " + file, Toast.LENGTH_SHORT).show() ); createCameraPreviewSession(); // 恢复预览 } @Override public void onCaptureFailed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureFailure failure) { super.onCaptureFailed(session, request, failure); Log.e(TAG, "拍照失败: " + failure.getReason()); } }; // 12. 执行拍照(添加会话状态检查) Log.d(TAG, "停止预览"); cameraCaptureSession.stopRepeating(); Log.d(TAG, "cameraCaptureSession.getDevice().getId():"+cameraCaptureSession.getDevice().getId()); Log.d(TAG, "cameraId:"+cameraId); if (cameraCaptureSession.getDevice().getId().equals(cameraId)) { // 状态验证 Log.d(TAG, "4.下发拍照"); isCapturing = true; cameraCaptureSession.capture(captureBuilder.build(), captureCallback, backgroundHandler); } else { Log.e(TAG, "会话与当前相机设备不匹配"); } } catch (CameraAccessException | IllegalStateException | SecurityException e) { Log.e(TAG, "拍照过程异常: " + e.getClass().getSimpleName(), e); } } } 这个运行拍照功能的时候错: Process: com.android.example.cameraappxjava, PID: 17276 java.lang.IllegalArgumentException: CaptureRequest contains unconfigured Input/Output Surface! at android.hardware.camera2.CaptureRequest.convertSurfaceToStreamId(CaptureRequest.java:791) 说是输入输出配置问题,帮我看看该怎么配
最新发布
09-09
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值