刚开始打算做一个简单的截屏程序时,以为很轻松就能搞定。
在Activity上放一个按钮,点击完成截屏操作,并将数据以图片形式保存在手机中。
动手之前,自然是看书和网上各种查资料。结果发现了解的知识越多,就越发感觉不对劲。
截屏,总以为其类似于其他小应用的开发,有现成的接口或者只需要稍微改动就能达到预期的效果。
一般讲解Android的书籍并没有提到截屏的内容,网上的文章很多,但也没有哪篇文章能真正完整地把解决思路和具体实现说清楚的。
总结的比较合理的一篇文章为Android截屏学习经历。
直白点说,就是在Windows平台下,不root,不签名,不......,就很难做到将手机整个屏幕截取下来(包括状态栏)
1、先介绍一下将应用程序本身的界面截取下来的方法,比较简单,不过对于手机屏幕上的其他信息就不会发挥任何作用了。如状态栏或者其他应用的界面。
View viewScreen = getWindow().getDecorView();
viewScreen.setDrawingCacheEnabled(true);
viewScreen.buildDrawingCache();
Bitmap bitmap = Bitmap.createBitmap(viewScreen.getDrawingCache(),0,0,windowWidth,windowHeight);
viewScreen.destroyDrawingCache();
imgScreen.setImageBitmap(bitmap);
其中,viewScreen.getDrawingCache()方法获取屏幕信息,通过ImageView对象imgScreen显示出来,效果如下:
可以看出,截取的部分只是为当前应用的界面,状态栏信息无法获取。中间的图案为imgView的初始显示内容,为手机桌面。
顺便提一下,桌面获取与ImageView视图显示为:
imgScreen.setImageDrawable(getWallpaper());
这其实从调用方法也可以知道,getWindow().getDecorView()是针对当前视图(View)的,并不是针对手机整个屏幕的。
2、接下来看一段比较有诱惑性的代码,出自这里。
public void screenShot() throws InterruptedException
{
Process sh;
try
{
sh = Runtime.getRuntime().exec("su", null, null);
OutputStream os = sh.getOutputStream();
os.write(("/system/bin/screencap -p " + "/sdcard/Image.png").getBytes("ASCII"));
os.flush();
os.close();
sh.waitFor();
}
catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
} }
个人没有在Linux下进行测试,如果哪位朋友有这方面的开发经验,还望分享与指点。
但从代码来看,如果没有其他约束(如手机权限、应用签名等)的话,是多么简单明了。
3、旧版本的Android API其实是有关于截屏的接口,只不过被Google隐藏了,所以还是不能轻易使用。
资料中也提到不少API中的截屏函数:screenshot()。
4、而在新版本中,Google在Examples中给出了一个样例:ScreenCapture工程,环境为Android Studio。
本人的API版本为22,工程路径为“Android\sdk\samples\android-22\media\ScreenCapture”。
找到时确实激动一番,马上导入、运行,应用界面成功出现了,点击 开始按钮,效果如下:
结果又很有趣,出现了一直截取的现象。很眼熟,在前后墙都装上镜子就会出现同样的场景了。
样例的实现是点击START就开始不断截屏,点击STOP就停止。
到手机文件管理中去找了一通,没发现有任何新的图片保存下来,起初以为Google只是没有做将屏幕数据保存为图片这一步。
去看源码之前还是抱有希望的,想着自己可以马上实现从data-->image的这一步。
5、程序中用到了Fragment,FragmentActivity。
将截取下来的屏幕信息显示在Fragment对象中,而该对象又作为主视图的一部分,及上图中的上半部分为主Activity视图,下半部分为Fragment部分。
主Activity中做的事情就是打开继承自Fragment类ScreenCaptureFragment的事务:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
ScreenCaptureFragment fragment = new ScreenCaptureFragment(); transaction.replace(R.id.sample_content_fragment, fragment);
transaction.commit();
}
}
关键类ScreenCaptureFragment的实现代码为:
package com.example.android.screencapture; import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.media.Image;
import android.media.ImageReader;
import android.media.projection.MediaProjection;
import android.media.projection.MediaProjectionManager;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.Toast;
import java.io.IOException; public class ScreenCaptureFragment extends Fragment implements View.OnClickListener { private static final String TAG = "ScreenCaptureFragment"; private static final String STATE_RESULT_CODE = "result_code";
private static final String STATE_RESULT_DATA = "result_data"; private static final int REQUEST_MEDIA_PROJECTION = 1; private int mScreenDensity; private int mResultCode;
private Intent mResultData; private Surface mSurface;
private MediaProjection mMediaProjection;
private VirtualDisplay mVirtualDisplay;
private MediaProjectionManager mMediaProjectionManager;
private Button mButtonToggle;
private SurfaceView mSurfaceView; @Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
mResultCode = savedInstanceState.getInt(STATE_RESULT_CODE);
mResultData = savedInstanceState.getParcelable(STATE_RESULT_DATA);
}
} @Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_screen_capture, container, false);
} @Override
public void onViewCreated(View view, Bundle savedInstanceState) {
mSurfaceView = (SurfaceView) view.findViewById(R.id.surface);
mSurface = mSurfaceView.getHolder().getSurface();
mButtonToggle = (Button) view.findViewById(R.id.toggle);
mButtonToggle.setOnClickListener(this);
} @Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Activity activity = getActivity();
DisplayMetrics metrics = new DisplayMetrics();
activity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
mScreenDensity = metrics.densityDpi;
mMediaProjectionManager = (MediaProjectionManager)
activity.getSystemService(Context.MEDIA_PROJECTION_SERVICE);
} @Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (mResultData != null) {
outState.putInt(STATE_RESULT_CODE, mResultCode);
outState.putParcelable(STATE_RESULT_DATA, mResultData);
}
} @Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.toggle:
if (mVirtualDisplay == null) {
try {
startScreenCapture();
} catch (IOException e) {
e.printStackTrace();
}
} else {
stopScreenCapture();
}
break;
}
} @Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_MEDIA_PROJECTION) {
if (resultCode != Activity.RESULT_OK) {
Toast.makeText(getActivity(), R.string.user_cancelled, Toast.LENGTH_SHORT).show();
return;
}
Activity activity = getActivity();
if (activity == null) {
return;
} mResultCode = resultCode;
mResultData = data;
setUpMediaProjection();
try {
setUpVirtualDisplay();
} catch (IOException e) {
e.printStackTrace();
}
}
} @Override
public void onPause() {
super.onPause();
stopScreenCapture();
} @Override
public void onDestroy() {
super.onDestroy();
tearDownMediaProjection();
} @TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void setUpMediaProjection() {
mMediaProjection = mMediaProjectionManager.getMediaProjection(mResultCode, mResultData);
} @TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void tearDownMediaProjection() {
if (mMediaProjection != null) {
mMediaProjection.stop();
mMediaProjection = null;
}
} @TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void startScreenCapture() throws IOException {
Activity activity = getActivity();
if (mSurface == null || activity == null) {
return;
}
if (mMediaProjection != null) {
setUpVirtualDisplay();
} else if (mResultCode != 0 && mResultData != null) {
setUpMediaProjection();
setUpVirtualDisplay();
} else {
startActivityForResult(
mMediaProjectionManager.createScreenCaptureIntent(),
REQUEST_MEDIA_PROJECTION);
}
} @TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void setUpVirtualDisplay() throws IOException { mVirtualDisplay = mMediaProjection.createVirtualDisplay("ScreenCapture",
180 mSurfaceView.getWidth(), mSurfaceView.getHeight(), mScreenDensity,
181 DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
182 mSurface, null, null); mButtonToggle.setText(R.string.stop);
} private void stopScreenCapture() {
if (mVirtualDisplay == null) {
return;
}
mVirtualDisplay.release();
mVirtualDisplay = null;
mButtonToggle.setText(R.string.start);
} }
上面高亮的代码作用是将截屏信息显示在界面下方Fragment的SurfaceView中,完全没有data的影子。
6、继续查资料,在老外的文章中找到了一些零散的建议与代码,总结之后,实现代码如下:
public void takeScreenshot2(View v){
MediaProjectionManager projectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
Intent intent = projectionManager.createScreenCaptureIntent();
startActivity(intent); int mWidth = mWindowManager.getDefaultDisplay().getWidth();
int mHeight = mWindowManager.getDefaultDisplay().getHeight();
ImageReader mImageReader = ImageReader.newInstance(mWidth, mHeight, ImageFormat.RGB_565, 2);
DisplayMetrics metrics = new DisplayMetrics();
mWindowManager.getDefaultDisplay().getMetrics(metrics);
int mScreenDensity = metrics.densityDpi; MediaProjection mProjection = projectionManager.getMediaProjection(1, intent);
final VirtualDisplay virtualDisplay = mProjection.createVirtualDisplay("screen-mirror",
mWidth, mHeight, mScreenDensity, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
mImageReader.getSurface(), null, null);
Image image = mImageReader.acquireLatestImage();
final Image.Plane[] planes = image.getPlanes();
final ByteBuffer buffer = planes[0].getBuffer();
int offset = 0;
int pixelStride = planes[0].getPixelStride();
int rowStride = planes[0].getRowStride();
int rowPadding = rowStride - pixelStride * mWidth;
Bitmap bitmap = Bitmap.createBitmap(mWidth+rowPadding/pixelStride, mHeight, Bitmap.Config.RGB_565);
bitmap.copyPixelsFromBuffer(buffer);
image.close(); SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy_MM_dd_hh_mm_ss");
String strDate = dateFormat.format(new java.util.Date());
String pathImage = Environment.getExternalStorageDirectory().getPath()+"/Pictures/";
String nameImage = pathImage+strDate+".png";
if(bitmap != null) {
try{
File fileImage = new File(nameImage);
if(!fileImage.exists()){
fileImage.createNewFile();
}
FileOutputStream out = new FileOutputStream(fileImage);
if(out != null){
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
out.flush();
out.close();
Toast.makeText(this,"get phone's screen succeed",Toast.LENGTH_SHORT).show();
Intent media = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
Uri contentUri = Uri.fromFile(fileImage);
media.setData(contentUri);
getApplicationContext().sendBroadcast(media);
}
}catch(FileNotFoundException e) {
e.printStackTrace();
}catch (IOException e){
e.printStackTrace();
}
}
else{
Toast.makeText(this,"cannot get phone's screen",Toast.LENGTH_SHORT).show();
}
}
理想中,这段代码可以实现的功能有:
a、截取手机整个屏幕信息;
b、将屏幕信息利用ImageReader的acquireLatestImage()保存入Image对象;
c、通过缓存读取方式赋给Bitmap对象;
d、有了Bitmap,接下来就不解释了;
但是,一运行就出现异常,还没来得及截程序就终止了。
希望有兴趣的朋友可以一起交流与学习,有已经实现了该功能的大神那就最好了,求教。