1、运行程序,显示一个Activity界面,点击按钮,显示一个浮窗。这里用到一个显示浮窗的小技术。
2、在显示浮窗的同时,会启动一个server服务,这个服务很重要,因为在这里会建立java端和android底层(即c语言端)的通信机制。这个地方比较抽象。以后再来解释,你就记住它是一个通信机制,相当于客户端和服务器端的关系
3、点击一下浮窗,就会向android底层发送消息,开始截屏,这里的向android底层发送消息,采用了android源代码里面的通信机制,我直接就把android源码拿来用了。
4、开始截屏,这里的截屏程序也是用的android自带的截屏程序,也是android源码,但是我在这里做了很多的工作。因为截屏出来的图片,我需要保存为png格式。
5、在保存png格式的图片的时候,我又使用了第三方的一个png库。
6、最后的运行效果相当于点击浮窗,开始截屏,再点击浮窗,停止截屏,图片会自动保存到/sdcard/DCIM/这个目录下面。
1、要用到android的应用程序开发的基本知识,这里就不多说了
2、用到了ndk开发技术
3、既然使用ndk开发,那C/C++的技术就不得不用了
4、因为做ndk开发,我觉的就相当于在Linux系统下面做c语言的开发,那Linux开发中使用的一些东西也就需要了。
5、最后一点,你的手机需要root。因为截屏的基本思路就读取屏幕像素在内存中的映射,所有需要直接读取内存中的内容,root是必须的。
1、从现在起,我会一步一步的把这个程序再重新做一遍,目的就是希望能把之前学习到的东西再回顾一遍,温故而知新。
2、先建立一个截屏项目工程,名字随便吧,我的叫screenshot如图
3、在Activity_main.xml文件中添加一个Button控件,如图
在MainActivity.java这个类里面做一些简单的初始化,为Button按钮添加点击响应事件,显示浮窗。因为显示浮窗,程序会直接跳到手机的桌面,所以这里有一个小技术,从应用程序直接跳到手机桌面,代码如下。
/**
* 返回到主桌面 类似按下Home按钮
*
* @param context
*/
public static void backToHomePage(Context context) {
Intent i = new Intent(Intent.ACTION_MAIN);
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
i.addCategory(Intent.CATEGORY_HOME);
context.startActivity(i);
}
4、要新建一个ScreenCaptureServer类,这个类就非常重要了,它主要有这几个作用,首先要创建一个浮窗,其次就是要建立与android底层的连接,然后要维护与android底层的通信。最后也是最重要的,建立截屏程序的独立线程。首先做一个浮窗,很简单,这里就不多说了,具体可以看我的另一篇博客-----android之浮窗篇。
5、实现android的java端与底层的c端通信。因为这里是调用的系统的通信代码,好多我也没有搞清楚。不过现在到是能用,在该程序里面,java端充当的是服务器端,它随service的启动开始运行,不停的监听来自客户端(c端的连接)。下面是服务器线程的具体代码。
/**
* 服务器端的线程
*
* @author jeck
*
*/
private class ServerSocketThread extends Thread {
/**
* 线程运行的标示
*/
private boolean keeprunning = true;
/**
* 本地服务socket
*/
private LocalServerSocket localServerSocket;
@Override
public void run() {
try {
// 创建一个本地socket
localServerSocket = new LocalServerSocket("screen_shot_socket");
} catch (Exception e) {
e.printStackTrace();
keeprunning = false;
}
// 通过while循环, 轮训从客户端发过来的连接请求
while (keeprunning) {
Log.d(TAG, "Waitting for client to connect !! ");
try {
// 监听客户端的连接
LocalSocket interactClientSocket = localServerSocket
.accept();
// 因为有可能在等待客户端连接的时候,accept阻塞了。Activity被finish掉,所以有必要在检测一次
if (keeprunning) {
Log.d(TAG, "now client coming !!!");
// 开始为客户端服务
new InteractClientSocketThread(interactClientSocket)
.start();
}
} catch (Exception e) {
e.printStackTrace();
keeprunning = false;
}
}
// 如果断开连接,那就关闭服务
if (localServerSocket != null) {
try {
localServerSocket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
// /**
// * 停止线程
// */
// public void stopRunning() {
//
// keeprunning = false;
// }
}
6、接下来的是service里面的第三个功能,就是维护与客户端(c端)的通信了。这里无非就是监听连接,连接到了就发送消息。因为要配合我的主要功能,截屏,所有我这里的逻辑是启动service,开启服务器监听,如果有客户端连接,会向客户端发送一个数字2,如果客户端收到2,会向服务器端发送2222表示连接成功,然后如果你点击了我们的小浮窗,会向客户端发送数字1,表示开始截屏,客户端收到消息会想服务器端发送1111表示我客户端已经收到消息了。然后你再点击一次小浮窗,服务器端会发送0,表示停止截屏,那客户端收到0会停止截屏,并向服务器端反馈0000.这就是我的通信机制。下面我把代码粘出来。
/**
* 服务器与客户端直接的通信线程
*
* @author jeck
*
*/
private class InteractClientSocketThread extends Thread {
/**
* 本地socket
*/
private LocalSocket interactClientSocket;
/**
* 输入流
*/
private InputStream inputStream = null;
/**
* 输出流
*/
private OutputStream outputStream = null;
/**
* 从客户端发来的消息
*/
private StringBuilder receiveFromClientString = new StringBuilder();
/**
* 输入缓存区
*/
private char[] readBuffer = new char[4096];
/**
* 输入字节数
*/
private int readBytes = -1;
/**
* 构造函数
*/
public InteractClientSocketThread(LocalSocket interactClientSocket) {
this.interactClientSocket = interactClientSocket;
}
/**
* 从客户端读取数据
*
* @return
*/
private boolean readDataFromClient() {
boolean readResult = false;
// 从本地连接中获取输入流
try {
inputStream = interactClientSocket.getInputStream();
// 读数据
InputStreamReader inputStreamReader = new InputStreamReader(
inputStream);
// 从输入流中读取数据
while ((readBytes = inputStreamReader.read(readBuffer)) != -1) {
String tmpStr = new String(readBuffer, 0, readBytes);
receiveFromClientString.append(tmpStr).append("\n");
}
if (receiveFromClientString.toString() != null) {
if (receiveFromClientString.toString().startsWith("0000")
|| receiveFromClientString.toString().startsWith(
"1111")) {
captureState = 2;
}
// 显示client发送的消息
Log.d(TAG, receiveFromClientString.toString());
// 读取时间成功
readResult = true;
}
} catch (IOException e) {
e.printStackTrace();
}
return readResult;
}
/**
* 向客户端写数据
*
* @return
*/
private boolean writeDataToClient(String writeContent) {
boolean writeResult = false;
try {
outputStream = interactClientSocket.getOutputStream();
// 如果点击了开始录屏,则发送消息
if (writeContent != null && !"".equals(writeContent)) {
outputStream.write(writeContent.getBytes());
}
writeResult = true;
} catch (IOException e) {
e.printStackTrace();
writeResult = false;
}
return writeResult;
}
@Override
public void run() {
try {
switch (captureState) {
case 0:
// 停止截屏
writeDataToClient(STOP_CAPTURE_SCREEN);
break;
case 1:
// 开始录屏
writeDataToClient(START_CAPTURE_SCREEN);
break;
case 2:
// 等待连接
writeDataToClient(KEEP_CONNECTION);
break;
}
readDataFromClient();
} catch (Exception e) {
e.printStackTrace();
Log.d(TAG, "receive data failed !!");
} finally {
if (outputStream != null) {
try {
outputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
if (inputStream != null) {
try {
inputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
7、接下来是最关键的部分,呵呵,其实那个地方都很关键,缺少了任何一个地方,这个程序也跑不起来。废话不多说了,现在简绍截屏进程了。这进程的工作原理是这样的。1需要手机root权限,在获取手机root权限之后,通过ndk的混合编译器,编译一个exe文件,在root权限下面,通过代码执行这个exe程序。换句话说就是相当于在java端,执行exe文件。因为android系统是基于Linux操作系统的,所以你的exe想要直接执行,必须获取一点的权限,就算你在Linux系统下面直接写代码,那你执行./xxxx 程序的时候,也是需要权限的。2就是要创建一个进程了(process)。你执行了exe程序,就相当于你创建了一个进程,所以要把这个进程获取出来,以便操作。最后这些东西都是在一个单独的线程中运行的,来代码。
/**
* 响应点击截屏按钮
*/
private boolean screenCapture() {
boolean result = false;
try {
// 创建log对象
screenLog = new StringBuilder();
// 创建一个进程
logcatProcess = RuntimeHelper.getLogcatProcess(this);
// 创建一个缓冲
bufferedReader = new BufferedReader(new InputStreamReader(
logcatProcess.getInputStream()), 8192);
String line;
while ((line = bufferedReader.readLine()) != null) {
Log.d(TAG, line);
screenLog.append(line).append("\n");
if (line.startsWith("Success")) {
result = true;
}
}
} catch (Exception e) {
e.printStackTrace();
result = false;
}
return result;
}
/**
* 使用异步线程执行截屏操作
*
* @author jeck
*
*/
private class ScreenCaptureTask extends AsyncTask
@Override
protected Boolean doInBackground(Void... params) {
return screenCapture();
}
@Override
protected void onPostExecute(Boolean result) {
if (result) {
// 将日志保存在SD卡上
// try {
// // Utils.saveCaptureLog(screenLog.toString());
// } catch (Exception e) {
// e.printStackTrace();
// }
Toast.makeText(getApplicationContext(), "截屏成功",
Toast.LENGTH_LONG).show();
}
}
}
8、以上就是我们这个截屏程序的java端的所有代码了,这只是一个好的开始。
在java端的功能就是显示一个浮窗,然后点击浮窗,会开始和C端进行交互。
C端涉及的东西就都是C语言的了,这里面都是ndk的知识了,这里首先要编写一个.cpp文件,就是我们的通信程序,因为这里用的是android源代码,所以这个程序目前只适合android4.1系统的。其他版本暂时没有测试。下面这是我的exe程序的主要代码。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "screen_capture_image.h"
#define TAG "--screen_capture-->"
int socketID;
/*
*获取当前的系统版本
*/
int getCurrentSDKVersion() {
int sdk;
char c[PROP_VALUE_MAX];
if (__system_property_get("ro.build.version.sdk", c) > 0) {
sscanf(c, "%d", &sdk);
} else {
sdk = 8;
}
return sdk;
}
/*
* Class: com_rdtd_jni_SendMessageFromClientJNI
* Method: startHeartBead
* Signature: ()I
*/
int connection_to_server() {
char path[] = "screen_shot_socket";
socketID = socket_local_client(path, ANDROID_SOCKET_NAMESPACE_ABSTRACT,
SOCK_STREAM);
//如果连接失败
if (socketID < 0) {
return socketID;
} else {
return 1;
}
}
void close_connection() {
if (socketID) {
close(socketID);
}
}
int read_data_from_server() {
//0表示停止录屏,1 表示开始录屏, 2 表示连接中, -1 表示读取数据出错
int result;
//读取的字数
int read_result;
char readBuffer[2];
memset(readBuffer, 0, 2);
read_result = read(socketID, readBuffer, 2);
if (read_result) {
result = atoi(readBuffer);
//printf("---->read data success :%d\n", result);
} else {
result = -1;
}
return result;
}
int write_data_to_server(const char *str) {
int write_result;
write_result = write(socketID, str, strlen(str));
if (write_result) {
close_connection();
return 1;
} else {
printf("write data failed !\n");
close_connection();
return 0;
}
}
void* begin_capture(void*) {
long result = 1;
//设置截屏开始的标示
set_capture_flag_png(1);
//截屏并保存成png图片,现在在联想的机器上是不行的。一直报的是找不到libpng。so文件
screen_capture_png();
return (void*) result;
}
/*
*截屏程序的入口,截屏参数都放在这里面。
*argv[1]表示宽度, argv[2]表示高度
*默认帧率15帧每秒,宽度和高度是手机屏幕的宽高
*/
int main(int argc, char *argv[]) {
int count = 0;
pthread_t thread_id;
void *thread_result;
//循环读写数据
while (connection_to_server()) {
//先读取数据
int read_result = read_data_from_server();
printf("---->receive message = %d\n", read_result);
__android_log_print(ANDROID_LOG_DEBUG, TAG, "receive message = %d",
read_result);
switch (read_result) {
case 0:
//停止录制
write_data_to_server("0000---------->");
set_capture_flag_png(0);
if (pthread_join(thread_id, &thread_result) == -1) {
printf("waiting thread failed !\n");
} else {
if ((long) thread_result == 0) {
printf("screen_cap return failed \n");
} else {
printf("Success------------------>\n");
//这个地方必须返回,否则保存的图片都是黑屏图片,不知道是为什么,呵呵,应该是没有关闭文件,只有函数返回了,系统自动关闭文件
exit(0);
}
}
break;
case 1:
//开始录制
write_data_to_server("1111---------->");
if (pthread_create(&thread_id, NULL, begin_capture, NULL) == -1) {
printf("create thread failed !\n");
}
break;
case 2:
//连接中
write_data_to_server("2222---------->");
break;
case -1:
//读取数据出错
printf("---->read data failed !\n");
goto exit;
}
sleep(3);
}
exit: close_connection();
return 0;
}
这个cpp文件就会被ndk编译成我们在上面提到的exe文件,就是Linux系统下面的可执行文件。只有这个文件,我们能做的就是和java端发个信息而已,还是不能截屏的,需要截屏的程序在下面,也是用的android源代码,不过基本让我改的没有源代码的味道了。
在这里,我使用了第三方的png库,我把它编译成静态库,然后链接到我的cpp文件中,最后把cpp文件编译成exe文件,再在代码中执行exe文件。下面是截屏的主要代码。
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "screen_capture_image.h"
#include
//#ifdef ANDROID_KK //4.4
//#include
//#include
//#else
#include
//#endif
using namespace android;
#define TAG "--screen_capture-->"
//截屏标识
static int capture_flag = 0;
//录音标识
static int record_flag = 0;
int PushTime = 0;
//获取当前时间(microsecond)
int64_t getCurrentTime() {
struct timeval tv;
gettimeofday(&tv, NULL);
return (1000000LL * tv.tv_sec) + tv.tv_usec;
}
//#ifdef ANDROID_KK
//static uint32_t DEFAULT_DISPLAY_ID = ISurfaceComposer::eDisplayIdMain;
//#endif
//显示错误信息
void error(const char* msg) {
fprintf(stderr, "%s: %s:\n", msg, strerror(errno));
exit(1);
}
typedef struct param {
void *fb_in;
FILE * fb_out;
int width;
int height;
int64_t usedTime;
size_t buffer_size;
param * next;
} image_info;
// 获取当前日期, 以秒为单位,现在我一秒之内可以截取3张图片,他的名字当然一样了
char* getLocalTime() {
char currentTime[128];
memset(currentTime, 0, 128);
int64_t time = getCurrentTime();
sprintf(currentTime, "%lld", time);
return currentTime;
}
//创建视频路径
void create_video_path(char* path) {
memset(path, 0, 256);
strcpy(path, "/sdcard/DCIM/Record_");
strcat(path, getLocalTime());
strcat(path, ".mp4");
printf("---->path = %s\n", path);
}
//创建视频路径
void create_image_path(char* path) {
memset(path, 0, 256);
strcpy(path, "/sdcard/DCIM/capture_");
strcat(path, getLocalTime());
strcat(path, ".png");
printf("---->path = %s\n", path);
}
//创建截屏图片信息的节点
image_info* create_image_info_node(const void * in, FILE* out, int w, int h,
size_t size, int64_t ut) {
image_info *newInfo = (image_info*) malloc(sizeof(image_info));
//分配内存
newInfo->fb_in = malloc(size);
//拷贝内存
memcpy(newInfo->fb_in, in, size);
newInfo->fb_out = out;
newInfo->buffer_size = size;
newInfo->width = w;
newInfo->height = h;
newInfo->usedTime = ut;
newInfo->next = NULL;
printf("---->create a new image node \n");
return newInfo;
}
//释放内存
void release_image_node(image_info* node) {
if (node != NULL) {
free(node->fb_in);
node->fb_in = NULL;
free(node);
node = NULL;
printf("---->success to release a image node \n");
}
}
/*
* fb_base 屏幕左上角的第一个像素的内存地址
* fb_out 生成的png保存的地址
* w 屏幕的宽度
* h 屏幕的高度
* f png的文件格式
*/
//void take_screenshot(char *fb_base, FILE* fb_out, int w, int h, int f) {
int take_screenshot(image_info *argu) {
long result = 0;
//printf("take_screenshot is running! \n");
//image_info *argu = (image_info*) param;
//png结构
png_structp png;
//png info
png_infop info;
struct fb_var_screeninfo vinfo;
// 因为是一行一行的扫描屏幕,r是行数
unsigned int r;
//每一行的长度,是右屏幕的宽度(像素) * 每一个像素的大小(32位 4个字节)
unsigned int rowlen;
//每一个像素所占的大小,4个字节
unsigned int bytespp = 4;
//创建一个png结构
png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
//printf("1---> png_create_write_struct is runed \n");
if (png == NULL) {
ALOGE("failed png_create_write_struct\n");
}
png_init_io(png, argu->fb_out);
//printf("2---> png_init_io is runed \n");
info = png_create_info_struct(png);
//printf("3---> png_create_info_struct is runed \n");
if (info == NULL) {
ALOGE("failed png_create_info_struct\n");
png_destroy_write_struct(&png, NULL);
}
if (setjmp(png_jmpbuf(png))) {
ALOGE("failed png setjmp\n");
png_destroy_write_struct(&png, NULL);
}
//设置png的各种信息
png_set_IHDR(png, info, argu->width, argu->height, 8,
PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
//printf("4---> png_set_IHDR is runed \n");
png_write_info(png, info);
//printf("5---> png_write_info is runed \n");
//计算每一行的长度
rowlen = argu->width * bytespp;
//新建一个临时变量,保存图片的内存块地址
png_bytep temp = (png_bytep) argu->fb_in;
//循环扫描屏幕,一行一行的读取数据
for (r = 0; r < argu->height; r++) {
//将第r行写到png结构中
png_write_row(png, temp);
//计算下一行的起始位置
temp += rowlen;
}
//写png的信息
png_write_end(png, info);
//printf("6---> png_write_end is runed \n");
png_destroy_write_struct(&png, NULL);
//printf("7---> png_destroy_write_struct is runed \n");
//保存完图片,就把文件关闭掉
if (fclose(argu->fb_out) == -1) {
error("close file failed !");
} else {
//printf("---->success to save a image \n");
temp = NULL;
result = 1;
}
return result;
}
//保存图片,返回保存成功的图片数量,如果不等于24, 则说明保存失败, 成功返回1, 失败,返回0
void* save_image(void *start) {
long result = 0;
image_info * p = (image_info*) start;
image_info * next = NULL;
while (p != NULL) {
//先将下一个节点的指针保存在next里面
next = p->next;
//直接保存,如果成功,则释放该节点所占的内存
if (take_screenshot(p) == 1) {
release_image_node(p);
} else {
error("save image failed");
}
//最后再将next节点复制给p,继续操作
p = next;
}
result = 1;
return (void*) result;
}
/*截屏程序*/
image_info* screen_shot() {
static int64_t base_time = getCurrentTime();
//截屏的开始时间和结束时间
int64_t beginTime = 0, usedTime = 0;
const void* base;
size_t size;
uint32_t width, height;
FILE *fb_out = NULL;
//#ifdef ANDROID_KK
//ProcessState::self()->startThreadPool();
//#endif
//
//#ifdef ANDROID_KK
//int32_t displayId = DEFAULT_DISPLAY_ID;
//#endif
ScreenshotClient screenshot;
beginTime = getCurrentTime();
//#ifdef ANDROID_KK
//sp
//if (display != NULL && screenshot.update(display) == NO_ERROR) {
//#else
if (screenshot.update() == NO_ERROR) {
//#endif
base = screenshot.getPixels();
size = screenshot.getSize();
width = screenshot.getWidth();
height = screenshot.getHeight();
//计算截图时间
usedTime = getCurrentTime() - beginTime;
printf("---->Image: width = %d, height = %d, used time = % lld\n",
screenshot.getWidth(), screenshot.getHeight(), usedTime);
char image_path[256];
create_image_path(image_path);
fb_out = fopen(image_path, "w");
return create_image_info_node(base, fb_out, width, height, size,
usedTime);
}
return NULL;
}
//截屏并生成视频链表
void* capture_and_link(void *) {
image_info* start = NULL;
image_info* newNode = NULL;
image_info* tail = NULL;
//开始截屏并生成截屏链表
while (capture_flag) {
//保存视频流
newNode = screen_shot();
if (newNode != NULL) {
if (start == NULL) {
start = newNode;
}
if (tail != NULL) {
tail->next = newNode;
}
tail = newNode;
} else {
printf("---->screen_shot return NULL !\n");
return NULL;
}
//睡3秒,不然录制的太快
sleep(3);
}
return (void*) start;
}
#ifdef __cplusplus
extern "C" {
#endif
//设置截屏标志
void set_capture_flag_png(int flag) {
capture_flag = flag;
record_flag = flag;
}
//截屏并保存为图片
void screen_capture_png() {
//截屏线程,只负责截屏
pthread_t capture_thread;
if (pthread_create(&capture_thread, NULL, capture_and_link, NULL) == -1) {
error("create capture_thread failed !");
}
//截屏之后返回的结果,是一个包含截屏图片信息的链表,这个链表保存的东西关系到整个截屏程序的成败
void* capture_result;
if (pthread_join(capture_thread, &capture_result) == -1) {
error("waiting capture_thread failed !");
}
//截屏完了,开始保存图片
pthread_t save_thread;
if (pthread_create(&save_thread, NULL, save_image, capture_result) == -1) {
error("create save_thread failed !");
}
//等待保存图片的线程
void* save_result;
if (pthread_join(save_thread, &save_result) == -1) {
error("waiting save_thread failed !");
}
if ((long) save_result == 1) {
printf("---->success to save image !\n");
}
}
#ifdef __cplusplus
}
#endif
到此为止,这个截屏程序的主要代码是都将完了,虽然说将的很简单,但是做起来一点都不简单,里面涉及的东西还是很多的。这需要你对ndk编程很熟悉才可能把这个程序顺利的运行起来