【发布时间】:2011-02-09 07:57:21
【问题描述】:
如何不通过任何程序而是通过代码截取手机屏幕的选定区域?
【问题讨论】:
-
不是来自模拟器。我需要对我的程序的一部分进行截图,并在同一个程序中使用它。
-
另一个很好的参考:stackoverflow.com/a/10296881/439171
标签: android screenshot
如何不通过任何程序而是通过代码截取手机屏幕的选定区域?
【问题讨论】:
标签: android screenshot
这是允许我的屏幕截图存储在 SD 卡上的代码,以便以后用于您的任何需求:
首先,您需要添加适当的权限来保存文件:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
这是代码(在 Activity 中运行):
private void takeScreenshot() {
Date now = new Date();
android.text.format.DateFormat.format("yyyy-MM-dd_hh:mm:ss", now);
try {
// image naming and path to include sd card appending name you choose for file
String mPath = Environment.getExternalStorageDirectory().toString() + "/" + now + ".jpg";
// create bitmap screen capture
View v1 = getWindow().getDecorView().getRootView();
v1.setDrawingCacheEnabled(true);
Bitmap bitmap = Bitmap.createBitmap(v1.getDrawingCache());
v1.setDrawingCacheEnabled(false);
File imageFile = new File(mPath);
FileOutputStream outputStream = new FileOutputStream(imageFile);
int quality = 100;
bitmap.compress(Bitmap.CompressFormat.JPEG, quality, outputStream);
outputStream.flush();
outputStream.close();
openScreenshot(imageFile);
} catch (Throwable e) {
// Several error may come out with file handling or DOM
e.printStackTrace();
}
}
您可以这样打开最近生成的图像:
private void openScreenshot(File imageFile) {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
Uri uri = Uri.fromFile(imageFile);
intent.setDataAndType(uri, "image/*");
startActivity(intent);
}
如果您想在片段视图上使用它,请使用:
View v1 = getActivity().getWindow().getDecorView().getRootView();
而不是
View v1 = getWindow().getDecorView().getRootView();
关于takeScreenshot()函数
注意:
如果您的对话框包含表面视图,则此解决方案不起作用。详情请查看以下问题的答案:
【讨论】:
mCurrentUrlMask 必须是 View,因为它是 Android API 中具有 getRootView() 方法的唯一类。可能是 UI 中的视图。
View v1 = mCurrentUrlMask.getRootView(); 我用过View v1 = getWindow().getDecorView().getRootView();,它对我有用。
调用此方法,传入您想要截屏的最外层 ViewGroup:
public Bitmap screenShot(View view) {
Bitmap bitmap = Bitmap.createBitmap(view.getWidth(),
view.getHeight(), Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
view.draw(canvas);
return bitmap;
}
【讨论】:
view是什么?`
getWindow().getDecorView().getRootView() 作为视图会导致只截取应用程序而不是屏幕的屏幕截图。
注意:仅适用于root手机
您可以通过编程方式运行adb shell /system/bin/screencap -p /sdcard/img.png,如下所示
Process sh = Runtime.getRuntime().exec("su", null,null);
OutputStream os = sh.getOutputStream();
os.write(("/system/bin/screencap -p " + "/sdcard/img.png").getBytes("ASCII"));
os.flush();
os.close();
sh.waitFor();
然后将img.png 读作Bitmap 并按照您的意愿使用。
【讨论】:
System.err : java.io.IOException: Error running exec(). Command: [su] Working Directory: null Environment: null ,我的设备是android 5.0
无需root权限或无需大编码。
在 adb shell 上使用下面的命令你可以截屏。
input keyevent 120
此命令不需要任何root权限,因此您也可以从android应用程序的java代码执行。
Process process;
process = Runtime.getRuntime().exec("input keyevent 120");
更多关于 android 中的 keyevent 代码见http://developer.android.com/reference/android/view/KeyEvent.html
这里我们用过。 KEYCODE_SYSRQ 其值为 120,用于 System Request / Print Screen 键。
正如CJBS所说,输出的图片会保存在/sdcard/Pictures/Screenshots
【讨论】:
/sdcard/Pictures/Screenshots
su -c "input keyevent 120" 没问题!!
Mualig 的答案非常好,但我遇到了 Ewoks 描述的同样问题,我没有了解背景。所以有时就足够了,有时我会在黑色背景上看到黑色文本(取决于主题)。
此解决方案主要基于 Mualig 代码和我在 Robotium 中找到的代码。我通过直接调用 draw 方法来放弃使用绘图缓存。在此之前,我将尝试从当前活动中获取可绘制的背景以首先绘制它。
// Some constants
final static String SCREENSHOTS_LOCATIONS = Environment.getExternalStorageDirectory().toString() + "/screenshots/";
// Get device dimmensions
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
// Get root view
View view = mCurrentUrlMask.getRootView();
// Create the bitmap to use to draw the screenshot
final Bitmap bitmap = Bitmap.createBitmap(size.x, size.y, Bitmap.Config.ARGB_4444);
final Canvas canvas = new Canvas(bitmap);
// Get current theme to know which background to use
final Activity activity = getCurrentActivity();
final Theme theme = activity.getTheme();
final TypedArray ta = theme
.obtainStyledAttributes(new int[] { android.R.attr.windowBackground });
final int res = ta.getResourceId(0, 0);
final Drawable background = activity.getResources().getDrawable(res);
// Draw background
background.draw(canvas);
// Draw views
view.draw(canvas);
// Save the screenshot to the file system
FileOutputStream fos = null;
try {
final File sddir = new File(SCREENSHOTS_LOCATIONS);
if (!sddir.exists()) {
sddir.mkdirs();
}
fos = new FileOutputStream(SCREENSHOTS_LOCATIONS
+ System.currentTimeMillis() + ".jpg");
if (fos != null) {
if (!bitmap.compress(Bitmap.CompressFormat.JPEG, 90, fos)) {
Log.d(LOGTAG, "Compress/Write failed");
}
fos.flush();
fos.close();
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
【讨论】:
作为参考,捕获屏幕(而不仅仅是您的应用活动)的一种方法是捕获 帧缓冲区(设备 /dev/graphics/fb0)。为此,您必须具有 root 权限,或者您的应用程序必须是具有 signature permissions 的应用程序(“仅当请求应用程序使用与声明权限的应用程序相同的证书签名时,系统才会授予该权限”) - 其中除非您自己编译 ROM,否则不太可能。
从我测试过的几台设备中捕获的每个帧缓冲区都包含恰好一个屏幕截图。人们报告它包含更多,我想这取决于框架/显示大小。
我尝试连续读取帧缓冲区,但它似乎返回了固定数量的读取字节。在我的例子中是 (3 410 432) 字节,这足以存储 854*480 RGBA(3 279 360 字节)的显示帧。是的,从 fb0 输出的二进制帧在我的设备中是 RGBA。这很可能取决于设备。这对你解码很重要=)
在我的设备 /dev/graphics/fb0 权限中,只有 root 和图形组中的用户才能读取 fb0。
graphics 是一个受限组,因此您可能只能使用 su 命令通过 root 手机访问 fb0。
Android 应用具有 用户 id (uid) = app_## 和 组 id (guid) = app_##。
adb shell 有 uid = shell 和 guid = shell,其权限比应用程序多得多。您实际上可以在 /system/permissions/platform.xml 中检查这些权限
这意味着您将能够在没有 root 的情况下在 adb shell 中读取 fb0,但在没有 root 的情况下您将无法在应用程序中读取它。
此外,在 AndroidManifest.xml 上授予 READ_FRAME_BUFFER 和/或 ACCESS_SURFACE_FLINGER 权限对于常规应用没有任何作用,因为这些仅适用于“signature”应用。
也可以查看此closed thread了解更多详情。
【讨论】:
private void captureScreen() {
View v = getWindow().getDecorView().getRootView();
v.setDrawingCacheEnabled(true);
Bitmap bmp = Bitmap.createBitmap(v.getDrawingCache());
v.setDrawingCacheEnabled(false);
try {
FileOutputStream fos = new FileOutputStream(new File(Environment
.getExternalStorageDirectory().toString(), "SCREEN"
+ System.currentTimeMillis() + ".png"));
bmp.compress(CompressFormat.PNG, 100, fos);
fos.flush();
fos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
在清单中添加权限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
如需支持Marshmallow或以上版本,请在activity onCreate方法中添加如下代码
ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},00);
【讨论】:
我的解决办法是:
public static Bitmap loadBitmapFromView(Context context, View v) {
DisplayMetrics dm = context.getResources().getDisplayMetrics();
v.measure(MeasureSpec.makeMeasureSpec(dm.widthPixels, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(dm.heightPixels, MeasureSpec.EXACTLY));
v.layout(0, 0, v.getMeasuredWidth(), v.getMeasuredHeight());
Bitmap returnedBitmap = Bitmap.createBitmap(v.getMeasuredWidth(),
v.getMeasuredHeight(), Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(returnedBitmap);
v.draw(c);
return returnedBitmap;
}
和
public void takeScreen() {
Bitmap bitmap = ImageUtils.loadBitmapFromView(this, view); //get Bitmap from the view
String mPath = Environment.getExternalStorageDirectory() + File.separator + "screen_" + System.currentTimeMillis() + ".jpeg";
File imageFile = new File(mPath);
OutputStream fout = null;
try {
fout = new FileOutputStream(imageFile);
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, fout);
fout.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
fout.close();
}
}
图像保存在外部存储文件夹中。
【讨论】:
ImageUtils.loadBitmapFromView(this, view)的view是什么?
您可以尝试以下库: http://code.google.com/p/android-screenshot-library/ Android 屏幕截图库 (ASL) 能够以编程方式从 Android 设备捕获屏幕截图,而无需拥有 root 访问权限。相反,ASL 使用在后台运行的本机服务,每次设备启动时通过 Android 调试桥 (ADB) 启动一次。
【讨论】:
根据上面@JustinMorris 和@NiravDangi 这里https://stackoverflow.com/a/8504958/2232148 的回答,我们必须将视图的背景和前景组合起来,如下所示:
public static Bitmap takeScreenshot(View view, Bitmap.Config quality) {
Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), quality);
Canvas canvas = new Canvas(bitmap);
Drawable backgroundDrawable = view.getBackground();
if (backgroundDrawable != null) {
backgroundDrawable.draw(canvas);
} else {
canvas.drawColor(Color.WHITE);
}
view.draw(canvas);
return bitmap;
}
质量参数采用 Bitmap.Config 常量,通常为 Bitmap.Config.RGB_565 或 Bitmap.Config.ARGB_8888。
【讨论】:
public class ScreenShotActivity extends Activity{
private RelativeLayout relativeLayout;
private Bitmap myBitmap;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
relativeLayout = (RelativeLayout)findViewById(R.id.relative1);
relativeLayout.post(new Runnable() {
public void run() {
//take screenshot
myBitmap = captureScreen(relativeLayout);
Toast.makeText(getApplicationContext(), "Screenshot captured..!", Toast.LENGTH_LONG).show();
try {
if(myBitmap!=null){
//save image to SD card
saveImage(myBitmap);
}
Toast.makeText(getApplicationContext(), "Screenshot saved..!", Toast.LENGTH_LONG).show();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
}
public static Bitmap captureScreen(View v) {
Bitmap screenshot = null;
try {
if(v!=null) {
screenshot = Bitmap.createBitmap(v.getMeasuredWidth(),v.getMeasuredHeight(), Config.ARGB_8888);
Canvas canvas = new Canvas(screenshot);
v.draw(canvas);
}
}catch (Exception e){
Log.d("ScreenShotActivity", "Failed to capture screenshot because:" + e.getMessage());
}
return screenshot;
}
public static void saveImage(Bitmap bitmap) throws IOException{
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 40, bytes);
File f = new File(Environment.getExternalStorageDirectory() + File.separator + "test.png");
f.createNewFile();
FileOutputStream fo = new FileOutputStream(f);
fo.write(bytes.toByteArray());
fo.close();
}
}
添加权限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
【讨论】:
你可以尝试做这样的事情,
通过执行类似的操作从布局或视图中获取位图缓存
首先你必须setDrawingCacheEnabled 到一个布局(一个线性布局或相对布局,或一个视图)
然后
Bitmap bm = layout.getDrawingCache()
然后你可以对位图做任何你想做的事情。要么将其转换为图像文件,要么将位图的 uri 发送到其他地方。
【讨论】:
捷径是
FrameLayout layDraw = (FrameLayout) findViewById(R.id.layDraw); /*Your root view to be part of screenshot*/
layDraw.buildDrawingCache();
Bitmap bmp = layDraw.getDrawingCache();
【讨论】:
这个问题的大部分答案都使用Canvas绘图方法或绘图缓存方法。但是,View.setDrawingCache() method is deprecated in API 28。目前推荐的用于制作屏幕截图的 API 是 API 24 提供的 PixelCopy 类(但接受 Window 参数的方法可从 API 26 == Android 8.0 Oreo 获得)。这是用于检索Bitmap 的示例 Kotlin 代码:
@RequiresApi(Build.VERSION_CODES.O)
fun saveScreenshot(view: View) {
val window = (view.context as Activity).window
if (window != null) {
val bitmap = Bitmap.createBitmap(view.width, view.height, Bitmap.Config.ARGB_8888)
val locationOfViewInWindow = IntArray(2)
view.getLocationInWindow(locationOfViewInWindow)
try {
PixelCopy.request(window, Rect(locationOfViewInWindow[0], locationOfViewInWindow[1], locationOfViewInWindow[0] + view.width, locationOfViewInWindow[1] + view.height), bitmap, { copyResult ->
if (copyResult == PixelCopy.SUCCESS) {
saveBitmap(bitmap)
}
// possible to handle other result codes ...
}, Handler())
} catch (e: IllegalArgumentException) {
// PixelCopy may throw IllegalArgumentException, make sure to handle it
}
}
}
【讨论】:
对于那些想要捕获 GLSurfaceView 的人,getDrawingCache 或绘图到画布方法将不起作用。
您必须在帧被渲染后读取 OpenGL 帧缓冲区的内容。有个好答案here
【讨论】:
我创建了一个简单的库,它从View 截取屏幕截图,并为您提供一个位图对象或将其直接保存到您想要的任何路径
【讨论】:
Screenshot.takeScreenshot(view, mPath); imageView.setImageBitmap(Screenshot.getBitmapScreenshot(view, mPath)); 如何查看当前活动。我不想使用 xml 的 id。
takeScreenshotForScreen()。
如果您想从fragment 截取屏幕截图,请按照以下步骤操作:
覆盖onCreateView():
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_one, container, false);
mView = view;
}
截图逻辑:
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
View view = mView.findViewById(R.id.scrollView1);
shareScreenShotM(view, (NestedScrollView) view);
}
方法shareScreenShotM)():
public void shareScreenShotM(View view, NestedScrollView scrollView){
bm = takeScreenShot(view,scrollView); //method to take screenshot
File file = savePic(bm); // method to save screenshot in phone.
}
方法 takeScreenShot():
public Bitmap takeScreenShot(View u, NestedScrollView z){
u.setDrawingCacheEnabled(true);
int totalHeight = z.getChildAt(0).getHeight();
int totalWidth = z.getChildAt(0).getWidth();
Log.d("yoheight",""+ totalHeight);
Log.d("yowidth",""+ totalWidth);
u.layout(0, 0, totalWidth, totalHeight);
u.buildDrawingCache();
Bitmap b = Bitmap.createBitmap(u.getDrawingCache());
u.setDrawingCacheEnabled(false);
u.destroyDrawingCache();
return b;
}
方法 savePic():
public static File savePic(Bitmap bm){
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
bm.compress(Bitmap.CompressFormat.JPEG, 100, bytes);
File sdCardDirectory = new File(Environment.getExternalStorageDirectory() + "/Foldername");
if (!sdCardDirectory.exists()) {
sdCardDirectory.mkdirs();
}
// File file = new File(dir, fileName);
try {
file = new File(sdCardDirectory, Calendar.getInstance()
.getTimeInMillis() + ".jpg");
file.createNewFile();
new FileOutputStream(file).write(bytes.toByteArray());
Log.d("Fabsolute", "File Saved::--->" + file.getAbsolutePath());
Log.d("Sabsolute", "File Saved::--->" + sdCardDirectory.getAbsolutePath());
} catch (IOException e) {
e.printStackTrace();
}
return file;
}
对于活动,您可以简单地使用View v1 = getWindow().getDecorView().getRootView(); 而不是mView
【讨论】:
只是扩展 taraloca 的答案。您必须添加以下行才能使其工作。 我已将图像名称设为静态。如果您需要动态图像名称,请确保使用 taraloca 的时间戳变量。
// Storage Permissions
private static final int REQUEST_EXTERNAL_STORAGE = 1;
private static String[] PERMISSIONS_STORAGE = {
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
};
private void verifyStoragePermissions() {
// Check if we have write permission
int permission = ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
if (permission != PackageManager.PERMISSION_GRANTED) {
// We don't have permission so prompt the user
ActivityCompat.requestPermissions(this, PERMISSIONS_STORAGE, REQUEST_EXTERNAL_STORAGE);
}else{
takeScreenshot();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
if (requestCode == REQUEST_EXTERNAL_STORAGE) {
takeScreenshot();
}
}
}
并且在 AndroidManifest.xml 文件中必须有以下条目:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
【讨论】:
全页滚动截图
如果您想捕获完整的视图屏幕截图(其中包含滚动视图左右),请查看此库
你所要做的就是导入Gradel,并创建一个BigScreenshot对象
BigScreenshot longScreenshot = new BigScreenshot(this, x, y);
将收到一个回调,其中包含在自动滚动屏幕视图组并最终组合在一起时拍摄的屏幕截图的位图。
@Override public void getScreenshot(Bitmap bitmap) {}
哪些可以保存到画廊中,或者在他们之后需要任何使用
【讨论】:
仅适用于系统应用程序!
Process process;
process = Runtime.getRuntime().exec("screencap -p " + outputPath);
process.waitFor();
注意:系统应用程序不需要运行“su”来执行该命令。
【讨论】:
参数视图是根布局对象。
public static Bitmap screenShot(View view) {
Bitmap bitmap = null;
if (view.getWidth() > 0 && view.getHeight() > 0) {
bitmap = Bitmap.createBitmap(view.getWidth(),
view.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
view.draw(canvas);
}
return bitmap;
}
【讨论】:
从 Android 11(API 级别 30)开始,您可以使用无障碍服务截屏:
takeScreenshot - 截取指定显示的屏幕截图并通过 AccessibilityService.ScreenshotResult 返回。
【讨论】:
在android中截取一个视图。
public static Bitmap getViewBitmap(View v) {
v.clearFocus();
v.setPressed(false);
boolean willNotCache = v.willNotCacheDrawing();
v.setWillNotCacheDrawing(false);
int color = v.getDrawingCacheBackgroundColor();
v.setDrawingCacheBackgroundColor(0);
if (color != 0) {
v.destroyDrawingCache();
}
v.buildDrawingCache();
Bitmap cacheBitmap = v.getDrawingCache();
if (cacheBitmap == null) {
return null;
}
Bitmap bitmap = Bitmap.createBitmap(cacheBitmap);
v.destroyDrawingCache();
v.setWillNotCacheDrawing(willNotCache);
v.setDrawingCacheBackgroundColor(color);
return bitmap;
}
【讨论】:
如果要截取视图的屏幕截图,请使用View::drawToBitmap扩展功能:
val bitmap = myTargetView.drawToBitmap(/*Optional:*/ Bitmap.Config.ARGB_8888)
请务必使用 -ktx 版本的AndroidX Core 库:
implementation("androidx.core:core-ktx:1.6.0")
我已经回答过类似的问题here。
【讨论】:
如果您想捕获 RelativeLayout 或 LinearLayout 等视图或布局。
只需使用代码:
LinearLayout llMain = (LinearLayout) findViewById(R.id.linearlayoutMain);
Bitmap bm = loadBitmapFromView(llMain);
现在您可以通过以下方式将此位图保存在设备存储中:
FileOutputStream outStream = null;
File f=new File(Environment.getExternalStorageDirectory()+"/Screen Shots/");
f.mkdir();
String extStorageDirectory = f.toString();
File file = new File(extStorageDirectory, "my new screen shot");
pathOfImage = file.getAbsolutePath();
try {
outStream = new FileOutputStream(file);
bm.compress(Bitmap.CompressFormat.PNG, 100, outStream);
Toast.makeText(getApplicationContext(), "Saved at "+f.getAbsolutePath(), Toast.LENGTH_LONG).show();
addImageGallery(file);
//mail.setEnabled(true);
flag=true;
} catch (FileNotFoundException e) {e.printStackTrace();}
try {
outStream.flush();
outStream.close();
} catch (IOException e) {e.printStackTrace();}
【讨论】: