您的位置:首页 > 移动开发 > Android开发

在windows下对android进行实时快速录屏和模拟点击(二)——win32 API操控模拟器

2018-02-28 15:42 651 查看
从上一节在windows下对android进行实时快速录屏和模拟点击(一)——使用adb命令行当中,描述了适合于所有情况下的通用方法——adb。而通用方法的最大缺点就是:速度非常慢,在有些时候是远远不能满足需求的。
为了解决这个问题,本节当中描述一个更高效的方法——通过win32 API直接操控模拟器。而众所周知,windows下编程没有什么能比windows api更为高效了。
(对于通常的android开发调试当中,操作模拟器已经足够。但如果有什么需求必须要使用真机同时还要求高速度,只需要将本文中的模拟器替换为远程桌面软件——我用的是Total Control——即可)
文章未写完,先放上程序和效果图



2018.2.28:过完春节的我来填坑了2333……

窗口截屏原理

窗口截屏主要参考了这一篇博文:

重温 Win32 API ----- 截屏指定窗口并打印

基本思路是将窗口DC中的显示缓冲区数据由DDB位图转化为DIB位图,随后保存到自己的内存缓冲区当中。
与参考博文当中的方法相比,有以下几个变化:
1.    去除了无关的打印机部分
2.    cScreenCap类是一个基类,主要有init()、release()、run()三个函数。run()函数会在未收到终止指令时无限循环调用回调虚函数virtual boolImgProcess( const cv::Mat & screen );
3.    cScreenCap类具有RAII特性
4.    DIB位图直接复制到OpenCV的Mat结构当中。由于其端序(大端BGRA)与opencv默认格式相同,可直接bit to bit 复制
头文件定义如下:class cScreenCap
{
public:
cScreenCap( float dpi_scale );
virtual ~cScreenCap();

void init( HWND hwnd );
void release();
bool run();//会循环调用ImgProcess()

void Press( cv::Point p, int ms );
void MouseDown( cv::Point p );
void MouseUp( cv::Point p );
void Swipe( cv::Point p1, cv::Point p2, int ms );
void HitKey( int key );

virtual bool ImgProcess( const cv::Mat & screen );//图像处理回调虚函数,默认操作是在新窗口中显示图像

const float _DPI_Scale;//windows窗口缩放比例
protected:
::time_t _t;//计时
::HWND _hWnd = nullptr, _hParentWnd = nullptr;//窗口句柄
::HDC _hdcWnd = nullptr, _hdcMem = nullptr;//窗口上下文,内存上下文
HBITMAP _hbmWnd = nullptr;//DIB图像句柄
BITMAP _bmpWnd;//DIB图像
::RECT _rectClient;//客户区矩形
};最重要的函数有两个:init()和run()。
初始化过程中,主要完成了获取客户区窗口大小(我并不需要标题栏和边框)、创建并初始化相关句柄的工作。这里特别需要注意的是,由于现在的windows为了支持高分辨率,通常都引入了DPI缩放。简而言之,就是先绘制一个大的窗口,然后缩小一定倍数显示出来。所以,实际内存中图像的宽、高是实际客户区乘以DPI缩放值。如果还按照客户区矩形的大小去获取图像,只能得到实际窗口的一部分。void cScreenCap::init( HWND hwnd )
{
release();

//获取窗口句柄
_hWnd = hwnd;

//获取窗口大小
::GetClientRect( _hWnd, &_rectClient );
int width = static_cast<int>( ( _rectClient.right - _rectClien
cb7c
t.left )*_DPI_Scale );
int height = static_cast<int>( ( _rectClient.bottom - _rectClient.top )*_DPI_Scale );

// 通过内存DC复制客户区到DDB位图
_hdcWnd = ::GetDC( _hWnd );
_hbmWnd = ::CreateCompatibleBitmap( _hdcWnd, width, height );
_hdcMem = ::CreateCompatibleDC( _hdcWnd );
::SelectObject( _hdcMem, _hbmWnd );

// 把窗口DDB转为DIB
_bmpWnd = { 0 };//清零
::GetObject( _hbmWnd, sizeof( BITMAP ), &_bmpWnd );
}

当获取了所有需要的内存对象之后,就可以将图像复制出来进行处理了。由于我后续需要使用OpenCV,所以就直接使用cv::Mat接收图像。这里要注意的是,获得的原始BMP图像数据是BGRA格式的(windows文件都是大端),如果直接以RGBA形式显示则会色彩会出错。所幸的是OpenCV的默认色彩模式就是BGRA,只需要直接内存复制即可,不需要调整端位序。bool cScreenCap::run()
{
cv::Mat img( _bmpWnd.bmHeight, _bmpWnd.bmWidth, CV_8UC4 );

do
{
_t = clock();
::BitBlt( _hdcMem, 0, 0, _bmpWnd.bmWidth, _bmpWnd.bmHeight, _hdcWnd, 0, 0, SRCCOPY );//读取显示缓冲区内存数据
::GetBitmapBits( _hbmWnd, 4 * img.total(), img.data );//复制图像进入内存缓冲区,注意原始数据都是BGRA格式
} while ( ImgProcess( img ) != false );//处理图像,直到返回false

return true;
}
Run()函数中会将得到的图像传递给ImgProcess()虚函数,如果ImgProcess()返回true,则继续运行。自定义的图像处理就只需要继承并重写ImgProcess()函数即可。默认的虚函数会将图像缩小并显示出来,按下ESC会退出。bool cScreenCap::ImgProcess( const cv::Mat & screen )
{
Mat img;
cv::resize( screen, img, Size( static_cast<int>( ( screen.cols + 1 ) / _DPI_Scale ), static_cast<int>( ( screen.rows + 1 ) / _DPI_Scale ) ) );
imshow( "", img );
cout << clock() << endl;
if ( waitKey( 1 ) != 27 )
return true;
else
return false;//按下ESC会返回false并结束run()函数
}

获得句柄

以上内容都假设已经获得了窗口相应句柄。但实际中,在每一次打开窗口,句柄都会变化。所以必须要使用一定的方法获得句柄。获得句柄有许多方法,如EnumWindows()枚举窗口,FindWindow()通过名称查找,::WindowFromPoint()通过位置查找等。我使用的是EnumWindows()枚举并筛选的方法,同时进行了简单封装。
定义:
class cEnumWndName
{
public:
static void Collect();//收集当前窗口信息
static void CollectChild( HWND hParent );
static std::vector<std::string>names;
static std::vector<HWND>hwnds;
protected:
cEnumWndName();//禁止默认构造
static BOOL CALLBACK _GetWnd_Visible( HWND hwnd, LPARAM lParam );//顶层可见窗口窗口枚举回调函数
static BOOL CALLBACK _GetChildWnd_Visible( HWND hwnd, LPARAM lParam );
};

HWND GetWndName_Console();//在控制台界面下让用户选择窗口
HWND GetWndName_PreDefine( const std::string & name, int childID );//根据预定义名称获得句柄

实现
std::vector<std::string> cEnumWndName::names;
std::vector<HWND> cEnumWndName::hwnds;

HWND GetWndName_Console()
{
//获取所有顶层可见窗口
int i;
cEnumWndName::Collect();
cout << "当前可见窗口有:" << endl;
//输出并要求选择
i = 0;
for ( auto s : cEnumWndName::names )
cout << i++ << ":\t" << s << endl;
cout << "请选择你要捕获的窗口:" << endl;
cin >> i;
//输出选中窗口及其子窗口
if ( i < 0 || i >= static_cast<int>( cEnumWndName::names.size() ) )//超出范围,选中桌面
{
cout << "选中:桌面" << endl;
return FindWindow( NULL, NULL );
}
else
{
HWND hParent = cEnumWndName::hwnds.at( i );
string sParent = cEnumWndName::names.at( i );

cout << sParent << "<顶层窗口>" << endl;
cEnumWndName::CollectChild( hParent );
i = 0;
for ( auto s : cEnumWndName::names )
cout << i++ << ":\t" << s << endl;
cout << "请选择你要捕获的子窗口,输入列表以外任一项则选择顶层窗口:" << endl;
cin >> i;
if ( i < 0 || i >= static_cast<int>( cEnumWndName::names.size() ) )//超出范围,选中桌面
{
cout << "选中:" << sParent << endl;
return hParent;
}
else
{
cout << "选中:" << cEnumWndName::names.at( i ) << endl;
return cEnumWndName::hwnds.at( i );
}
}
}

HWND GetWndName_PreDefine( const std::string & name, int childID )
{
//获取所有顶层可见窗口
int i;
cEnumWndName::Collect();
//输出并要求选择
i = 0;
for ( auto s : cEnumWndName::names )
{
if ( name == s )
break;
i++;
}
if ( i >= static_cast<int>( cEnumWndName::names.size() ) )//超出范围,选中桌面
{
return GetWndName_Console();
}
else
{
HWND hParent = cEnumWndName::hwnds.at( i );
string sParent = cEnumWndName::names.at( i );

cEnumWndName::CollectChild( hParent );
i = childID;

if ( i < 0 || i >= static_cast<int>( cEnumWndName::names.size() ) )//超出范围
{
return GetWndName_Console();
}
else
{
cout << "选中:" << cEnumWndName::names.at( i ) << endl;
return cEnumWndName::hwnds.at( i );
}
}
}

BOOL CALLBACK cEnumWndName::_GetWnd_Visible( HWND hwnd, LPARAM lParam ) //窗口枚举回调函数
{
if ( GetParent( hwnd ) == NULL  &&  IsWindowVisible( hwnd ) &&
( ::GetWindowLong( hwnd, GWL_EXSTYLE )&WS_EX_TOOLWINDOW ) != WS_EX_TOOLWINDOW &&
::GetWindowLong( hwnd, GWL_HWNDPARENT ) == 0 )                            //判断窗口是否是可见、顶层
{
hwnds.push_back( hwnd );

char ps[200];
::GetWindowText( hwnd, ps, 200 );
names.push_back( ps );
}
return true;
}

BOOL CALLBACK cEnumWndName::_GetChildWnd_Visible( HWND hwnd, LPARAM lParam )
{
if ( IsWindowVisible( hwnd ) )
{
hwnds.push_back( hwnd );

char ps[200];
::GetWindowText( hwnd, ps, 200 );
names.push_back( ps );
}
return true;
}

void cEnumWndName::Collect() //收集当前窗口信息
{
names.swap( std::vector<std::string>() );
hwnds.swap( std::vector<HWND>() );
::EnumWindows( _GetWnd_Visible, 0 );
}

void cEnumWndName::CollectChild( HWND hParent )
{
names.swap( std::vector<std::string>() );
hwnds.swap( std::vector<HWND>() );
::EnumChildWindows( hParent, _GetChildWnd_Visible, 0 );
}

完整程序

#pragma once
#include <string>
#include <vector>

#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>

#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>

class cEnumWndName
{
public:
static void Collect()//收集当前窗口信息
{
names.swap( std::vector<std::string>() );
hwnds.swap( std::vector<HWND>() );
::EnumWindows( _GetWnd_Visible, 0 );
}
static void CollectChild( HWND hParent )
{
names.swap( std::vector<std::string>() );
hwnds.swap( std::vector<HWND>() );
::EnumChildWindows( hParent, _GetChildWnd_Visible, 0 );
}
static std::vector<std::string>names;
static std::vector<HWND>hwnds;
protected:
cEnumWndName();//禁止默认构造
static BOOL CALLBACK _GetWnd_Visible( HWND hwnd, LPARAM lParam );//顶层可见窗口窗口枚举回调函数
static BOOL CALLBACK _GetChildWnd_Visible( HWND hwnd, LPARAM lParam );
};

