{"id":19372935,"url":"https://github.com/android-notes/androidscreenshare","last_synced_at":"2025-05-16T18:06:23.137Z","repository":{"id":55766218,"uuid":"75452243","full_name":"android-notes/androidScreenShare","owner":"android-notes","description":"Android投屏及控制","archived":false,"fork":false,"pushed_at":"2019-10-08T09:41:25.000Z","size":5309,"stargazers_count":981,"open_issues_count":1,"forks_count":328,"subscribers_count":51,"default_branch":"master","last_synced_at":"2025-04-03T17:13:57.139Z","etag":null,"topics":["android","control","remote-control"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/android-notes.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"License","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-12-03T04:46:10.000Z","updated_at":"2025-03-28T02:16:18.000Z","dependencies_parsed_at":"2022-08-15T06:50:42.874Z","dependency_job_id":null,"html_url":"https://github.com/android-notes/androidScreenShare","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/android-notes%2FandroidScreenShare","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/android-notes%2FandroidScreenShare/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/android-notes%2FandroidScreenShare/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/android-notes%2FandroidScreenShare/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/android-notes","download_url":"https://codeload.github.com/android-notes/androidScreenShare/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248599294,"owners_count":21131257,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android","control","remote-control"],"created_at":"2024-11-10T08:25:47.204Z","updated_at":"2025-04-12T16:42:25.080Z","avatar_url":"https://github.com/android-notes.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n### \n### 效果\n![demo](https://github.com/android-notes/androidScreenShareAndControl/blob/master/demo.gif?raw=true)\n### 使用方式\n\n对于mac 笔记本用户：\n* Android手机开启开发者选项\n* 用数据线连接Android手机和mac\n* 运行lib目录下的Client，用于显示和控制\n* 运行lib目录下的Install，然后点击连接按钮，若不显示请安如下方式操作\n\n\n对于所有用户：\n* Android手机开启开发者选项\n* 用数据线连接Android手机和PC\n* 使用adb命令把项目根目录下的Main.dex放到手机中 `adb push Main.dex /sdcard/Main.dex`\n* 执行adb shell命令 `adb shell`\n* 执行命令 `export CLASSPATH=/sdcard/Main.dex`\n* 执行命令 `exec app_process /sdcard com.wanjian.puppet.Main`\n* 新建命令窗口，然后执行 `adb forward tcp:8888 localabstract:puppet-ver1`\n* 运行lib目录下的Client，用于显示和控制,点击连接按钮即可\n\n注意：高版本的android手机需要去开发者选项中开启 允许模拟点击\n\n完整命令如下\n```html\n\nMGJwanjian:sss wanjian$ adb push Main.dex /sdcard/Main.dex\n[100%] /sdcard/Main.dex\nMGJwanjian:sss wanjian$ adb shell\nshell@mx5:/ $ export CLASSPATH=/sdcard/Main.dex\nshell@mx5:/ $ exec app_process /sdcard com.wanjian.puppet.Main\nMGJwanjian:~ wanjian$ adb forward tcp:8888 localabstract:puppet-ver1\n\n\n```\n\n\n\n\n### 效果视频\n\n[https://github.com/android-notes/androidScreenShareAndControl/blob/master/%E6%95%88%E6%9E%9C%E8%A7%86%E9%A2%91.mp4](https://github.com/android-notes/androidScreenShareAndControl/blob/master/%E6%95%88%E6%9E%9C%E8%A7%86%E9%A2%91.mp4)\n\n\n\n### 原理\n\n原理和Vysor相同，Android提供了两个截屏方法Surface. screenshot和SurfaceControl. screenshot，\n这两个API是隐藏的，客户端没有权限调用，即使通过反射也得不到bitmap，我们可以使用adb命令\n启动一个进程，让该进程调用该API就可以得到bitmap了，然后通过socket把数据发送到PC即可。\n\n关键代码如下：\n```java\n\npublic class Main{\n    public static void main(String[]args){\n        Point size = new Point();\n        size.x = 1080;//最终截屏图片的大小，可以和屏幕不一样大\n        size.y = 1920;\n       String surfaceClassName;\n           if (Build.VERSION.SDK_INT \u003c= 17) {\n            surfaceClassName = \"android.view.Surface\";\n           } else {\n            surfaceClassName = \"android.view.SurfaceControl\";\n           }\n           Bitmap b = (Bitmap) Class.forName(surfaceClassName).getDeclaredMethod(\"screenshot\", new Class[]{Integer.TYPE, Integer.TYPE}).invoke(null, new Object[]{Integer.valueOf(size.x), Integer.valueOf(size.y)});\n    }\n}\n\n\n```\n\n* 然后按照如下操作：\n    * 把这个类编译成class文件\n    * 使用dx --dex --output=Main.dex  Main.class打包成dex文件\n    * 把dex文件发送到手机adb push Main.dex /sdcard/Main.dex\n    * 执行adb shell进入shell\n    * 设置类路径 export CLASSPATH=/sdcard/Main.dex\n    * 运行代码 exec app_process /sdcard Main\n\n 这样就可以调用到Main.main方法\n\n### 控制原理\n\n* PC端获取点击位置相对于当前显示窗口的比例\n* 把该比例发送给手机端\n* 手机端根据手机屏幕大小把比例转换成绝对位置并调用如下代码既可以实现远程控制，x和y是点击的绝对位置，action是动作，如按下，滑动，抬起等\n\n```java\n\n\n\nInputManager im = (InputManager) InputManager.class.getDeclaredMethod(\"getInstance\", new Class[0]).invoke(null, new Object[0]);\n\n\nMotionEvent.class.getDeclaredMethod(\"obtain\", new Class[0]).setAccessible(true);\n\nMethod injectInputEventMethod = InputManager.class.getMethod(\"injectInputEvent\", new Class[]{InputEvent.class, Integer.TYPE});\n\n\nMotionEvent event = MotionEvent.obtain(downTime, eventTime, action, x, y, pressure, 1.0f, 0, 1.0f, 1.0f, 0, 0);\nevent.setSource(InputDeviceCompat.SOURCE_TOUCHSCREEN);\n\ninjectInputEventMethod.invoke(im, new Object[]{event, Integer.valueOf(0)});\n\n\n\n```\n\n##### 注意：\n      获取屏幕大小时会用到几个特殊的API，Android sdk没有提供这几个API，但Android运行时却可以调用，\n      为了保证编译不报错，我们可以自己手写这几个API，保证包名，方法签名和系统中的相同即可，方法若有\n      返回值直接返回null即可。例如\n\n\n```java\n\npackage android.view;\nimport android.graphics.Point;\nimport android.os.IBinder;\n/**\n * Created by wanjian on 2017/4/4.\n */\npublic interface IWindowManager {\n    void getInitialDisplaySize(int i, Point displaySize);\n    int getRotation();\n    void getRealDisplaySize(Point displaySize);\n    abstract class Stub {\n         public static IWindowManager asInterface(IBinder invoke) {\n                     return null;\n         }\n     }\n}\n\n\n```\n\n\n```java\n\npackage android.view;\n\n/**\n * Created by wanjian on 2017/4/4.\n */\n\npublic interface DisplayInfo {\n}\n\n\n\n```\n\n\n\n```java\n package android.view;\n\n/**\n * Created by wanjian on 2017/4/4.\n */\n\npublic interface IRotationWatcher {\n}\n\n\n```\n\n\n由于Android的双亲委派类加载机制，Android会从系统路径下加载这几个类，并不会使用我们编写的类，我们编\n写的这几个类只是为了编译不报错，所以返回null也不会出现空指针\n\n\n\n\n\n\n### 简易版\n[http://blog.csdn.net/qingchunweiliang/article/details/69210431](http://blog.csdn.net/qingchunweiliang/article/details/69210431)\n\n\n\n### Github\n[https://github.com/android-notes/androidScreenShareAndControl](https://github.com/android-notes/androidScreenShareAndControl)\n\n\n### 附 编译class方式：\n\n最简单的方式：\n在`android studio`中右击`com.wanjian.puppet.Main`这个文件，选择 `run Main.main()`，编译后的class文件就会自动保存到  \n`androidScreenShareAndControl/shareandcontrollib/build/intermediates/classes/debug` 这个目录中 \n\n\n方式2：\n* 把 `android sdk`目录下的`android.jar`和`supportv4.jar`拷贝到   \n`androidScreenShareAndControl/shareandcontrollib/src/main/java`\n目录下\n\n* 同时在这个目录下新建`classes`文件夹，用于保存编译后的class文件，并把命令行切换到这个目录\n\n* 执行如下命令，其中`android.jar`和`support-v4-23.4.0-sources.jar` 是`android sdk`中的`jar`包,一个在`platforms/android-xx`文件夹下，一个在`extras/android/m2repository/com/android/support/support-v4`下。（命令中间用:分割，windows的话需要用;分割）\n\n* `javac -cp android.jar:support-v4-23.4.0-sources.jar:./  com/wanjian/puppet/Main.java  -d classes`\n\n* 这样就会在classes文件夹中生成class文件了 (JDK版本不能太高，不然会提示 unsupported class file version 52.0)\n\n![img](https://raw.githubusercontent.com/android-notes/blogimg/master/%E6%89%93%E5%8C%85class%E5%92%8Cdex.png)\n\n\n### 打包dex方式：\n首先命令窗口切换到 `/androidScreenShareAndControl/shareandcontrollib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes` 目录下，可以看到\n所有编译生成的class文件，如果没有先执行上面步骤生成class文件。\n\n然后使用 `dx  --dex --output=Main.dex ./`命令生成dex文件。dx命令文件在 `sdk/build-tools/版本号` 下\n\n![dex](https://raw.githubusercontent.com/android-notes/androidScreenShareAndControl/master/dex-package.png)\n\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandroid-notes%2Fandroidscreenshare","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fandroid-notes%2Fandroidscreenshare","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandroid-notes%2Fandroidscreenshare/lists"}