在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……
与参考博文当中的方法相比,有以下几个变化:
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()函数
}
定义:
实现
完整程序
为了解决这个问题,本节当中描述一个更高效的方法——通过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; }
相关文章推荐
- 在windows下对android进行实时快速录屏和模拟点击(一)——使用adb命令行
- Android---Monkey指令进行压力测试实例(模拟点击)
- Android如何基于坐标对View进行模拟点击事件详解
- 在windows下使用cmd命令通过adb shell自动调用android模拟或真机的InstrumentRunner进行android 自动化测试
- c#使用API进行模拟鼠标点击 底层操作同样简单
- Android基于坐标对View进行模拟点击事件
- Android通过代码模拟物理、屏幕点击事件
- windows下的android删除模拟器中的apk文件
- Android 模拟器按Ctrl+F11切换横屏后,点击EditText不弹出软键盘
- python模拟android屏幕高频点击工具
- linux(ubuntu)和windows下面快速搭建android开发环境
- Android 【真机】与【模拟器】触摸屏事件的模拟差异分析
- Android快速搭建模拟器环境另类方… 分类: Android开发 2014-05-30 10:52 90人阅读 评论(0) 收藏
- windows环境下进入到android 模拟器
- 豌豆荚、酷市场、应用宝,模拟手指点击事件进行应用自动安装的思考
- android 模拟点击2
- Android模拟点击的四种方式
- golang 调用win32 api 实现windows注销、重启、关机
- android终端模拟器运行命令可以进行adb connect
- webbrowser指定显示网页的位置然后API实现模拟人工点击