HWND GetWndName_Console();//在控制台界面下让用户选择窗口
HWND GetWndName_PreDefine( const std::string & name, int childID );//根据预定义

class cScreenCap
{
public:
cScreenCap( float dpi_scale );
virtual ~cScreenCap();

void init( HWND hwnd );
void release();
bool run();

void Press( cv::Point p, int ms );
void MouseDown( cv::Point p );
void MouseUp( cv::Point p );
void Swipe( cv::Point p1, cv::Point p2, int ms );

virtual bool ImgProcess( const cv::Mat & screen );

const float _DPI_Scale;//windows窗口缩放比例
protected:
::time_t _t;
::HWND _hWnd = nullptr, _hParentWnd = nullptr;//窗口句柄
::HDC _hdcWnd = nullptr, _hdcMem = nullptr;//窗口上下文,内存上下文
HBITMAP _hbmWnd = nullptr;

BITMAP _bmpWnd;
::RECT _rectClient;//客户区矩形
};

#include "cScreenCap.h"
#include <iostream>
#include <time.h>

using namespace std;
using namespace cv;

std::vector<std::string> cEnumWndName::names;
std::vector<HWND> cEnumWndName::hwnds;

HWND GetWndName_Console()
{
//获取所有顶层可见窗口
int i;
cEnumWndName::Collect();
cout << "当前可见窗口有:" << endl;
//输出并要求选择
i = 0;
for ( auto s : cEnumWndName::names )
cout << i++ << ":\t" << s << endl;
cout << "请选择你要捕获的窗口:" << endl;
cin >> i;
//输出选中窗口及其子窗口
if ( i < 0 || i >= static_cast<int>( cEnumWndName::names.size() ) )//超出范围,选中桌面
{
cout << "选中:桌面" << endl;
return FindWindow( NULL, NULL );
}
else
{
HWND hParent = cEnumWndName::hwnds.at( i );
string sParent = cEnumWndName::names.at( i );

cout << sParent << "<顶层窗口>" << endl;
cEnumWndName::CollectChild( hParent );
i = 0;
for ( auto s : cEnumWndName::names )
cout << i++ << ":\t" << s << endl;
cout << "请选择你要捕获的子窗口,输入列表以外任一项则选择顶层窗口:" << endl;
cin >> i;
if ( i < 0 || i >= static_cast<int>( cEnumWndName::names.size() ) )//超出范围,选中桌面
{
cout << "选中:" << sParent << endl;
return hParent;
}
else
{
cout << "选中:" << cEnumWndName::names.at( i ) << endl;
return cEnumWndName::hwnds.at( i );
}
}
}

HWND GetWndName_PreDefine( const std::string & name, int childID )
{
//获取所有顶层可见窗口
int i;
cEnumWndName::Collect();
//输出并要求选择
i = 0;
for ( auto s : cEnumWndName::names )
{
if ( name == s )
break;
i++;
}
if ( i >= static_cast<int>( cEnumWndName::names.size() ) )//超出范围,选中桌面
{
return GetWndName_Console();
}
else
{
HWND hParent = cEnumWndName::hwnds.at( i );
string sParent = cEnumWndName::names.at( i );

cEnumWndName::CollectChild( hParent );
i = childID;

if ( i < 0 || i >= static_cast<int>( cEnumWndName::names.size() ) )//超出范围
{
return GetWndName_Console();
}
else
{
cout << "选中:" << cEnumWndName::names.at( i ) << endl;
return cEnumWndName::hwnds.at( i );
}
}
}

cScreenCap::cScreenCap( float dpi_scale )
:_DPI_Scale( dpi_scale ), _t( clock() )
{
}

