android屏幕录像
实现原理:
利用Java反射机制,获取WindowManagerImpl,该类里面有一个mViews的参数,它就是存放最终输出到界面的view视图,把这些视图保存在文件中为png,最后播放的时候从文件中拿出来即可。
一个Activity,一个后台Service既可以完成:
package com.jack.screenrecorder;
import java.io.File;
import java.util.Arrays;
import java.util.Comparator;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.Toast;
import com.jack.service.RecoderService;
public class MainActivity extends Activity implements OnClickListener{
private static final String TAG = "MainActivity";
private EditText input;
private File[] arrayOfFile2;
private ImageView playView;
private MainActivityHandler mHandler;
private boolean flagPlay = false;
private boolean flagRecorder = false;
private Button play,recorder,delete;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
input = (EditText)this.findViewById(R.id.input);
playView = (ImageView)this.findViewById(R.id.playView);
playView.setImageResource(R.drawable.ic_launcher);
mHandler = new MainActivityHandler();
play = (Button)this.findViewById(R.id.startPlay);
recorder = (Button)this.findViewById(R.id.startRecorder);
delete = (Button)this.findViewById(R.id.delete);
play.setOnClickListener(this);
recorder.setOnClickListener(this);
delete.setOnClickListener(this);
Log.i(TAG, Environment.getExternalStorageState());
}
@Override
protected void onStart() {
// TODO Auto-generated method stub
super.onStart();
int[] lo = new int[2];
input.getLocationOnScreen(lo);
Log.i(TAG, "input x y :" + lo[0] + lo[1]);
playView.getLocationOnScreen(lo);
Log.i(TAG, "playView x y :" + lo[0] + lo[1]);
playView.getLocationInWindow(lo);
Log.i(TAG, "playView x y :" + lo[0] + lo[1]);
}
/**
* 开始录制方法
*/
public void startRecorder(){
Log.i(TAG, "startRecorder");
flagRecorder = true;
recorder.setText("停止录制");
Intent intent = new Intent(this,RecoderService.class);
intent.putExtra("isStart", flagRecorder);
startService(intent);
}
public void endRecorder(){
Log.i(TAG, "endRecorder");
flagRecorder = false;
recorder.setText("开始录制");
Intent intent = new Intent(this,RecoderService.class);
stopService(intent);
}
/**
* 开始播放方法
*/
public void startPlay(){
flagPlay = true;
Log.i(TAG, "startPlay");
play.setText("停止播放");
arrayOfFile2 = getFileList();
mHandler.setCount(0);
mHandler.sendEmptyMessageDelayed(RecoderService.MSG_WHAT, RecoderService.MSG_DELEAY);
}
/**
* 停止播放
*/
public void endPlay(){
flagPlay = false;
Log.i(TAG, "endPlay");
play.setText("开始播放");
mHandler.removeMessages(RecoderService.MSG_WHAT);
}
/**
* 获取存储卡里面路径下的文件列表
* @return
*/
public File[] getFileList(){
File localFile1 = Environment.getExternalStorageDirectory();
File localFile2 = new File(localFile1, RecoderService.PATH);
//该路径创建不成功
if(!localFile2.exists()){
return null;
}
//返回该路径下的文件列表
File[] fileList = localFile2.listFiles();
//把文件拿来排序,存放文件时名字为日期,将日期转为long型数据进行排序
Arrays.sort(fileList, new Comparator<File>() {
@Override
public int compare(File arg0, File arg1) {
// TODO Auto-generated method stub
Long preFileLength1 = Long.valueOf(getPreFileName(arg0.getName()));
long preFileLength2 = Long.valueOf(getPreFileName(arg1.getName()));
return (int)(preFileLength1-preFileLength2);
}
});
return fileList;
}
/**
* 获取filename的前缀名
* @param filename
* @return
* 如:20150305.jpg === >>> 20150305
*/
public String getPreFileName(String filename){
String preFileName = null;
if(filename == null || filename.length() < 1){
Log.i(TAG, "当前文件名为空或长度小于1");
return null;
}
int i = filename.lastIndexOf('.');
if(i != -1){
int j = filename.length();
if(i < j){
preFileName = filename.substring(0, i);
}
}
return preFileName;
}
private class MainActivityHandler extends Handler{
private int count = 0;
public void setCount(int value){
count = value;
}
public int getCount(){
return count;
}
@Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
if(arrayOfFile2 != null && count < arrayOfFile2.length){
Bitmap localBitmap = BitmapFactory.decodeFile(arrayOfFile2[count++].getAbsolutePath()); //获取该路径下的文件 并转换为bitmap
playView.setImageBitmap(localBitmap);
localBitmap.recycle();
//延迟发送message
if(flagPlay){
mHandler.sendEmptyMessageDelayed(RecoderService.MSG_WHAT, RecoderService.MSG_DELEAY);
}
}
}
}
@Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
switch(arg0.getId()){
case R.id.startPlay:
if(flagPlay){
endPlay();
}else{
if(!flagRecorder){
startPlay();
}else{
Toast.makeText(MainActivity.this, "请先关闭录制功能", Toast.LENGTH_SHORT).show();
}
}
break;
case R.id.startRecorder:
if(flagRecorder){
endRecorder();
}else{
if(!flagPlay){
startRecorder();
}else{
Toast.makeText(MainActivity.this, "请先关闭播放功能", Toast.LENGTH_SHORT).show();
}
}
break;
case R.id.delete:
if(!flagRecorder && !flagPlay){
Log.i(TAG, "delete file");
deleteRecorderFile();
}else{
Toast.makeText(MainActivity.this, "请关闭录制和播放功能", Toast.LENGTH_SHORT);
}
break;
default:
break;
}
}
public void deleteRecorderFile(){
File[] file1 = this.getFileList();
for(File file : file1){
if(file.isFile() || file.exists()){
file.delete();
}
}
}
@Override
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
int[] lo = new int[2];
input.getLocationOnScreen(lo);
Log.i(TAG, "input x y :" + lo[0] + lo[1]);
playView.getLocationOnScreen(lo);
Log.i(TAG, "playView x y :" + lo[0] + lo[1]);
playView.getLocationInWindow(lo);
Log.i(TAG, "playView x y :" + lo[0] + lo[1]);
}
@Override
protected void onPause() {
// TODO Auto-generated method stub
super.onResume();
int[] lo = new int[2];
input.getLocationOnScreen(lo);
Log.i(TAG, "onPause input x y :" + lo[0] + lo[1]);
playView.getLocationOnScreen(lo);
Log.i(TAG, "playView x y :" + lo[0] + lo[1]);
playView.getLocationInWindow(lo);
Log.i(TAG, "playView x y :" + lo[0] + lo[1]);
}
}
package com.jack.service;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import android.app.Service;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.util.Log;
import android.view.View;
public class RecoderService extends Service{
private static final String TAG = "RecoderService";
public static final int MSG_WHAT = 256;
public static final long MSG_DELEAY = 100L;
public static final String PATH = "/recoder/";
private boolean isStart = false;
@Override
public IBinder onBind(Intent arg0) {
// TODO Auto-generated method stub
return null;
}
@Override
public void onCreate() {
// TODO Auto-generated method stub
super.onCreate();
Log.i(TAG, "onCreate()...");
}
@Override
public void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
Log.i(TAG, "end Service()===========================================");
Log.i(TAG, "end Service()===========================================");
Log.i(TAG, "end Service()===========================================");
isStart = false;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// TODO Auto-generated method stub
Log.i(TAG, "onStartCommand()===========================================");
Log.i(TAG, "onStartCommand()===========================================");
Log.i(TAG, "onStartCommand()===========================================");
boolean flag = intent.getBooleanExtra("isStart", false);
//如果传递过来的值时开启就开启 否则不开启
if(flag != isStart){
isStart = flag;
if(isStart){
mHandler.sendEmptyMessageDelayed(MSG_WHAT, MSG_DELEAY);
}else{
mHandler.removeMessages(MSG_WHAT);
}
}
return super.onStartCommand(intent, flags, startId);
}
@Override
public boolean onUnbind(Intent intent) {
// TODO Auto-generated method stub
return super.onUnbind(intent);
}
/**
* 排序views,以view的绝对坐标
* @param viewArray
* @return
*/
public View[] sortViews(View[] viewArray){
if(viewArray == null || viewArray.length <= 0){
Log.i(TAG, "sortView() paramter viewArray is null");
return null;
}
int i = 0;
View[] views = new View[viewArray.length];
for(View view : viewArray){
views[i++] = view;
}
//sort the view
int length = views.length;
int[] arrayInt = new int[2];
for(i = 0; i < length; i++){
View localView = views[i];
localView.getLocationOnScreen(arrayInt);
if(arrayInt[0] > 0 && arrayInt[1] > 0){
for(int j = i+1; j < views.length; i++){
views[j-1] = views[j];
}
views[views.length-1] = localView;
i--;
length--;
}
}
return views;
}
/**
*
* @return views[]s
*/
public View[] getWindowDecorViewDownApi15(){
View[] view = null;
try {
Log.i(TAG, "getWindowDecorViewDownApi15");
Class<?> windowManager = Class.forName("android.view.WindowManagerImpl");
Field viewFiled = windowManager.getDeclaredField("mViews");
Field instanceFiled = windowManager.getDeclaredField("mWindowManager");
viewFiled.setAccessible(true);
instanceFiled.setAccessible(true); //可修改
Object instance = instanceFiled.get(null);
view = (View[]) viewFiled.get(instance);
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
Log.i(TAG, "getWindowDecorViewDownApi15() ClassNotFoundException " + e.toString());
e.printStackTrace();
} catch (NoSuchFieldException e) {
// TODO Auto-generated catch block
Log.i(TAG, "getWindowDecorViewDownApi15() NoSuchFieldException " + e.toString());
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return sortViews(view);
}
/**
*
* @return
*/
public View[] getWindowDecorViewsApi14_16(){
View[] view = null;
try {
Log.i(TAG, "getWindowDecorViewsApi14_16");
Class<?> windowManager = Class
.forName("android.view.WindowManagerImpl");
Field viewsField = windowManager.getDeclaredField("mViews");
Field instanceField = windowManager.getDeclaredField("sWindowManager");
viewsField.setAccessible(true);
instanceField.setAccessible(true); //可修改
Object instance = instanceField.get(null);
view = (View[]) viewsField.get(instance);
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
Log.i(TAG, "getWindowDecorViewDownApi15() ClassNotFoundException " + e.toString());
e.printStackTrace();
} catch (NoSuchFieldException e) {
// TODO Auto-generated catch block
Log.i(TAG, "getWindowDecorViewDownApi15() NoSuchFieldException " + e.toString());
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return sortViews(view);
}
public View[] getWindowDecorViewsApiUp17(){
View[] view = null;
try {
Log.i(TAG, "getWindowDecorViewsApiUp17");
Class<?> windowManager = Class
.forName("android.view.WindowManagerGlobal");
Field viewsField = windowManager.getDeclaredField("mViews");
Field instanceField = windowManager.getDeclaredField("sDefaultWindowManager");
viewsField.setAccessible(true);
instanceField.setAccessible(true); //可修改
Object instance = instanceField.get(null);
view = (View[]) viewsField.get(instance);
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
Log.i(TAG, "getWindowDecorViewDownApi15() ClassNotFoundException " + e.toString());
e.printStackTrace();
} catch (NoSuchFieldException e) {
// TODO Auto-generated catch block
Log.i(TAG, "getWindowDecorViewDownApi15() NoSuchFieldException " + e.toString());
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return sortViews(view);
}
/**
* 根据不同的sdk版本采取不同的方法获取图片
* @return
*/
private View[] getWindowDecorViews(){
View[] views = null;
if(android.os.Build.VERSION.SDK_INT >= 14 && android.os.Build.VERSION.SDK_INT < 17){
views = getWindowDecorViewsApi14_16();
}else if(android.os.Build.VERSION.SDK_INT >= 17){
views = getWindowDecorViewsApiUp17();
}else{
views = getWindowDecorViewDownApi15();
}
return views;
}
/**
* 从底层获取view在转换为bitmap,在保存到文件
*/
public void screenShot(){
try {
View[] views = getWindowDecorViews();
Bitmap bitmap = viewsToBitmap(views);
new Thread(new SavePicThread(bitmap)).start();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
Log.i(TAG, "screenshot() has a error. " + e.toString());
}
}
/**
*
* @param view
* @return Bitmap
*/
public Bitmap viewToBitmap(View view){
Log.i(TAG, "turn view to bitmap");
/**
* set缓冲方便下次调用getDrawingCache把view画到一个bitmap里面去
*/
view.setDrawingCacheEnabled(true);
view.buildDrawingCache();
Bitmap bitmap1 = view.getDrawingCache();
Bitmap bitmap2 = null;
if(bitmap1 != null){
bitmap2 = Bitmap.createBitmap(bitmap1);
}
view.destroyDrawingCache();
return bitmap2;
}
/**
* 将几个view画到一个bitmap有点不懂为什么
* @param arrayView
* @return
*/
public Bitmap viewsToBitmap(View[] arrayView){
Log.i(TAG, "turn viewssss to bitmap");
Canvas localCanvas = null;
Paint mPaint = null;
Bitmap newBitmap = null;
int[] location = new int[2];
Bitmap localBitmap = null;
for(View view : arrayView){
localBitmap = viewToBitmap(view);
view.getLocationOnScreen(location);
//画一个和view尺寸大小的画布
if(null == localCanvas){
newBitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Config.ARGB_8888);
localCanvas = new Canvas(newBitmap);
mPaint = new Paint();
mPaint.setAntiAlias(true);
}
localCanvas.drawBitmap(localBitmap, location[0], location[1], mPaint);
localBitmap.recycle();
localBitmap = null;
}
if(localCanvas != null){
localCanvas.save(Canvas.ALL_SAVE_FLAG);
localCanvas.restore();
}
return newBitmap;
}
/**
* b保存bitmap为图片
* @param paramBitmap
* @param paramString
*/
public void savePic(Bitmap paramBitmap, String paramString){
FileOutputStream fsStream = null;
try {
fsStream = new FileOutputStream(paramString);
if(fsStream != null){
paramBitmap.compress(Bitmap.CompressFormat.PNG, 90, fsStream); //压缩保存
fsStream.flush();
fsStream.close();
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
Log.i(TAG, e.toString());
Log.i(TAG, "savePic() paramString is not exist" + paramString);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
Log.i(TAG, "savePic() close() flush() is error!");
}
}
/**
* 保存图片线程
* @author JackZhouYu
* 将内存中的bitmap写到file里面去,因为是一个比较耗时的工作,所以开线程存储
*/
private class SavePicThread implements Runnable{
private Bitmap bitmap;
public SavePicThread(Bitmap bitmap){
this.bitmap = bitmap;
}
@Override
public void run() {
// TODO Auto-generated method stub
File file1 = Environment.getExternalStorageDirectory();
File file2 = new File(file1,PATH);
//创建目录
if(!file2.exists()){
file2.mkdir();
}
String filename = file2.getAbsolutePath() + "/" + System.currentTimeMillis() + ".png";
Log.i(TAG, "save pic to file " + filename);
savePic(bitmap, filename);
bitmap.recycle();
bitmap = null;
}
}
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
Log.i(TAG, "mHandler i got message");
// TODO Auto-generated method stub
if(msg.what == MSG_WHAT){
RecoderService.this.screenShot();
if(isStart){
mHandler.sendEmptyMessageDelayed(MSG_WHAT, MSG_DELEAY);
}
}
}
};
}
但是,这只能录制当前App下的图像
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】