Android截屏和录屏Demo

最近两天研究了一下安卓截屏和录屏功能的实现,基本的思路如下:

截屏:通过View绘制缓冲获得Bitmap,然后写到文件中,完成截屏的功能;

录屏:通过MediaRecorder进行video record,基本过程如下:

MediaRecorder recorder = new MediaRecorder();
 recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
 recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
 recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
 recorder.setOutputFile(PATH_NAME);
 recorder.prepare();
 recorder.start();   // Recording is now started
 ...
 recorder.stop();
 recorder.reset();   // You can reuse the object by going back to setAudioSource() step
 recorder.release(); // Now the object cannot be reused

要是想实现录屏功能,首先需要向系统申请权限,然后得到系统的反馈,分别创建录屏服务、MediaProjectionManager、MediaRecorder以及创建VirtualDisplay,最后开始录屏。录屏结束后对相关资源进行释放。

下面上代码:

首先是配置权限:

    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

    <uses-permission android:name="android.permission.CAMERA"/>
然而在实际开发中发生了如下一幕,Permission Deny.这个时候可能是相关的权限没有自动打开,需要手动去应用权限管理中将相关的权限打开,如下:


然后就是写界面XML(界面很简单):

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/linearLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1">

        <Button
            android:id="@+id/buttonCapture"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_marginEnd="20dp"
            android:layout_marginStart="20dp"
            android:text="截屏"
            android:textSize="24sp" />

    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1">

        <Button
            android:id="@+id/buttonRecord"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginEnd="20dp"
            android:layout_marginStart="20dp"
            android:text="录屏"
            android:textSize="24sp" />

    </LinearLayout>

</LinearLayout>

效果如下:


接下来是MainActivity.java(相关说明写在注释中了,直接参考的API,准确一些,现在访问API可方便多了,哈哈哈)

package com.dzjin.screen.screenshotandrecorddemo;

import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.media.projection.MediaProjectionManager;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.view.KeyEvent;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.Toast;

import java.io.FileOutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;

public class MainActivity extends AppCompatActivity {

    private LinearLayout linearLayout=null;
    private Button buttonRecord=null;
    private Button buttonCapture=null;

    private boolean isRecord=false;

    private int mScreenWidth;
    private int mScreenHeight;
    private int mScreenDensity;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        getScreenBaseInfo();

        linearLayout=(LinearLayout)findViewById(R.id.linearLayout);
        buttonRecord=(Button)findViewById(R.id.buttonRecord);
        buttonCapture=(Button)findViewById(R.id.buttonCapture);

        buttonRecord.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                if(isRecord){
                    stopScreenRecord();
                }else{
                    startScreenRecord();
                }
            }
        });

        buttonCapture.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                capture(linearLayout);
            }
        });

    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if(requestCode == 1000){
            if(resultCode == RESULT_OK){
                //获得录屏权限,启动Service进行录制
                Intent intent=new Intent(MainActivity.this,ScreenRecordService.class);
                intent.putExtra("resultCode",resultCode);
                intent.putExtra("resultData",data);
                intent.putExtra("mScreenWidth",mScreenWidth);
                intent.putExtra("mScreenHeight",mScreenHeight);
                intent.putExtra("mScreenDensity",mScreenDensity);
                startService(intent);
                Toast.makeText(this,"录屏开始",Toast.LENGTH_SHORT).show();
            }else{
                Toast.makeText(this,"录屏失败",Toast.LENGTH_SHORT).show();
            }

        }
    }

    //start screen record
    private void startScreenRecord(){
        //Manages the retrieval of certain types of MediaProjection tokens.
        MediaProjectionManager mediaProjectionManager=
                (MediaProjectionManager)getSystemService(Context.MEDIA_PROJECTION_SERVICE);
        //Returns an Intent that must passed to startActivityForResult() in order to start screen capture.
        Intent permissionIntent=mediaProjectionManager.createScreenCaptureIntent();
        startActivityForResult(permissionIntent,1000);
        isRecord=true;
        buttonRecord.setText(new String("停止录屏"));

    }

    //stop screen record.
    private void stopScreenRecord(){
        Intent service = new Intent(this, ScreenRecordService.class);
        stopService(service);
        isRecord=false;
        buttonRecord.setText(new String("开始录屏"));
        Toast.makeText(this,"录屏成功",Toast.LENGTH_SHORT).show();

    }

    public void capture(View v){
        //格式化时间作为截屏文件名
        SimpleDateFormat simpleDateFormat=new SimpleDateFormat("YY-MM-DD-HH-MM-SS");
        //在获取外部存储的时候,一定注意添加权限,如果添加权限还不能成功,则手动在应用中开启权限。
        String filePathName= Environment.getExternalStorageDirectory()+"/"+simpleDateFormat.format(new Date())+".png";
        //Find the topmost view in the current view hierarcht.
        View view=v.getRootView();
        // Enable or disable drawing cache.
        view.setDrawingCacheEnabled(true);
        // Calling this method is equivalent to calling buildDrawingCache(false);
        // In order to force drawing cache to be buuild.
        view.buildDrawingCache();
        //Calling this method is equivalent to calling getDrawingCache(false);
        //Return the Bitmap in which this view drawing is cached.
        Bitmap bitmap=view.getDrawingCache();
        try{
            System.out.println(filePathName);
            FileOutputStream fileOutputStream=new FileOutputStream(filePathName);
            //Write a compressed version of bitmap to specified outputStream.
            bitmap.compress(Bitmap.CompressFormat.PNG,100,fileOutputStream);
            Toast.makeText(this,"Cpature Succeed",Toast.LENGTH_SHORT).show();
        }catch (Exception e){
            e.printStackTrace();
            Toast.makeText(this,"Cpature Failed",Toast.LENGTH_SHORT).show();
        }
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        // 在这里将BACK键模拟了HOME键的返回桌面功能(并无必要)
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            simulateHome();
            return true;
        }
        return super.onKeyDown(keyCode, event);
    }

    /**
     * 获取屏幕基本信息
     */
    private void getScreenBaseInfo() {
        //A structure describing general information about a display, such as its size, density, and font scaling.
        DisplayMetrics metrics = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(metrics);
        mScreenWidth = metrics.widthPixels;
        mScreenHeight = metrics.heightPixels;
        mScreenDensity = metrics.densityDpi;
    }

    /**
     * 模拟HOME键返回桌面的功能
     */
    private void simulateHome() {
        //Intent.ACTION_MAIN,Activity Action: Start as a main entry point, does not expect to receive data.
        Intent intent = new Intent(Intent.ACTION_MAIN);
        //If set, this activity will become the start of a new task on this history stack.
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        //This is the home activity, that is the first activity that is displayed when the device boots.
        intent.addCategory(Intent.CATEGORY_HOME);
        this.startActivity(intent);
    }

}

录屏ScreenRecordService.java

package com.dzjin.screen.screenshotandrecorddemo;

import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.media.MediaRecorder;
import android.media.projection.MediaProjection;
import android.media.projection.MediaProjectionManager;
import android.os.Environment;
import android.os.IBinder;
import android.support.annotation.Nullable;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * Created by dzjin on 2018/1/9.
 */

public class ScreenRecordService extends Service {

    private int resultCode;
    private Intent resultData=null;

    private MediaProjection mediaProjection=null;
    private MediaRecorder mediaRecorder=null;
    private VirtualDisplay virtualDisplay=null;

    private int mScreenWidth;
    private int mScreenHeight;
    private int mScreenDensity;

    private Context context=null;

    @Override
    public void onCreate() {
        super.onCreate();
    }

    /**
     * Called by the system every time a client explicitly starts the service by calling startService(Intent),
     * providing the arguments it supplied and a unique integer token representing the start request.
     * Do not call this method directly.
     * @param intent
     * @param flags
     * @param startId
     * @return
     */
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        try{
            resultCode=intent.getIntExtra("resultCode",-1);
            resultData=intent.getParcelableExtra("resultData");
            mScreenWidth=intent.getIntExtra("mScreenWidth",0);
            mScreenHeight=intent.getIntExtra("mScreenHeight",0);
            mScreenDensity=intent.getIntExtra("mScreenDensity",0);

            mediaProjection=createMediaProjection();
            mediaRecorder=createMediaRecorder();
            virtualDisplay=createVirtualDisplay();
            mediaRecorder.start();

        }catch (Exception e) {
            e.printStackTrace();
        }
        /**
         * START_NOT_STICKY:
         * Constant to return from onStartCommand(Intent, int, int): if this service's process is
         * killed while it is started (after returning from onStartCommand(Intent, int, int)),
         * and there are no new start intents to deliver to it, then take the service out of the
         * started state and don't recreate until a future explicit call to Context.startService(Intent).
         * The service will not receive a onStartCommand(Intent, int, int) call with a null Intent
         * because it will not be re-started if there are no pending Intents to deliver.
         */
        return Service.START_NOT_STICKY;
    }

    //createMediaProjection
    public MediaProjection createMediaProjection(){
        /**
         * Use with getSystemService(Class) to retrieve a MediaProjectionManager instance for
         * managing media projection sessions.
         */
        return ((MediaProjectionManager)getSystemService(Context.MEDIA_PROJECTION_SERVICE))
                .getMediaProjection(resultCode,resultData);
        /**
         * Retrieve the MediaProjection obtained from a succesful screen capture request.
         * Will be null if the result from the startActivityForResult() is anything other than RESULT_OK.
         */
    }

    private MediaRecorder createMediaRecorder(){
        SimpleDateFormat simpleDateFormat=new SimpleDateFormat("YY-MM-DD-HH-MM-SS");
        String filePathName= Environment.getExternalStorageDirectory()+"/video"+simpleDateFormat.format(new Date())+".mp4";
        //Used to record audio and video. The recording control is based on a simple state machine.
        MediaRecorder mediaRecorder=new MediaRecorder();
        //Set the video source to be used for recording.
        mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
        //Set the format of the output produced during recording.
        //3GPP media file format
        mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
        //Sets the video encoding bit rate for recording.
        //param:the video encoding bit rate in bits per second.
        mediaRecorder.setVideoEncodingBitRate(5*mScreenWidth*mScreenHeight);
        //Sets the video encoder to be used for recording.
        mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
        //Sets the width and height of the video to be captured.
        mediaRecorder.setVideoSize(mScreenWidth,mScreenHeight);
        //Sets the frame rate of the video to be captured.
        mediaRecorder.setVideoFrameRate(60);
        try{
            //Pass in the file object to be written.
            mediaRecorder.setOutputFile(filePathName);
            //Prepares the recorder to begin capturing and encoding data.
            mediaRecorder.prepare();
        }catch (Exception e){
            e.printStackTrace();
        }
        return mediaRecorder;
    }

    private VirtualDisplay createVirtualDisplay(){
        /**
         * name	String: The name of the virtual display, must be non-empty.This value must never be null.
         width	int: The width of the virtual display in pixels. Must be greater than 0.
         height	int: The height of the virtual display in pixels. Must be greater than 0.
         dpi	int: The density of the virtual display in dpi. Must be greater than 0.
         flags	int: A combination of virtual display flags. See DisplayManager for the full list of flags.
         surface	Surface: The surface to which the content of the virtual display should be rendered, or null if there is none initially.
         callback	VirtualDisplay.Callback: Callback to call when the virtual display's state changes, or null if none.
         handler	Handler: The Handler on which the callback should be invoked, or null if the callback should be invoked on the calling thread's main Looper.
         */
        /**
         * DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR
         * Virtual display flag: Allows content to be mirrored on private displays when no content is being shown.
         */
        return mediaProjection.createVirtualDisplay("mediaProjection",mScreenWidth,mScreenHeight,mScreenDensity,
                DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,mediaRecorder.getSurface(),null,null);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if(virtualDisplay!=null){
            virtualDisplay.release();
            virtualDisplay=null;
        }
        if(mediaRecorder!=null){
            mediaRecorder.stop();
            mediaRecorder=null;
        }
        if(mediaProjection!=null){
            mediaProjection.stop();
            mediaProjection=null;
        }
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}
OK,完成,相关的解释直接写在代码中了。

非淡泊无以明志,非宁静无以致远。共勉。

Demo下载地址:http://download.csdn.net/download/dzjin1234/10199546

  • 2
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值