在这里首先很感谢博主zmyde2010发表的这篇http://blog.csdn.net/zmyde2010/article/details/6925498#reply文章,我最开始也是看了他的文章才弄懂截屏的,而且博主基本上把全部的过程都讲出来,后来还附了代码,对于他这样无私的奉献我表示很感谢。我这篇文章也是在他的基础上修改,所以称做他的一个续集吧。有什么问题请大家及时在评论中提出来,我会尽快修复的。
在zmyde2010那篇文章中,他是直接在c层截出图并保存在指定位置,而我这篇讲的是在c层将图输出为流的形式传到java层,然后再由java层处理,这样可以在java 层将其转化成图片或者做其他相应的操作。下面首先给出cpp代码,我采用的是动态注册本地函数的形式.
com_android_screencapture_ScreenCaptureNative.cpp
#define LOG_TAG "Screen_Capture" #include "jni.h" #include <utils/Log.h> #include <binder/IPCThreadState.h> #include <binder/ProcessState.h> #include <binder/IServiceManager.h> #include <binder/IMemory.h> #include <surfaceflinger/ISurfaceComposer.h> #include <SkImageEncoder.h> #include <SkBitmap.h> #include "CreateJavaOutputStreamAdaptor.h" using namespace android; const int kJPEG = 0; const int kPNG = 1; //format表示图片格式,目前是jpeg或者png;quality为图片质量,一般为90;outputStream表示截图对应的流输出,buffer为缓冲区,我是定为4096个byte jboolean nativeCaptureScreen(JNIEnv *env, jobject object, jint format, jint quality, jobject outputStream, jbyteArray buffer) { const String16 name("SurfaceFlinger"); sp<ISurfaceComposer> composer; getService(name, &composer); sp<IMemoryHeap> heap; uint32_t w, h; PixelFormat f; status_t err = composer->captureScreen(0, &heap, &w, &h, &f, 0, 0); if (err != NO_ERROR) { LOGE("Screen shot failed"); return JNI_FALSE; } LOGD("screen capture success: w=%u, h=%u, pixels=%p\n",w, h, heap->getBase()); SkBitmap b; b.setConfig(SkBitmap::kARGB_8888_Config, w, h); b.setPixels(heap->getBase()); SkImageEncoder::Type fm; switch (format) { case kJPEG: fm = SkImageEncoder::kJPEG_Type; break; case kPNG: fm = SkImageEncoder::kPNG_Type; break; default: LOGE("FORMAT ERROR"); return JNI_FALSE; } SkWStream* strm = CreateJavaOutputStreamAdaptor(env, outputStream, buffer); if (NULL != strm) { SkImageEncoder* encoder = SkImageEncoder::Create(fm); if (NULL != encoder) { encoder->encodeStream(strm, b, quality); delete encoder; } delete strm; } return JNI_TRUE; } static const char *classPathName = "com/android/screencapture/ScreenCaptureNative"; static JNINativeMethod methods[] = { {"nativeCaptureScreen", "(IILjava/io/OutputStream;[B)Z", (void*)nativeCaptureScreen}, }; /* * Register several native methods for one class. */ static int registerNativeMethods(JNIEnv* env, const char* className, JNINativeMethod* gMethods, int numMethods) { jclass clazz; clazz = env->FindClass(className); if (clazz == NULL) { LOGE("Native registration unable to find class '%s'", className); return JNI_FALSE; } if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) { LOGE("RegisterNatives failed for '%s'", className); return JNI_FALSE; } return JNI_TRUE; } /* * Register native methods for all classes we know about. * * returns JNI_TRUE on success. */ static int registerNatives(JNIEnv* env) { if (!registerNativeMethods(env, classPathName, methods, sizeof(methods) / sizeof(methods[0]))) { return JNI_FALSE; } return JNI_TRUE; } /* * This is called by the VM when the shared library is first loaded. */ typedef union { JNIEnv* env; void* venv; } UnionJNIEnvToVoid; jint JNI_OnLoad(JavaVM* vm, void* reserved) { UnionJNIEnvToVoid uenv; uenv.venv = NULL; jint result = -1; JNIEnv* env = NULL; LOGI("JNI_OnLoad"); if (vm->GetEnv(&uenv.venv, JNI_VERSION_1_4) != JNI_OK) { LOGE("ERROR: GetEnv failed"); goto bail; } env = uenv.env; if (registerNatives(env) != JNI_TRUE) { LOGE("ERROR: registerNatives failed"); goto bail; } result = JNI_VERSION_1_4; bail: return result; }
下面是对应的Android.mk文件:
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_SRC_FILES := com_android_screencapture_ScreenCaptureNative.cpp LOCAL_SHARED_LIBRARIES := \ libnativehelper \ libcutils \ libutils \ libbinder \ libskia \ libui \ libsurfaceflinger_client \ libandroid_runtime LOCAL_C_INCLUDES += \ $(JNI_H_INCLUDE) \ external/skia/include/core \ external/skia/include/effects \ external/skia/include/images \ external/skia/src/ports \ external/skia/include/utils \ frameworks/base/core/jni/android/graphics LOCAL_MODULE := libscreencapjni LOCAL_MODULE_TAGS := optional LOCAL_PRELINK_MODULE := false include $(BUILD_SHARED_LIBRARY)
下面是对应的java层代码:
ScreenCaptureNative.java
package com.android.screencapture; import java.io.OutputStream; import java.io.ByteArrayOutputStream; import android.util.Log; public class ScreenCaptureNative { private final static int BUFFER_SIZE = 4096; private byte[] mBuffer = null; static { System.loadLibrary("screencapjni"); }; private native boolean nativeCaptureScreen(int format, int quality, OutputStream stream, byte[] buffer); public ScreenCaptureNative(){ mBuffer = new byte[BUFFER_SIZE]; } public byte[] startCaptureScreen(int format, int quality) { ByteArrayOutputStream srcPic = new ByteArrayOutputStream(); if (!nativeCaptureScreen(format, quality, srcPic, mBuffer)) { Log.e("screencapture", "CAPTURE FAILED"); return null; } byte[] screenData = srcPic.toByteArray(); return screenData; } }
上述java程序对应的make文件如下:
LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE_TAGS := optional LOCAL_SRC_FILES := $(call all-subdir-java-files) LOCAL_PACKAGE_NAME := ScreenCapture LOCAL_CERTIFICATE := platform LOCAL_REQUIRED_MODULES := libscreencapjni LOCAL_JNI_SHARED_LIBRARIES := libscreencapjni include $(BUILD_PACKAGE) include $(call all-makefiles-under,$(LOCAL_PATH))
在这里特别强调两点:第一,在你所编写的android截屏程序中记得在对应的AndroidManifest.xml添加权限<uses-permission android:name="android.permission.READ_FRAME_BUFFER" />,否则就会在调用c层代码时报错;第二,在编写android截屏程序对应的make文件中,记得添加LOCAL_CERTIFICATE := platform,这也是跟权限相关,然后再放到源码中编译该程序。
另外在zmyde2010的那篇文章中说到编译android自带的截屏demo,结果生成了 test-screencap,我按照博主的要求test-screencap /mnt/sdcard/scapxx.png那样没有弄出截屏,没大明白他说的终端具体指哪个。我这里说下我是怎么用的,首先将test-screencap push到手机中,最好不要放到sd卡所在位置,你可以push到/data下面,然后手机连着电脑,在电脑命令行中输入adb shell进入手机的linux文件系统中,之后cd到刚才存放test-screencap的目录下,先修改下该文件的权限,使其可以执行,然后再输入命令 ./test-screencap /mnt/sdcard/scapxx.png即可截图了,最终在你的sd卡中就可看到刚才截的图。
我也是个android初学者,有哪里错误了欢迎大家指正。。。