void cScreenCap::init( HWND hwnd )
{
release();
//根据窗口名获取窗口句柄
_hWnd = hwnd;
//获取窗口大小
::GetClientRect( _hWnd, &_rectClient );
int width = static_cast<int>( ( _rectClient.right - _rectClient.left )*_DPI_Scale );
int height = static_cast<int>( ( _rectClient.bottom - _rectClient.top )*_DPI_Scale );
// 通过内存DC复制客户区到DDB位图
_hdcWnd = ::GetDC( _hWnd );
_hbmWnd = ::CreateCompatibleBitmap( ::GetDC( _hWnd ), width, height );
_hdcMem = ::CreateCompatibleDC( _hdcWnd );
::SelectObject( _hdcMem, _hbmWnd );
// 把窗口DDB转为DIB
_bmpWnd = { 0 };
::GetObject( _hbmWnd, sizeof( BITMAP ), &_bmpWnd );
}

void cScreenCap::release()
{
if ( _hdcMem != nullptr )
::DeleteDC( _hdcMem );
_hdcMem = 0;
if ( _hbmWnd != nullptr )
::DeleteObject( _hbmWnd );
_hbmWnd = 0;
if ( _hdcWnd != nullptr )
::ReleaseDC( _hWnd, _hdcWnd );
_hdcWnd = 0;
_bmpWnd = { 0 };
}

cScreenCap::~cScreenCap()
{
release();
}

bool cScreenCap::run()
{
Mat img( _bmpWnd.bmHeight, _bmpWnd.bmWidth, CV_8UC4 );

do
{
_t = clock();
::BitBlt( _hdcMem, 0, 0, _bmpWnd.bmWidth, _bmpWnd.bmHeight, _hdcWnd, 0, 0, SRCCOPY );
::GetBitmapBits( _hbmWnd, 4 * img.total(), img.data );
} while ( ImgProcess( img ) != false );
return true;
}

void cScreenCap::Press( cv::Point p, int ms )
{
p /= _DPI_Scale;
UINT loc = ( p.y << 16 ) | p.x;

struct
{
UINT Msg;
WPARAM wParam;
LPARAM lParam;
}msgs[] =
{
{ WM_LBUTTONDOWN, MK_LBUTTON, loc },
{ WM_LBUTTONUP, 0, loc },
};
for ( auto m : msgs )
{
PostMessage( _hWnd, m.Msg, m.wParam, m.lParam );
Sleep( ms );
}
}

bool cScreenCap::ImgProcess( const cv::Mat & screen )
{
Mat img;
cv::resize( screen, img, Size( ( screen.cols + 1 ) / 2, ( screen.rows + 1 ) / 2 ) );
Press( Point( 200, 200 ), 50 );
imshow( "", img );
cout << clock() << endl;
if ( waitKey( 1 ) != 27 )
return true;
else
return false;
}

BOOL CALLBACK cEnumWndName::_GetWnd_Visible( HWND hwnd, LPARAM lParam ) //窗口枚举回调函数
{
if ( GetParent( hwnd ) == NULL  &&  IsWindowVisible( hwnd ) &&
( ::GetWindowLong( hwnd, GWL_EXSTYLE )&WS_EX_TOOLWINDOW ) != WS_EX_TOOLWINDOW &&
::GetWindowLong( hwnd, GWL_HWNDPARENT ) == 0 )                            //判断窗口是否是可见、顶层
{
hwnds.push_back( hwnd );

char ps[200];
::GetWindowText( hwnd, ps, 200 );
names.push_back( ps );
}
return true;
}

BOOL CALLBACK cEnumWndName::_GetChildWnd_Visible( HWND hwnd, LPARAM lParam )
{
if ( IsWindowVisible( hwnd ) )
{
hwnds.push_back( hwnd );

char ps[200];
::GetWindowText( hwnd, ps, 200 );
names.push_back( ps );
}
return true;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: