Aandroid截屏遇到SurfaceView

最近做项目遇到个问题,网上搜索了一下,很多人也遇到这个问题,觉得很有必要拿出来说一下,做个总结!嗯,内容精华,千万别错过!

项目具体的需求说起来又要大费周章说半天,我就直接简单地说下我们的目的是什么,很简单就是把拍摄的照片和自定义view原样显示(简单理解就是截屏)

  如下图空白区域就是SurfaceView,空白区域的上方有个刻度尺,我们的目的就是要使拍摄的照片上存在这个刻度尺,这个刻度尺是自定义view是可调的(颜色,宽度,刻度间距等都可调)

初期我是没有想到截屏方案的,初期的方案 :将拍照前的尺子view画到拍摄后的图片上,但是画上去的view是初始时的view行不通

Aandroid截屏遇到SurfaceView_第1张图片

后来想到使用截屏功能,截屏后将图片进行裁剪只保留中间白色区域部分岂不快哉?嗯,感觉前途很光明啊,按下关机音量下按键,一阵舒爽的截屏声音后留下了一地的..额,不,是留下了一张图片,嗯很符合我的要求嘛.

于是就是网上烂大街的代码,不做过多解释,加几句注释方便各位理解

public static Bitmap takeScreenShot(Activity act) {  
    if (act == null || act.isFinishing()) {  
        Log.d(TAG, "act参数为空.");  
        return null;  
    }  
  
    // 获取当前视图的view ,获取顶层view视图,网上还有view.getRootView 其实本质都是一样的,就是获取根view(前台视图,你能看到的)
    View scrView = act.getWindow().getDecorView();  
    scrView.setDrawingCacheEnabled(true);  
    scrView.buildDrawingCache(true);  
  
    // 获取状态栏高度  
    Rect statuBarRect = new Rect();  
    scrView.getWindowVisibleDisplayFrame(statuBarRect);  
    int statusBarHeight = statuBarRect.top;  
    int width = act.getWindowManager().getDefaultDisplay().getWidth();  
    int height = act.getWindowManager().getDefaultDisplay().getHeight();  
  
    Bitmap scrBmp = null;  
    try {  
        // 去掉标题栏的截图  
        scrBmp = Bitmap.createBitmap( scrView.getDrawingCache(), 0, statusBarHeight,  
                width, height - statusBarHeight);  
    } catch (IllegalArgumentException e) {  
        Log.d("", "#### 旋转屏幕导致去掉状态栏失败");  
    }  
    scrView.setDrawingCacheEnabled(false);  
    scrView.destroyDrawingCache();  
    return scrBmp;  
}  
对于一般的截图,上面的代码就够用了,但是当你遇到SurfaceView你就崩溃了,怎么截屏出来的图片SurfaceView区域是黑色的?

简单地说这是因为SurfaceView的特性决定的,我们知道安卓中唯一可以在子线程中进行绘制的view就只有Surfaceview了,他可以独立于子线程中绘制,不会导致主线程的卡顿,至于造成surfaceView黑屏的原因,可以移步这里,写得很清楚了,我就不在多加阐述了

http://blog.csdn.net/luoshengyang/article/details/8661317  其实个人的建议对于一些过于底层的知识点不必去刨根问底,意义不大,首先用到的很少,花费的时间很多,得不偿失,完全可以用这些时间去学习新技术更划算(仅仅是个人意见)

http://www.cnblogs.com/lipeil/archive/2012/08/31/2666187.html移步这里看下view和SurfaceView的区别


如果你想刨根问底,那么这个适合你...http://blog.csdn.net/luoshengyang/article/details/8661317

既然截屏是黑色的,理所当然的我们就是要去克服这个缺陷,实在克服不了就去规避,苦逼的程序员...

既然自己无法去通过代码去截屏,那么我们就只能通过使用系统的按键去截屏,两行代码即可实现

new Thread(new Runnable() {
			public void run() {

				try {
					Instrumentation inst = new Instrumentation();
					inst.sendKeyDownUpSync(KeyEvent.KEYCODE_SYSRQ);

				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}).start();

其实这两行代码就是按下音量下和关机键而已,然后图片获取了,因为我做的应用是定制在设备上的,所以截屏目录固定,直接去获取即可.到这里可能你认为我们离成功应该差不多了!错了,你会发现这段代码没有任何效果,因为是用的这两行代码需要系统级权限....不过还好我的应用是定制在专用的手机上,所以可以拿到签名,具体操作如下

1 清单文件的根节点 添加属性 表示配置应用运行在系统进程中  仅仅是这句话是不够的

android:sharedUserId="android.uid.system"

2 找驱动工程师要到platform.pk8和platform.x509.pem两个文件进行签名,其实大多数人在这里就会跪了,除了应用为某部手机定制的,不同手机的签名文件是完全不一样的

3 使用jar进行签名 java -jar signapk.jar platform.x509.pem platform.pk8 app-release.apk app-signapk.apk 

完成这三步,应用就可以实现截屏了

于是奇怪的事情发生了,居然获取不到这张图片,即使我线程睡了3S还是获取不到(实际上应该存在,至于为什么卖个关子,后面再说)

既然获取不到只能另辟蹊径了,怎么去获取图片呢,使用按键截图既没有回调,也没有广播好烦啊,但是办法总比问题多,不是么?

安卓四大组件知道不?是不是有一个组件基本用不到,用的太少了,让我都几乎遗忘他了,对,就是内容提供者,我们这里要使用的是内容观察者,在安卓中有一个数据库,媒体文件信息通通会被记录在这个数据库,并共享出去(内容提供者)

所以我们只需要利用内容观察者观察到数据库有变化就进行图片的获取,提供一个链接,非常好用,拿来拷贝到工程里就直接使用了,里面也写得很清楚,不多做介绍了

http://blog.csdn.net/xietansheng/article/details/52692163


接下来就很简单了我只要在内容观察者的回调中,对返回的图片路径进行操作即可

基本步骤如下  

1 根据返回的路径,拿到bitmap对象

File file = new File(pathScreen);
Bitmap srcBitmap = BitmapFactory.decodeFile(pathScreen);

2 对图片进行裁剪 (去掉SurfaceView布局以外的地方)

Bitmap newBitmap = ImageUtil.getNewMap(srcBitmap, this);

3 对裁剪后的图片右上角进行绘制图片编号

Bitmap drawTextBitmap = FileUtil.bitMapDrawText(newBitmap);

4 将图片命名压缩保存到固定文件夹下
FileUtil.saveBitmapInSdcard(drawTextBitmap);

5 右下角展示图片缩略图

runOnUiThread(new Runnable() {
			@Override
			public void run() {
				refleshRecentPicView(drawTextBitmap);
				hiddenProgressDialog();
				long l1 = System.currentTimeMillis() - mL;
			
			}
		});

图片的处理是一个耗时过程1-4在子线程处理  5展示缩略图,在主线程运行

这样基本就完成了我们的所有需求,但是还没完,还有新问题发生!!!我只想好好地写个代码,,非要玩死我么...

你会发现在步骤一,你获取的对象一直是null,不对啊,明明是内容观察者返回来额路径怎么可能会不存在呢,明明是内容观察者返回回来的路径,确实,这的确是内容观察者返回来的路径,这也确实是存在的.说到这里又得去分析源码了...

下面就是截屏后对图片进行保存的代码了,这部分代码是运行在子线程中(AasynTask),这部分代码可以分为两部分

1 将截图的图片信息保存到媒体数据库中(这就是我们为什么收到观察者的回调,因为操作了媒体数据库)

2 将截图保存到本地

明白了吧,1完成时我们接收到回调,但此时2还没有完成,所以我们去根据路径获取对象肯定就不会成功的

 //将截屏后要保存的图片的信息保存到MediaStore数据库
            ContentValues values = new ContentValues();
            ContentResolver resolver = context.getContentResolver();
            values.put(MediaStore.Images.ImageColumns.DATA, mImageFilePath);
            values.put(MediaStore.Images.ImageColumns.TITLE, mImageFileName);
            values.put(MediaStore.Images.ImageColumns.DISPLAY_NAME, mImageFileName);
            values.put(MediaStore.Images.ImageColumns.DATE_TAKEN, mImageTime);
            values.put(MediaStore.Images.ImageColumns.DATE_ADDED, dateSeconds);
            values.put(MediaStore.Images.ImageColumns.DATE_MODIFIED, dateSeconds);
            values.put(MediaStore.Images.ImageColumns.MIME_TYPE, "image/png");
            values.put(MediaStore.Images.ImageColumns.WIDTH, mImageWidth);
            values.put(MediaStore.Images.ImageColumns.HEIGHT, mImageHeight);
            Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);

            String subjectDate = new SimpleDateFormat("hh:mma, MMM dd, yyyy")
                .format(new Date(mImageTime));
            String subject = String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate);
            Intent sharingIntent = new Intent(Intent.ACTION_SEND);
            sharingIntent.setType("image/png");
            sharingIntent.putExtra(Intent.EXTRA_STREAM, uri);
            sharingIntent.putExtra(Intent.EXTRA_SUBJECT, subject);

            Intent chooserIntent = Intent.createChooser(sharingIntent, null);
            chooserIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK
                    | Intent.FLAG_ACTIVITY_NEW_TASK);

            mNotificationBuilder.addAction(R.drawable.ic_menu_share,
                     r.getString(com.android.internal.R.string.share),
                     PendingIntent.getActivity(context, 0, chooserIntent,
                             PendingIntent.FLAG_CANCEL_CURRENT));

            //将截屏后的bitmap保存为png图片
            FileOutputStream out = new FileOutputStream(mImageFilePath);
            boolean bCompressOK = image.compress(Bitmap.CompressFormat.PNG, 100, out);
            out.flush();
            out.close();

我的代码步骤一获取不到对象我就只好写个判断循环获取,直到获取了对象再往下继续执行,接下来就比较顺利了,整个任务也就完美结束了,真是一波三折啊..但是你知道么从点击事件开始截图到生成图片一共耗时6-9秒,虽然加了进度框进行过渡,但是时间也略长了吧,为了追踪五步操作中,哪个步骤占据了太多时间,打个log看看哪里耗时最多

回调1708  ---->截图到内容观察者观察到
获取对象5018----->获取到对象
裁剪图片耗时忽略不计
保存图片6921------>图片保存到内存
完成7004----->完成一切操作共耗时7S

优化只能把每500ms去赋值对象时间改的更短一点,但基本上杯水车薪...

你要问我 没root  也不为定制手机做应用,就是面向广安卓用户,也不想使用系统的按键截图 可以对SurfaceView进行截图么?

我只能说,没有什么方法的.写这篇博客做个记录,给有需求的朋友做个参考,让大家少走弯路. 感谢观看!

你可能感兴趣的:(Android)