iOS 屏幕截图为来自后台应用程序的 System.Drawing.Bitmap (Xamarin)

Posted

技术标签:

【中文标题】iOS 屏幕截图为来自后台应用程序的 System.Drawing.Bitmap (Xamarin)【英文标题】:iOS Screenshot as System.Drawing.Bitmap (Xamarin) from Background App 【发布时间】:2017-02-27 05:42:53 【问题描述】:

对于越狱的 iOS 设备上的私人应用(=不发布在任何应用商店),我需要截取整个屏幕的屏幕截图,以免发送到背景。

已发布类似问题

ios: Is it possible to take screenshots while running as a background task? 得出的结论是,由于 iOS 的安全沙箱,在非越狱设备上是不可能的 How to take screenshots of running iPhone from background app? 尚待处理,但似乎也针对非越狱设备 How to take screenshot of entire screen on a Jailbroken iOS Device?(和 jailbroken iOS taking screenshot from background app 有点相似)似乎与我要找的很接近,但代码是用 Objective C 编写的,并使用了一个神秘的私有函数 _UICreateScreenUIImage()我不知道它是在哪个模块中定义的(所以我不知道如何将它声明为外部函数)。屏幕截图也作为 UIImage(参见 http://developer.xamarin.com/api/type/MonoTouch.UIKit.UIImage/)返回,我还不知道如何转换为 System.Drawing.Bitmap(参见 https://developer.xamarin.com/api/type/System.Drawing.Bitmap/)。

以下是我的代码的帖子。我正在寻求有关private System.Drawing.Bitmap ShootScreen()实施的帮助

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Threading;

namespace ***Snippets

    public sealed class ScreenshooterThread
    
        private Thread _t = null;                   // Thread-Object: Execution-Helper
        private Boolean _terminate = false;         // Stop current execution?
        private Int32 _intervalMS = 0;              // Take Screenshots every ? MS
        private Queue<Bitmap> _q;                   // Queue to store the Screenshots

        #region Interface
        /// <summary>
        /// Creates a Screenshooter
        /// </summary>
        /// <param name="intervalMS">Defines every how many MS a screenshot should be taken</param>
        public ScreenshooterThread(Int32 intervalMS = 200)
        
            _intervalMS = intervalMS;
        

        /// <summary>
        /// Starts taking Screenshots
        /// </summary>
        public void Run()
        
            if(this.IsRunning || this.IsTerminating)
            
                throw new InvalidOperationException("ScreenshooterThread is currently running or terminating");
            

            lock (this)
            
                _terminate = false;
                _t = new Thread(this.DoWork);
                _t.Start();
            
        

        /// <summary>
        /// Stops taking Screenshots
        /// </summary>
        public void Stop()
        
            if (!this.IsRunning) return; if (this.IsTerminating) return;

            lock(this)
            
                _terminate = true;
            
        

        /// <summary>
        /// Determines whether the Thread is currently running
        /// </summary>
        public Boolean IsRunning
        
            get
            
                if (_t == null) return false;

                return _t.IsAlive;
            
        
        /// <summary>
        /// Determines whether Thread-Termination has been invoked
        /// </summary>
        public Boolean IsTerminating
        
            get
            
                return _terminate;
            
        

        /// <summary>
        /// Get a Screenshot from the Queue. Returns null if none available
        /// </summary>
        public Bitmap Deqeue()
        
            lock(_q)
            
                if (_q.Count < 1) return null;
                return _q.Dequeue();
            
        

        #endregion

        #region Implementation Details
        /// <summary>
        /// Add a Screenshot to the Queue
        /// </summary>
        private void Enqueue(Bitmap b)
        
            lock(_q)
            
                _q.Enqueue(b);
            
        

        /// <summary>
        /// Task-Implementation while running
        /// </summary>
        private void DoWork()
        
            while(!_terminate)
            
                if (_intervalMS > 0) Thread.Sleep(_intervalMS);

                this.Enqueue(this.ShootScreen());
            

            this.CleanUp();
        

        /// <summary>
        /// Takes a Screenshot of the device even if the app is in the Background
        /// </summary>
        private System.Drawing.Bitmap ShootScreen()
        
            // System.Drawing.Bitmap supported by Xamarin
            // https://developer.xamarin.com/api/type/System.Drawing.Bitmap/

            // Method in ObjC: https://***.com/questions/33369014/how-to-take-screenshot-of-entire-screen-on-a-jailbroken-ios-device

            throw new NotImplementedException();
        
        /// <summary>
        /// Preparing for next launch
        /// </summary>
        private void CleanUp()
        
            lock(this)
            
                _terminate = false;
                _t = null;
            
        
        #endregion
    

PS:截图需要是System.Drawing.Bitmap,因为之后我需要对其进行Pixelwise操作

编辑 1

经过进一步调查,我在http://sharpmobilecode.com/introduction-to-xamarin-what-it-is-what-it-isnt/ 发现以下段落

例如,假设您现有的 C# 代码使用 System.Drawing.Bitmap。这是您编写的多个通用库 桌面应用参考。一种可能的方法如下所示:

using System.Drawing; 

public Bitmap GetBitmapFromCache(string fileName) 
 
   return (Bitmap) Image.FromFile(fileName); 
    

如果要在 Xamarin.iOS 或 Xamarin.android 应用程序中编译此方法,则会收到编译错误。嗯?之上 进一步调查,您会看到 System.Drawing.BitmapSystem.Drawing.Image 未定义。什么!?!?这是有效的 C# 句法!经过进一步调查,您只看到几个类 System.Drawing 实际上已定义(RectangleF、PointF 等)。在哪里 到底是位图和图像?嗯,这些是有充分理由的 Mono 框架中没有实现类。这是因为他们 不兼容跨平台。

这到底是什么意思?让我们看一下 System.Drawing MSDN 上的文档。它指出,“System.Drawing 命名空间 提供对 GDI+ 基本图形功能的访问。”。什么是 GDI+? GDI+(图形设备接口)是一个图形库 (Win32),它是 特定于 Windows 操作系统。所以在一个引擎盖下 System.Drawing.Bitmap,是一个 Win32 实现,与 Windows 桌面/服务器操作系统。这意味着 System.Drawing.Bitmap 和 System.Drawing.Image 是平台相关的, 并且仅适用于 Windows 桌面/服务器应用程序。你将无法 在 iOS 应用、Android 应用甚至 Windows Phone 上使用这些类 应用。那么如何在 iOS 或 Android 中使用当前的 C# 代码 应用程序?稍作修改如下:

public byte[] GetBitmapFromCache(string fileName) 
 
    return File.ReadAllBytes(fileName); 
 

iOS 和 Android 没有 GDI+ 位图的概念 (System.Drawing.Bitmap),但是,每个平台都知道如何转换 一个字节数组自己实现的位图。使用 iOS,您可以从字节数组创建 UIImage,而在 Android 您可以从同一个数组创建一个 Android.Graphics.Bitmap。

【问题讨论】:

【参考方案1】:

如http://sharpmobilecode.com/introduction-to-xamarin-what-it-is-what-it-isnt/ 中所述,无法创建System.Drawing.Bitmap,因为System.Drawing 命名空间提供了对Windows 操作系统特有的GDI+ 基础图形功能的访问,因此在iOS 上根本不可用。

但是,可以将屏幕截图创建为UIImage,如下所示

    How to take screenshot of entire screen on a Jailbroken iOS Device? 表明,存在一个私有函数 _UICreateScreenUIImage(void),它返回所需的屏幕截图。为了访问这个函数,必须声明它

    using System.Runtime.InteropServices;
    using ObjCRuntime;
    
    [DLLImport(/*ObjCRuntime.*/Constants.UIKitLibrary)]
    extern static IntPtr _UICreateScreenUIImage();
    

    调用时,返回的IntPtr指向一个原生的UIImage,需要包装成一个托管对象,由Runtime.GetNSObject(IntPtr)完成

    IntPtr ptr = _UICreateScreenUIImage();
    UIImage uiimg = Runtime.GetNSObject(ptr) as UIImage;
    

    生成的uiimg 包含屏幕截图

所附图片包含一个关于如何创建屏幕截图并将其保存为.jpeg的简约示例

还有一个应用程序在后台时屏幕截图的示例

【讨论】:

以上是关于iOS 屏幕截图为来自后台应用程序的 System.Drawing.Bitmap (Xamarin)的主要内容,如果未能解决你的问题,请参考以下文章

防止 iOS 在进入后台之前对应用进行屏幕截图

IOSurface - IOS 私有 API - 在后台捕获屏幕截图

来自原生 iOS 的 Unity AR 视图的屏幕截图

iOS - 来自后台时上一个屏幕闪烁

我需要我没有的设备的 iOS 屏幕截图

如何在后台线程上截取 UIView 的屏幕截图?