├── .gitignore
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── yorhp
│ │ └── screenrecord
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── yorhp
│ │ │ └── screenrecord
│ │ │ ├── MainActivity.java
│ │ │ ├── MyService.java
│ │ │ └── app
│ │ │ └── MyApplication.java
│ └── res
│ │ ├── drawable-v24
│ │ ├── ic_launcher_foreground.xml
│ │ └── ic_star.xml
│ │ ├── drawable
│ │ └── ic_launcher_background.xml
│ │ ├── layout
│ │ └── activity_main.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ └── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── yorhp
│ └── screenrecord
│ └── ExampleUnitTest.java
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── recordlibrary
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── yorhp
│ │ └── recordlibrary
│ │ ├── OnScreenShotListener.java
│ │ ├── ScreenRecordActivity.java
│ │ ├── ScreenRecordUtil.java
│ │ ├── ScreenShotUtil.java
│ │ ├── ScreenUtil.java
│ │ └── util
│ │ └── ScreenRecorder.java
│ └── res
│ ├── layout
│ └── activity_screen_record.xml
│ └── values
│ ├── strings.xml
│ └── styles.xml
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | ### macOS template
3 | # General
4 | .DS_Store
5 | .AppleDouble
6 | .LSOverride
7 |
8 | # Icon must end with two \r
9 | Icon
10 |
11 | # Thumbnails
12 | ._*
13 |
14 | # Files that might appear in the root of a volume
15 | .DocumentRevisions-V100
16 | .fseventsd
17 | .Spotlight-V100
18 | .TemporaryItems
19 | .Trashes
20 | .VolumeIcon.icns
21 | .com.apple.timemachine.donotpresent
22 |
23 | # Directories potentially created on remote AFP share
24 | .AppleDB
25 | .AppleDesktop
26 | Network Trash Folder
27 | Temporary Items
28 | .apdisk
29 | ### Android template
30 | # Built application files
31 | *.apk
32 | *.ap_
33 |
34 | # Files for the ART/Dalvik VM
35 | *.dex
36 |
37 | # Java class files
38 | *.class
39 |
40 | # Generated files
41 | bin/
42 | gen/
43 | out/
44 |
45 | # Gradle files
46 | .gradle/
47 | build/
48 |
49 | # Local configuration file (sdk path, etc)
50 | local.properties
51 |
52 | # Proguard folder generated by Eclipse
53 | proguard/
54 |
55 | # Log Files
56 | *.log
57 |
58 | # Android Studio Navigation editor temp files
59 | .navigation/
60 |
61 | # Android Studio captures folder
62 | captures/
63 |
64 | # IntelliJ
65 | *.iml
66 | .idea/workspace.xml
67 | .idea/tasks.xml
68 | .idea/gradle.xml
69 | .idea/assetWizardSettings.xml
70 | .idea/dictionaries
71 | .idea/libraries
72 | .idea/caches
73 |
74 | .idea/libraries/
75 | .idea/.name
76 | .idea/compiler.xml
77 | .idea/copyright/profiles_settings.xml
78 | .idea/encodings.xml
79 | .idea/misc.xml
80 | .idea/modules.xml
81 | .idea/scopes/scope_settings.xml
82 | .idea/vcs.xml
83 | .classpath
84 | .project
85 |
86 | .idea
87 | #.idea/workspace.xml - remove # and delete .idea if it better suit your needs.
88 | .gradle
89 |
90 | # Keystore files
91 | # Uncomment the following line if you do not want to check your keystore files in.
92 | #*.jks
93 |
94 | # External native build folder generated in Android Studio 2.2 and later
95 | .externalNativeBuild
96 |
97 | # Google Services (e.g. APIs or Firebase)
98 | google-services.json
99 |
100 | # Freeline
101 | freeline.py
102 | freeline/
103 | freeline_project_description.json
104 |
105 | # fastlane
106 | fastlane/report.xml
107 | fastlane/Preview.html
108 | fastlane/screenshots
109 | fastlane/test_output
110 | fastlane/readme.md
111 |
112 | .idea/fileTemplates/
113 | fingersm2blibrary/src/androidTest/java/
114 | fingersm2blibrary/src/main/java/com/example/fingersm2blibrary/
115 | fingersm2blibrary/src/main/res/
116 | fingersm2blibrary/src/test/
117 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Android截屏工具
2 |
3 | 标签(空格分隔): Android
4 |
5 | ---
6 |
7 | > 原文链接:https://www.jianshu.com/p/8a428fb45098
8 |
9 | 有时候会用到颜色拾取器这样的东西来查看屏幕上的颜色值,一直是用Pixolor这个软件来看颜色的;很方便,点哪里显示哪里,也没有延迟,以为是什么黑科技;我注意到一个细节,如果只是切换屏幕,颜色拾取器不会更新,只有移动拾取器才更新选中;可以确定是截屏来实现的了,那就简单了,截屏获取像素点的颜色值就好了
10 |
11 |
12 | 用到截屏,网上看了一下,大概分为保存View为图像和调用录屏服务来截屏,录屏是比较好的办法,可以在APP外截屏,所以简单的封装了一下
13 |
14 | ###集成方法:
15 |
16 | Step 1. Add the JitPack repository to your build file
17 | ```
18 | allprojects {
19 | repositories {
20 | ...
21 | maven { url 'https://jitpack.io' }
22 | }
23 | }
24 |
25 | ```
26 | Step 2. Add the dependency
27 | ```
28 | dependencies {
29 | implementation 'com.github.tyhjh:ScreenShot:v1.0.0'
30 | }
31 | ```
32 | ### 简单使用
33 |
34 | 主要分为两步,第一步是开启录屏;第二步就可以直接获取截屏,返回Bitmap
35 | 截图的过程录屏是开启的,录屏开启就可以进行截屏,操作完需要关闭录屏
36 | 截屏过程很快,效果很好
37 |
38 | ```java
39 | //第一次会自动申请录屏权限
40 | ScreenRecordUtil.getInstance().screenShot(this, new OnScreenShotListener() {
41 | @Override
42 | public void screenShot() {
43 | //可以获取截图,可以多次调用
44 | iv_pre.setImageBitmap(ScreenRecordUtil.getInstance().getScreenShot());
45 | //最后关闭录屏服务
46 | ScreenRecordUtil.getInstance().destroy();
47 | }
48 | });
49 | ```
50 | 如果是APP外截屏则开启悬浮窗服务,可以通过操作悬浮窗进行截屏
51 |
52 | #### 参考文章:[Android 截屏方式整理](https://www.jianshu.com/p/63e29dc43a69)
53 |
54 | ### 截屏实现代码
55 |
56 | 1.初始化一个`MediaProjectionManager`
57 |
58 | ```java
59 | MediaProjectionManager mMediaProjectionManager = (MediaProjectionManager) activity.getSystemService(Context.MEDIA_PROJECTION_SERVICE);
60 | ```
61 | 2.创建并启动`Intent`
62 | ```java
63 | startActivityForResult(mMediaProjectionManager.createScreenCaptureIntent(),REQUEST_MEDIA_PROJECTION);
64 | ```
65 | 3.在`onActivityResult`中拿到MediaProjection
66 | ```java
67 | mMediaProjection = mMediaProjectionManager.getMediaProjection(resultCode, data);
68 | ```
69 | 4.设置VirtualDisplay将图像和展示的View关联起来。一般来说我们会将图像展示到SurfaceView,这里为了为了便于拿到截图,我们使用ImageReader,他内置有SurfaceView。
70 | ```java
71 | mVirtualDisplay = mMediaProjection.createVirtualDisplay("screen-mirror",
72 | mScreenWidth, mScreenHeight, mScreenDensity,
73 | DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
74 | mImageReader.getSurface(), null, null);
75 | ```
76 |
77 | 5.通过ImageReader拿到截图
78 | ```java
79 | Image image = null;
80 | image = mImageReader.acquireLatestImage();
81 | while (image == null) {
82 | SystemClock.sleep(10);
83 | image = mImageReader.acquireLatestImage();
84 | }
85 | int width = image.getWidth();
86 | int height = image.getHeight();
87 | final Image.Plane[] planes = image.getPlanes();
88 | final ByteBuffer buffer = planes[0].getBuffer();
89 | //每个像素的间距
90 | int pixelStride = planes[0].getPixelStride();
91 | //总的间距
92 | int rowStride = planes[0].getRowStride();
93 | int rowPadding = rowStride - pixelStride * width;
94 | Bitmap bitmap = Bitmap.createBitmap(width + rowPadding / pixelStride, height, Bitmap.Config.ARGB_8888);
95 | bitmap.copyPixelsFromBuffer(buffer);
96 | bitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height);
97 | image.close();
98 | return bitmap;
99 | ```
100 | 6.注意截屏之后要及时关闭VirtualDisplay ,因为VirtualDisplay 是十分消耗内存和电量的。
101 | ```java
102 | if (mVirtualDisplay == null) {
103 | return;
104 | }
105 | mVirtualDisplay.release();
106 | mVirtualDisplay = null;
107 |
108 | ```
109 |
110 | #### 项目地址:https://github.com/tyhjh/ScreenShot
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 28
5 | defaultConfig {
6 | applicationId "com.yorhp.screenrecord"
7 | minSdkVersion 15
8 | targetSdkVersion 28
9 | versionCode 1
10 | versionName "1.0"
11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
12 | }
13 | buildTypes {
14 | release {
15 | minifyEnabled false
16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
17 | }
18 | }
19 | }
20 |
21 | dependencies {
22 | implementation fileTree(include: ['*.jar'], dir: 'libs')
23 | implementation 'com.android.support:appcompat-v7:28.0.0'
24 | implementation 'com.android.support.constraint:constraint-layout:1.1.3'
25 | testImplementation 'junit:junit:4.12'
26 | androidTestImplementation 'com.android.support.test:runner:1.0.2'
27 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
28 | implementation project(':recordlibrary')
29 | implementation 'com.github.tyhjh:PermissionUtil:v1.0.5'
30 | }
31 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/yorhp/screenrecord/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.yorhp.screenrecord;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumented test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.yorhp.screenrecord", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/app/src/main/java/com/yorhp/screenrecord/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.yorhp.screenrecord;
2 |
3 | import android.Manifest;
4 | import android.graphics.Bitmap;
5 | import android.os.Build;
6 | import android.os.Bundle;
7 | import android.support.annotation.RequiresApi;
8 | import android.support.v7.app.AppCompatActivity;
9 | import android.view.View;
10 | import android.widget.ImageView;
11 | import android.widget.Toast;
12 |
13 | import com.yorhp.recordlibrary.OnScreenShotListener;
14 | import com.yorhp.recordlibrary.ScreenShotUtil;
15 | import com.yorhp.recordlibrary.ScreenUtil;
16 |
17 | import permison.PermissonUtil;
18 |
19 | public class MainActivity extends AppCompatActivity {
20 |
21 | ImageView iv_pre;
22 |
23 | private int screenRecordBitrate = 32 * 1024 * 1024;
24 |
25 | @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
26 | @Override
27 | protected void onCreate(Bundle savedInstanceState) {
28 | super.onCreate(savedInstanceState);
29 | setContentView(R.layout.activity_main);
30 | iv_pre = findViewById(R.id.iv_pre);
31 |
32 | PermissonUtil.checkPermission(this, null, Manifest.permission.WRITE_EXTERNAL_STORAGE);
33 | ScreenUtil.getScreenSize(this);
34 |
35 |
36 |
37 | /*ScreenRecordUtil.init(ScreenUtil.SCREEN_WIDTH, ScreenUtil.SCREEN_HEIGHT, screenRecordBitrate);
38 | String savePath = MyApplication.baseDir + System.currentTimeMillis() + ".mp4";
39 | ScreenRecordUtil.getInstance().start(MainActivity.this, savePath);
40 |
41 |
42 | new Thread(new Runnable() {
43 | @Override
44 | public void run() {
45 | try {
46 | Thread.sleep(10 * 1000);
47 | ScreenRecordUtil.getInstance().destroy();
48 | } catch (InterruptedException e) {
49 | e.printStackTrace();
50 | }
51 | }
52 | }).start();*/
53 |
54 |
55 |
56 |
57 | //申请悬浮窗权限
58 | //FloatWindowManager.getInstance().applyOrShowFloatWindow(MainActivity.this);
59 |
60 |
61 | iv_pre.setOnClickListener(new View.OnClickListener() {
62 | @Override
63 | public void onClick(View view) {
64 | /* startService(new Intent(MainActivity.this, MyService.class));
65 | moveTaskToBack(true);*/
66 | /**
67 | * 初始化成功
68 | */
69 | ScreenShotUtil.getInstance().screenShot(MainActivity.this, new OnScreenShotListener() {
70 | @Override
71 | public void screenShot(boolean success) {
72 | if(success){
73 | Bitmap bitmap=ScreenShotUtil.getInstance().getScreenShot();
74 | iv_pre.setImageBitmap(bitmap);
75 | }else {
76 | Toast.makeText(MainActivity.this,"初始化失败",Toast.LENGTH_SHORT).show();
77 | }
78 |
79 | }
80 | });
81 | }
82 | });
83 |
84 |
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/app/src/main/java/com/yorhp/screenrecord/MyService.java:
--------------------------------------------------------------------------------
1 | package com.yorhp.screenrecord;
2 |
3 | import android.app.Service;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.graphics.Bitmap;
7 | import android.graphics.PixelFormat;
8 | import android.os.Build;
9 | import android.os.IBinder;
10 | import android.support.annotation.RequiresApi;
11 | import android.view.Gravity;
12 | import android.view.MotionEvent;
13 | import android.view.View;
14 | import android.view.WindowManager;
15 | import android.widget.ImageView;
16 |
17 | import com.yorhp.recordlibrary.ScreenShotUtil;
18 |
19 |
20 | public class MyService extends Service {
21 |
22 | public static final int FLAG_LAYOUT_INSET_DECOR = 0x00000200;
23 | WindowManager.LayoutParams params3;
24 | WindowManager windowManager;
25 | ImageView btnView3;
26 |
27 | public MyService() {
28 | }
29 |
30 | @Override
31 | public IBinder onBind(Intent intent) {
32 | // TODO: Return the communication channel to the service.
33 | throw new UnsupportedOperationException("Not yet implemented");
34 | }
35 |
36 | @Override
37 | public void onCreate() {
38 | super.onCreate();
39 | createWindowView();
40 | }
41 |
42 | private void createWindowView() {
43 | btnView3 = new ImageView(getApplicationContext());
44 | btnView3.setImageResource(R.drawable.ic_star);
45 | btnView3.setScaleType(ImageView.ScaleType.CENTER_CROP);
46 | windowManager = (WindowManager) getApplicationContext()
47 | .getSystemService(Context.WINDOW_SERVICE);
48 |
49 | params3 = new WindowManager.LayoutParams();
50 |
51 | // 设置Window Type
52 |
53 |
54 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
55 | params3.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
56 | } else {
57 | params3.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
58 | }
59 |
60 |
61 | // 设置悬浮框不可触摸
62 | params3.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
63 | | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | FLAG_LAYOUT_INSET_DECOR;
64 | // 悬浮窗不可触摸,不接受任何事件,同时不影响后面的事件响应
65 | params3.format = PixelFormat.RGBA_8888;
66 | // 设置悬浮框的宽高
67 | params3.width = 400;
68 | params3.height = 400;
69 | params3.gravity = Gravity.TOP;
70 | params3.x = 300;
71 | params3.y = 200;
72 | btnView3.setOnTouchListener(new View.OnTouchListener() {
73 |
74 | //保存悬浮框最后位置的变量
75 | int lastX, lastY;
76 | int paramX, paramY;
77 |
78 | @Override
79 | public boolean onTouch(View v, MotionEvent event) {
80 |
81 | switch (event.getAction()) {
82 | case MotionEvent.ACTION_DOWN:
83 | lastX = (int) event.getRawX();
84 | lastY = (int) event.getRawY();
85 | paramX = params3.x;
86 | paramY = params3.y;
87 | break;
88 | case MotionEvent.ACTION_MOVE:
89 | int dx = (int) event.getRawX() - lastX;
90 | int dy = (int) event.getRawY() - lastY;
91 | params3.x = paramX + dx;
92 | params3.y = paramY + dy;
93 | // 更新悬浮窗位置
94 | windowManager.updateViewLayout(btnView3, params3);
95 | break;
96 | }
97 | return false;
98 | }
99 | });
100 | windowManager.addView(btnView3, params3);
101 | btnView3.setOnClickListener(new View.OnClickListener() {
102 | @RequiresApi(api = Build.VERSION_CODES.KITKAT)
103 | @Override
104 | public void onClick(View view) {
105 | Bitmap bitmap = ScreenShotUtil.getInstance().getScreenShot();
106 | btnView3.setImageBitmap(bitmap);
107 | }
108 | });
109 | }
110 |
111 | }
112 |
--------------------------------------------------------------------------------
/app/src/main/java/com/yorhp/screenrecord/app/MyApplication.java:
--------------------------------------------------------------------------------
1 | package com.yorhp.screenrecord.app;
2 |
3 | import android.app.Application;
4 | import android.os.Environment;
5 |
6 | import java.io.File;
7 |
8 | public class MyApplication extends Application {
9 |
10 | public static String baseDir = Environment.getExternalStorageDirectory() + "/ASceenUtil/";
11 |
12 | @Override
13 | public void onCreate() {
14 | super.onCreate();
15 | initDir();
16 | }
17 |
18 | void initDir() {
19 | File file = new File(baseDir);
20 | if (!file.exists()) {
21 | file.mkdirs();
22 | }
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_star.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tyhjh/ScreenShot/1b103a676c3103f866bb2092c63539e6d9b0ece2/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tyhjh/ScreenShot/1b103a676c3103f866bb2092c63539e6d9b0ece2/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tyhjh/ScreenShot/1b103a676c3103f866bb2092c63539e6d9b0ece2/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tyhjh/ScreenShot/1b103a676c3103f866bb2092c63539e6d9b0ece2/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tyhjh/ScreenShot/1b103a676c3103f866bb2092c63539e6d9b0ece2/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tyhjh/ScreenShot/1b103a676c3103f866bb2092c63539e6d9b0ece2/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tyhjh/ScreenShot/1b103a676c3103f866bb2092c63539e6d9b0ece2/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tyhjh/ScreenShot/1b103a676c3103f866bb2092c63539e6d9b0ece2/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tyhjh/ScreenShot/1b103a676c3103f866bb2092c63539e6d9b0ece2/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tyhjh/ScreenShot/1b103a676c3103f866bb2092c63539e6d9b0ece2/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #008577
4 | #00574B
5 | #D81B60
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | ScreenRecord
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/test/java/com/yorhp/screenrecord/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.yorhp.screenrecord;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 |
5 | repositories {
6 | google()
7 | jcenter()
8 | }
9 | dependencies {
10 | classpath 'com.android.tools.build:gradle:3.2.1'
11 |
12 |
13 | // NOTE: Do not place your application dependencies here; they belong
14 | // in the individual module build.gradle files
15 | }
16 | }
17 |
18 | allprojects {
19 | repositories {
20 | google()
21 | jcenter()
22 | maven { url 'https://jitpack.io' }
23 | }
24 | }
25 |
26 | task clean(type: Delete) {
27 | delete rootProject.buildDir
28 | }
29 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 |
15 |
16 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tyhjh/ScreenShot/1b103a676c3103f866bb2092c63539e6d9b0ece2/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/recordlibrary/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/recordlibrary/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion 25
5 |
6 |
7 |
8 | defaultConfig {
9 | minSdkVersion 15
10 | targetSdkVersion 25
11 | versionCode 1
12 | versionName "1.0"
13 |
14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
15 |
16 | }
17 |
18 | buildTypes {
19 | release {
20 | minifyEnabled false
21 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
22 | }
23 | }
24 |
25 | }
26 |
27 | dependencies {
28 | implementation fileTree(dir: 'libs', include: ['*.jar'])
29 |
30 | implementation 'com.android.support:appcompat-v7:25.0.0'
31 | implementation 'com.android.support.constraint:constraint-layout:1.1.3'
32 | testImplementation 'junit:junit:4.12'
33 | androidTestImplementation 'com.android.support.test:runner:1.0.2'
34 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
35 | }
36 |
--------------------------------------------------------------------------------
/recordlibrary/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/recordlibrary/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/recordlibrary/src/main/java/com/yorhp/recordlibrary/OnScreenShotListener.java:
--------------------------------------------------------------------------------
1 | package com.yorhp.recordlibrary;
2 |
3 | /**
4 | * 作者:Tyhj on 2018/10/22 01:32
5 | * 邮箱:tyhj5@qq.com
6 | * github:github.com/tyhjh
7 | * description:
8 | */
9 |
10 | /**
11 | * 录屏开启监听
12 | */
13 | public interface OnScreenShotListener {
14 | /**
15 | * 返回是否成功
16 | * @param success
17 | */
18 | void screenShot(boolean success);
19 | }
20 |
--------------------------------------------------------------------------------
/recordlibrary/src/main/java/com/yorhp/recordlibrary/ScreenRecordActivity.java:
--------------------------------------------------------------------------------
1 | package com.yorhp.recordlibrary;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 | import android.media.projection.MediaProjectionManager;
6 | import android.os.Build;
7 | import android.os.Bundle;
8 | import android.support.annotation.Nullable;
9 | import android.support.annotation.RequiresApi;
10 | import android.support.v7.app.AppCompatActivity;
11 | import android.util.Log;
12 |
13 | public class ScreenRecordActivity extends AppCompatActivity {
14 | public static final int REQUEST_MEDIA_PROJECTION = 18;
15 |
16 | public static boolean isScrennShot;
17 |
18 | @Override
19 | protected void onCreate(Bundle savedInstanceState) {
20 | super.onCreate(savedInstanceState);
21 | MediaProjectionManager projectionManager = null;
22 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
23 | projectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
24 | startActivityForResult(projectionManager.createScreenCaptureIntent(), REQUEST_MEDIA_PROJECTION);
25 | }else {
26 | requestCapturePermission();
27 | }
28 | }
29 |
30 |
31 | @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
32 | @Override
33 | protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
34 | super.onActivityResult(requestCode, resultCode, data);
35 | if (requestCode == REQUEST_MEDIA_PROJECTION) {
36 | if (isScrennShot) {
37 | ScreenShotUtil.getInstance().permissionResult(resultCode, data);
38 | } else {
39 | ScreenRecordUtil.getInstance().permissionResult(resultCode, data);
40 | }
41 | finish();
42 | }
43 | }
44 |
45 | //请求权限
46 | private void requestCapturePermission() {
47 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
48 | //5.0 之后才允许使用屏幕截图
49 | return;
50 | }
51 | if (isScrennShot) {
52 | startActivityForResult(ScreenShotUtil.mMediaProjectionManager.createScreenCaptureIntent(), REQUEST_MEDIA_PROJECTION);
53 | } else {
54 | startActivityForResult(ScreenRecordUtil.mMediaProjectionManager.createScreenCaptureIntent(), REQUEST_MEDIA_PROJECTION);
55 | }
56 |
57 | }
58 |
59 | @Override
60 | protected void onDestroy() {
61 | super.onDestroy();
62 | Log.e("ScreenRecordActivity:", "onDestroy");
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/recordlibrary/src/main/java/com/yorhp/recordlibrary/ScreenRecordUtil.java:
--------------------------------------------------------------------------------
1 | package com.yorhp.recordlibrary;
2 |
3 | import android.app.Activity;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.media.projection.MediaProjection;
7 | import android.media.projection.MediaProjectionManager;
8 | import android.os.Build;
9 | import android.support.annotation.RequiresApi;
10 | import android.util.DisplayMetrics;
11 | import android.view.WindowManager;
12 |
13 | import com.yorhp.recordlibrary.util.ScreenRecorder;
14 |
15 | import static android.app.Activity.RESULT_OK;
16 |
17 | @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
18 | public class ScreenRecordUtil {
19 |
20 | static ScreenRecorder mRecorder;
21 |
22 | private int mScreenDensity;
23 | private int mRecordWidth;
24 | private int mRecordheight;
25 | private int mScreenRecordBitrate = 30;
26 | private String savePath;
27 |
28 | static MediaProjectionManager mMediaProjectionManager;
29 |
30 | private ScreenRecordUtil() {
31 | }
32 |
33 |
34 | public void init(int width,int height,int screenRecordBitrate){
35 | mRecordWidth=width;
36 | mRecordheight=height;
37 | mScreenRecordBitrate=screenRecordBitrate;
38 |
39 | }
40 |
41 |
42 | public static ScreenRecordUtil getInstance() {
43 | return ScreenRecordHolder.instance;
44 | }
45 |
46 | public void start(Activity activity, String savePath) {
47 | this.savePath = savePath;
48 | WindowManager windowManager = (WindowManager) activity.getSystemService(Context.WINDOW_SERVICE);
49 | DisplayMetrics metrics = new DisplayMetrics();
50 | windowManager.getDefaultDisplay().getMetrics(metrics);
51 | mScreenDensity = metrics.densityDpi;
52 | mMediaProjectionManager = (MediaProjectionManager) activity.getSystemService(Context.MEDIA_PROJECTION_SERVICE);
53 | ScreenRecordActivity.isScrennShot = false;
54 | activity.startActivity(new Intent(activity, ScreenRecordActivity.class));
55 | }
56 |
57 |
58 | //返回可以开始录屏的数据
59 | public void permissionResult(int resultCode, Intent data) {
60 | if (resultCode == RESULT_OK && data != null) {
61 | MediaProjection mMediaProjection = mMediaProjectionManager.getMediaProjection(resultCode, data);
62 | mRecorder = new ScreenRecorder(mRecordWidth, mRecordheight, mScreenRecordBitrate, mScreenDensity, mMediaProjection, savePath);
63 | mRecorder.start();
64 | }
65 | }
66 |
67 |
68 | static class ScreenRecordHolder {
69 | private static ScreenRecordUtil instance = new ScreenRecordUtil();
70 | }
71 |
72 | public void destroy() {
73 | if (mRecorder != null) {
74 | mRecorder.quit();
75 | mRecorder = null;
76 | mMediaProjectionManager=null;
77 | }
78 | }
79 |
80 | public int getmScreenDensity() {
81 | return mScreenDensity;
82 | }
83 |
84 | public void setmScreenDensity(int mScreenDensity) {
85 | this.mScreenDensity = mScreenDensity;
86 | }
87 |
88 | public int getmRecordWidth() {
89 | return mRecordWidth;
90 | }
91 |
92 | public void setmRecordWidth(int mRecordWidth) {
93 | this.mRecordWidth = mRecordWidth;
94 | }
95 |
96 | public int getmRecordheight() {
97 | return mRecordheight;
98 | }
99 |
100 | public void setmRecordheight(int mRecordheight) {
101 | this.mRecordheight = mRecordheight;
102 | }
103 |
104 | public int getmScreenRecordBitrate() {
105 | return mScreenRecordBitrate;
106 | }
107 |
108 | public void setmScreenRecordBitrate(int mScreenRecordBitrate) {
109 | this.mScreenRecordBitrate = mScreenRecordBitrate;
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/recordlibrary/src/main/java/com/yorhp/recordlibrary/ScreenShotUtil.java:
--------------------------------------------------------------------------------
1 | package com.yorhp.recordlibrary;
2 |
3 | import android.app.Activity;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.graphics.Bitmap;
7 | import android.graphics.PixelFormat;
8 | import android.hardware.display.DisplayManager;
9 | import android.hardware.display.VirtualDisplay;
10 | import android.media.Image;
11 | import android.media.ImageReader;
12 | import android.media.projection.MediaProjection;
13 | import android.media.projection.MediaProjectionManager;
14 | import android.os.Build;
15 | import android.os.SystemClock;
16 | import android.util.DisplayMetrics;
17 | import android.view.WindowManager;
18 |
19 | import java.nio.ByteBuffer;
20 |
21 | import static android.app.Activity.RESULT_OK;
22 |
23 | /**
24 | * 作者:Tyhj on 2018/10/22 01:20
25 | * 邮箱:tyhj5@qq.com
26 | * github:github.com/tyhjh
27 | * description:
28 | */
29 |
30 | public class ScreenShotUtil {
31 |
32 | static MediaProjectionManager mMediaProjectionManager;
33 | static MediaProjection mMediaProjection;
34 | static ImageReader mImageReader;
35 | static VirtualDisplay mVirtualDisplay;
36 | static int mScreenDensity;
37 | static int mScreenWidth;
38 | static int mScreenHeight;
39 | WindowManager mWindowManager;
40 |
41 | public volatile boolean isInit;
42 |
43 | private OnScreenShotListener onScreenShotListener;
44 |
45 | private ScreenShotUtil() {
46 | }
47 |
48 | public static ScreenShotUtil getInstance() {
49 | return ScreenRecordUtilHolder.screenRecord;
50 | }
51 |
52 | /**
53 | * 获取第一次截图
54 | *
55 | * @param activity
56 | * @param listener
57 | */
58 | public void screenShot(Activity activity, OnScreenShotListener listener) {
59 | if (!isInit) {
60 | onScreenShotListener = listener;
61 | init(activity);
62 | ScreenRecordActivity.isScrennShot = true;
63 | activity.startActivity(new Intent(activity, ScreenRecordActivity.class));
64 | } else {
65 | listener.screenShot(true);
66 | }
67 | }
68 |
69 | /**
70 | * 获取截屏
71 | *
72 | * @return
73 | */
74 | public Bitmap getScreenShot() {
75 | if (isInit) {
76 | return startCapture();
77 | } else {
78 | return null;
79 | }
80 |
81 | }
82 |
83 | /**
84 | * 停止录屏
85 | */
86 | public void destroy() {
87 | onScreenShotListener = null;
88 | if (mVirtualDisplay == null) {
89 | return;
90 | }
91 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
92 | mVirtualDisplay.release();
93 | }
94 | mVirtualDisplay = null;
95 |
96 | if (mMediaProjection != null) {
97 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
98 | mMediaProjection.stop();
99 | }
100 | }
101 | isInit = false;
102 | }
103 |
104 | /**
105 | * 初始化
106 | *
107 | * @param activity
108 | */
109 | void init(final Activity activity) {
110 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
111 | mMediaProjectionManager = (MediaProjectionManager) activity.getSystemService(Context.MEDIA_PROJECTION_SERVICE);
112 | }
113 | ScreenUtil.getScreenSize(activity);
114 | mScreenWidth = ScreenUtil.SCREEN_WIDTH;
115 | mScreenHeight = ScreenUtil.SCREEN_HEIGHT;
116 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
117 | mImageReader = ImageReader.newInstance(ScreenUtil.SCREEN_WIDTH, ScreenUtil.SCREEN_HEIGHT, PixelFormat.RGBA_8888, 1);
118 | }
119 | mWindowManager = (WindowManager) activity.getSystemService(Context.WINDOW_SERVICE);
120 | DisplayMetrics metrics = new DisplayMetrics();
121 | mWindowManager.getDefaultDisplay().getMetrics(metrics);
122 | mScreenDensity = metrics.densityDpi;
123 | }
124 |
125 | /**
126 | * 返回可以开始录屏的数据
127 | *
128 | * @param resultCode
129 | * @param data
130 | */
131 | public void permissionResult(int resultCode, Intent data) {
132 | if (resultCode == RESULT_OK && data != null) {
133 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
134 | mMediaProjection = mMediaProjectionManager.getMediaProjection(resultCode, data);
135 | }
136 | virtualDisplay();
137 | }else {
138 | isInit = false;
139 | onScreenShotListener.screenShot(false);
140 | }
141 | }
142 |
143 | /**
144 | * 开始录屏
145 | */
146 | private void virtualDisplay() {
147 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
148 | mVirtualDisplay = mMediaProjection.createVirtualDisplay("screen-mirror",
149 | mScreenWidth, mScreenHeight, mScreenDensity, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
150 | mImageReader.getSurface(), null, null);
151 | isInit = true;
152 | onScreenShotListener.screenShot(true);
153 | } else {
154 | isInit = false;
155 | onScreenShotListener.screenShot(false);
156 | }
157 |
158 | }
159 |
160 | /**
161 | * 获取截图
162 | * @return
163 | */
164 | private Bitmap startCapture() {
165 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
166 | Image image = null;
167 | image = mImageReader.acquireLatestImage();
168 | while (image == null) {
169 | SystemClock.sleep(10);
170 | image = mImageReader.acquireLatestImage();
171 | }
172 | int width = image.getWidth();
173 | int height = image.getHeight();
174 | final Image.Plane[] planes = image.getPlanes();
175 | final ByteBuffer buffer = planes[0].getBuffer();
176 | //每个像素的间距
177 | int pixelStride = planes[0].getPixelStride();
178 | //总的间距
179 | int rowStride = planes[0].getRowStride();
180 | int rowPadding = rowStride - pixelStride * width;
181 | Bitmap bitmap = Bitmap.createBitmap(width + rowPadding / pixelStride, height, Bitmap.Config.ARGB_8888);
182 | bitmap.copyPixelsFromBuffer(buffer);
183 | bitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height);
184 | image.close();
185 | return bitmap;
186 | }
187 | return null;
188 | }
189 |
190 | private static class ScreenRecordUtilHolder {
191 | private static final ScreenShotUtil screenRecord = new ScreenShotUtil();
192 | }
193 |
194 | }
195 |
--------------------------------------------------------------------------------
/recordlibrary/src/main/java/com/yorhp/recordlibrary/ScreenUtil.java:
--------------------------------------------------------------------------------
1 | package com.yorhp.recordlibrary;
2 |
3 | import android.content.Context;
4 | import android.os.Build;
5 | import android.util.DisplayMetrics;
6 | import android.view.Display;
7 | import android.view.WindowManager;
8 |
9 | /**
10 | * Created by Tyhj on 2018/4/1.
11 | */
12 |
13 | public class ScreenUtil {
14 |
15 | public static int SCREEN_WIDTH = 0;
16 | public static int SCREEN_HEIGHT = 0;
17 |
18 | //保存屏幕大小
19 | public static void getScreenSize(Context context) {
20 | DisplayMetrics metrics = context.getResources().getDisplayMetrics();
21 | SCREEN_WIDTH = metrics.widthPixels;//获取到的是px,像素,绝对像素,需要转化为dpi
22 | SCREEN_HEIGHT = getRealHeight(context);
23 | }
24 |
25 | //获取正确的屏幕高度
26 | public static int getRealHeight(Context context) {
27 | WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
28 | Display display = wm.getDefaultDisplay();
29 | int screenHeight = 0;
30 |
31 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
32 | DisplayMetrics dm = new DisplayMetrics();
33 | display.getRealMetrics(dm);
34 | screenHeight = dm.heightPixels;
35 | } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
36 | try {
37 | screenHeight = (Integer) Display.class.getMethod("getRawHeight").invoke(display);
38 | } catch (Exception e) {
39 | DisplayMetrics dm = new DisplayMetrics();
40 | display.getMetrics(dm);
41 | screenHeight = dm.heightPixels;
42 | }
43 | }
44 | return screenHeight;
45 | }
46 |
47 |
48 | /**
49 | * 获取状态栏高度
50 | *
51 | * @param context
52 | * @return
53 | */
54 | public int getStatusBarHeight(Context context) {
55 | int result = 0;
56 | int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
57 | if (resourceId > 0) {
58 | result = context.getResources().getDimensionPixelSize(resourceId);
59 | }
60 | return result;
61 | }
62 |
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/recordlibrary/src/main/java/com/yorhp/recordlibrary/util/ScreenRecorder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2014 Yrom Wang
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.yorhp.recordlibrary.util;
18 |
19 | import android.hardware.display.DisplayManager;
20 | import android.hardware.display.VirtualDisplay;
21 | import android.media.MediaCodec;
22 | import android.media.MediaCodecInfo;
23 | import android.media.MediaFormat;
24 | import android.media.MediaMuxer;
25 | import android.media.projection.MediaProjection;
26 | import android.os.Build;
27 | import android.support.annotation.RequiresApi;
28 | import android.util.Log;
29 | import android.view.Surface;
30 |
31 | import java.io.IOException;
32 | import java.nio.ByteBuffer;
33 | import java.util.concurrent.atomic.AtomicBoolean;
34 |
35 | /**
36 | * @author Yrom
37 | */
38 | @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
39 | public class ScreenRecorder extends Thread {
40 | private static final String TAG = "ScreenRecorder";
41 |
42 | private int mWidth;
43 | private int mHeight;
44 | private int mBitRate;
45 | private int mDpi;
46 | private String mDstPath;
47 | private MediaProjection mMediaProjection;
48 | // parameters for the encoder
49 | private static final String MIME_TYPE = "video/avc"; // H.264 Advanced Video Coding
50 | private static final int FRAME_RATE = 30; // 30 fps
51 | private static final int IFRAME_INTERVAL = 10; // 10 seconds between I-frames
52 | private static final int TIMEOUT_US = 10000;
53 |
54 | private MediaCodec mEncoder;
55 | private Surface mSurface;
56 | private MediaMuxer mMuxer;
57 | private boolean mMuxerStarted = false;
58 | private int mVideoTrackIndex = -1;
59 | private AtomicBoolean mQuit = new AtomicBoolean(false);
60 | private MediaCodec.BufferInfo mBufferInfo = new MediaCodec.BufferInfo();
61 | private VirtualDisplay mVirtualDisplay;
62 |
63 | public ScreenRecorder(int width, int height, int bitrate, int dpi, MediaProjection mp, String dstPath) {
64 | super(TAG);
65 | mWidth = width;
66 | mHeight = height;
67 | mBitRate = bitrate;
68 | mDpi = dpi;
69 | mMediaProjection = mp;
70 | mDstPath = dstPath;
71 | }
72 |
73 |
74 | public ScreenRecorder(MediaProjection mp) {
75 | // 480p 2Mbps
76 | this(640, 480, 2000000, 1, mp, "/sdcard/test.mp4");
77 | }
78 |
79 | /**
80 | * stop task
81 | */
82 | public final void quit() {
83 | mQuit.set(true);
84 | }
85 |
86 |
87 | @Override
88 | public void run() {
89 | try {
90 | try {
91 | prepareEncoder();
92 | mMuxer = new MediaMuxer(mDstPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
93 | } catch (IOException e) {
94 | throw new RuntimeException(e);
95 | }
96 | mVirtualDisplay = mMediaProjection.createVirtualDisplay(TAG + "-display",
97 | mWidth, mHeight, mDpi, DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC,
98 | mSurface, null, null);
99 | Log.d(TAG, "created virtual display: " + mVirtualDisplay);
100 | recordVirtualDisplay();
101 |
102 | } finally {
103 | release();
104 | }
105 | }
106 |
107 | private void recordVirtualDisplay() {
108 | while (!mQuit.get()) {
109 | int index = mEncoder.dequeueOutputBuffer(mBufferInfo, TIMEOUT_US);
110 | Log.i(TAG, "dequeue output buffer index=" + index);
111 | if (index == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
112 | resetOutputFormat();
113 |
114 | } else if (index == MediaCodec.INFO_TRY_AGAIN_LATER) {
115 | Log.d(TAG, "retrieving buffers time out!");
116 | try {
117 | // wait 10ms
118 | Thread.sleep(10);
119 | } catch (InterruptedException e) {
120 | }
121 | } else if (index >= 0) {
122 |
123 | if (!mMuxerStarted) {
124 | throw new IllegalStateException("MediaMuxer dose not call addTrack(format) ");
125 | }
126 | encodeToVideoTrack(index);
127 |
128 | mEncoder.releaseOutputBuffer(index, false);
129 | }
130 | }
131 | }
132 |
133 | private void encodeToVideoTrack(int index) {
134 | ByteBuffer encodedData = mEncoder.getOutputBuffer(index);
135 |
136 | if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
137 | // The codec config data was pulled out and fed to the muxer when we got
138 | // the INFO_OUTPUT_FORMAT_CHANGED status.
139 | // Ignore it.
140 | Log.d(TAG, "ignoring BUFFER_FLAG_CODEC_CONFIG");
141 | mBufferInfo.size = 0;
142 | }
143 | if (mBufferInfo.size == 0) {
144 | Log.d(TAG, "info.size == 0, drop it.");
145 | encodedData = null;
146 | } else {
147 | Log.d(TAG, "got buffer, info: size=" + mBufferInfo.size
148 | + ", presentationTimeUs=" + mBufferInfo.presentationTimeUs
149 | + ", offset=" + mBufferInfo.offset);
150 | }
151 | if (encodedData != null) {
152 | encodedData.position(mBufferInfo.offset);
153 | encodedData.limit(mBufferInfo.offset + mBufferInfo.size);
154 | mMuxer.writeSampleData(mVideoTrackIndex, encodedData, mBufferInfo);
155 | Log.i(TAG, "sent " + mBufferInfo.size + " bytes to muxer...");
156 | }
157 | }
158 |
159 | private void resetOutputFormat() {
160 | // should happen before receiving buffers, and should only happen once
161 | if (mMuxerStarted) {
162 | throw new IllegalStateException("output format already changed!");
163 | }
164 | MediaFormat newFormat = mEncoder.getOutputFormat();
165 |
166 | Log.i(TAG, "output format changed.\n new format: " + newFormat.toString());
167 | mVideoTrackIndex = mMuxer.addTrack(newFormat);
168 | mMuxer.start();
169 | mMuxerStarted = true;
170 | Log.i(TAG, "started media muxer, videoIndex=" + mVideoTrackIndex);
171 | }
172 |
173 | private void prepareEncoder() throws IOException {
174 |
175 | MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, mWidth, mHeight);
176 | format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
177 | MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
178 | format.setInteger(MediaFormat.KEY_BIT_RATE, mBitRate);
179 | format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
180 | format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
181 |
182 | Log.d(TAG, "created video format: " + format);
183 | mEncoder = MediaCodec.createEncoderByType(MIME_TYPE);
184 | mEncoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
185 | mSurface = mEncoder.createInputSurface();
186 | Log.d(TAG, "created input surface: " + mSurface);
187 | mEncoder.start();
188 | }
189 |
190 | private void release() {
191 | if (mEncoder != null) {
192 | mEncoder.stop();
193 | mEncoder.release();
194 | mEncoder = null;
195 | }
196 | if (mVirtualDisplay != null) {
197 | mVirtualDisplay.release();
198 | }
199 | if (mMediaProjection != null) {
200 | mMediaProjection.stop();
201 | }
202 | if (mMuxer != null) {
203 | mMuxer.stop();
204 | mMuxer.release();
205 | mMuxer = null;
206 | }
207 | }
208 | }
209 |
--------------------------------------------------------------------------------
/recordlibrary/src/main/res/layout/activity_screen_record.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
--------------------------------------------------------------------------------
/recordlibrary/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | recordlibrary
3 |
4 |
--------------------------------------------------------------------------------
/recordlibrary/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
19 |
20 | #00000000
21 |
22 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':recordlibrary'
2 |
--------------------------------------------------------------------------------