说明:
demo要求是录屏后上传视频到服务器。
demo下载地址:
1、重要代码
1.0、先加上权限申请
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.RECORD_AUDIO"/>
1.1、申请视频和存储权限
//请求视频和存储权限 private void requestPermission() { if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, STORAGE_REQUEST_CODE); } if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.RECORD_AUDIO}, AUDIO_REQUEST_CODE); } }
@Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == STORAGE_REQUEST_CODE || requestCode == AUDIO_REQUEST_CODE) { if (grantResults[0] != PackageManager.PERMISSION_GRANTED) { finish(); } } }
1.2、申请录屏和建立录屏服务
Intent intent = new Intent(this, RecordService.class); bindService(intent, connection, BIND_AUTO_CREATE);
//录屏权限同意后返回 @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == RECORD_REQUEST_CODE && resultCode == RESULT_OK) { mediaProjection = projectionManager.getMediaProjection(resultCode, data); recordService.setMediaProject(mediaProjection); recordService.startRecord(); startBtn.setText(R.string.stop_record);//设置文字 countTimer();//开始计时 openZFB();//打开支付宝 luPinStatus = true;//设置录屏状态为true } }
//建立录屏服务 private ServiceConnection connection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName className, IBinder service) { DisplayMetrics metrics = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(metrics); RecordService.RecordBinder binder = (RecordService.RecordBinder) service; recordService = binder.getRecordService(); recordService.setConfig(metrics.widthPixels, metrics.heightPixels, metrics.densityDpi); startBtn.setEnabled(true); startBtn.setText(recordService.isRunning() ? R.string.stop_record : R.string.start_record); } @Override public void onServiceDisconnected(ComponentName arg0) { } };
2、完整代码
1、RecordApplication.java
package com.baofu.lupin; import android.app.Application; import android.content.Context; import android.content.Intent; /** * Created by BF100165 on 2018/6/21. */ public class RecordApplication extends Application { private static RecordApplication application; @Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); application = this; } @Override public void onCreate() { super.onCreate(); // 启动 Marvel service startService(new Intent(this, RecordService.class)); } public static RecordApplication getInstance() { return application; } }
2、AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.baofu.lupin"> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.RECORD_AUDIO"/> <application android:name=".RecordApplication" android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity" android:label="@string/app_name" android:theme="@style/AppTheme.NoActionBar"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <service android:name=".RecordService"/> </application> </manifest>
3、MainActivity.java
package com.baofu.lupin; import android.Manifest; import android.content.ActivityNotFoundException; import android.content.ComponentName; import android.content.Intent; import android.content.ServiceConnection; import android.content.pm.PackageManager; import android.hardware.display.DisplayManager; import android.hardware.display.VirtualDisplay; import android.media.projection.MediaProjection; import android.media.projection.MediaProjectionManager; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.CountDownTimer; import android.os.Environment; import android.os.IBinder; import android.support.annotation.RequiresApi; import android.support.design.widget.FloatingActionButton; import android.support.design.widget.Snackbar; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.text.TextUtils; import android.util.DisplayMetrics; import android.util.Log; import android.view.View; import android.view.Menu; import android.view.MenuItem; import android.widget.Button; import android.widget.TextView; import android.widget.Toast; import java.io.BufferedReader; import java.io.File; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import java.util.HashMap; import java.util.Map; public class MainActivity extends AppCompatActivity { private static final int RECORD_REQUEST_CODE = 101; private static final int STORAGE_REQUEST_CODE = 102; private static final int AUDIO_REQUEST_CODE = 103; private MediaProjectionManager projectionManager; private MediaProjection mediaProjection; private RecordService recordService; private Button startBtn; private TextView tv_timer; private TextView btn_upload; private CountDownTimer timer; private boolean luPinStatus = false;//录屏状态初始为false @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); projectionManager = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE); setContentView(R.layout.activity_main); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); startBtn = (Button) findViewById(R.id.start_record); tv_timer = (TextView) findViewById(R.id.tv_timer); btn_upload = (TextView) findViewById(R.id.btn_upload); //开始录屏 startBtn.setEnabled(false); startBtn.setOnClickListener(new View.OnClickListener() { @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) @Override public void onClick(View v) { if (recordService != null && recordService.isRunning()) { recordService.stopRecord();//结束录屏 startBtn.setText(R.string.start_record); timer.cancel();//结束计时 luPinStatus = false;//录屏状态为false } else { Intent captureIntent = projectionManager.createScreenCaptureIntent(); startActivityForResult(captureIntent, RECORD_REQUEST_CODE); } } }); //上传视频 btn_upload.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if(luPinStatus) { Toast.makeText(MainActivity.this, "请先结束录屏", Toast.LENGTH_SHORT).show(); return; } final String filePath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + "ScreenRecord" + "/123456.mp4"; File file = new File(filePath); final String url = "http://10.0.19.48/creditApi/video"; if(!file.exists()) { Toast.makeText(MainActivity.this, "请先录屏芝麻信用", Toast.LENGTH_SHORT).show(); return; } new Thread(new Runnable() { @Override public void run() { try { UploadUtils.uploadFile(MainActivity.this, btn_upload, tv_timer, url, filePath); } catch (Exception e) { e.printStackTrace(); } } }).start(); } }); Intent intent = new Intent(this, RecordService.class); bindService(intent, connection, BIND_AUTO_CREATE); requestPermission(); } //请求视频和存储权限 private void requestPermission() { if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, STORAGE_REQUEST_CODE); } if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.RECORD_AUDIO}, AUDIO_REQUEST_CODE); } } @Override protected void onDestroy() { super.onDestroy(); unbindService(connection); } //录屏权限同意后返回 @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == RECORD_REQUEST_CODE && resultCode == RESULT_OK) { mediaProjection = projectionManager.getMediaProjection(resultCode, data); recordService.setMediaProject(mediaProjection); recordService.startRecord(); startBtn.setText(R.string.stop_record);//设置文字 countTimer();//开始计时 openZFB();//打开支付宝 luPinStatus = true;//设置录屏状态为true } } @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == STORAGE_REQUEST_CODE || requestCode == AUDIO_REQUEST_CODE) { if (grantResults[0] != PackageManager.PERMISSION_GRANTED) { finish(); } } } //建立录屏服务 private ServiceConnection connection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName className, IBinder service) { DisplayMetrics metrics = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(metrics); RecordService.RecordBinder binder = (RecordService.RecordBinder) service; recordService = binder.getRecordService(); recordService.setConfig(metrics.widthPixels, metrics.heightPixels, metrics.densityDpi); startBtn.setEnabled(true); startBtn.setText(recordService.isRunning() ? R.string.stop_record : R.string.start_record); } @Override public void onServiceDisconnected(ComponentName arg0) { } }; //界面倒计时 private void countTimer() { /** 倒计时60秒,一次1秒 */ timer = new CountDownTimer(60 * 1000, 1000) { @Override public void onTick(long millisUntilFinished) { // TODO Auto-generated method stub tv_timer.setText("还剩" + millisUntilFinished / 1000 + "秒"); } @Override public void onFinish() { tv_timer.setText("倒计时完毕了"); } }.start(); } //打开支付宝 private void openZFB() { try { PackageManager packageManager = this.getApplicationContext().getPackageManager(); Intent intent = packageManager. getLaunchIntentForPackage("com.eg.android.AlipayGphone"); startActivity(intent); } catch (Exception e) { String url = "https://ds.alipay.com/?from=mobileweb"; Intent intent = new Intent(Intent.ACTION_VIEW); intent.setData(Uri.parse(url)); startActivity(intent); } } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.menu_main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); //noinspection SimplifiableIfStatement if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } }
main.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior" tools:context="com.baofu.lupin.MainActivity" tools:showIn="@layout/activity_main"> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:orientation="vertical"> <TextView android:id="@+id/tv_timer" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:text="倒计时 - 打开支付宝" /> </LinearLayout> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_centerHorizontal="true" android:orientation="vertical"> <Button android:id="@+id/btn_upload" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="上传录屏"/> <Button android:id="@+id/start_record" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_marginBottom="10dp" android:text="@string/start_record"/> </LinearLayout> </RelativeLayout>
strings.xml
<resources> <string name="app_name">录屏</string> <string name="action_settings">Settings</string> <string name="start_record">开始录屏</string> <string name="stop_record">停止录屏</string> </resources>
3、RecordService.java
package com.baofu.lupin; import android.annotation.TargetApi; import android.app.Service; 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.os.Binder; import android.os.Build; import android.os.Environment; import android.os.HandlerThread; import android.os.IBinder; import android.widget.Toast; import java.io.File; import java.io.IOException; /** * 录屏服务 * Created by BF100165 on 2018/6/21. */ public class RecordService extends Service { private MediaProjection mediaProjection; private MediaRecorder mediaRecorder; private VirtualDisplay virtualDisplay; private boolean running; private int width = 720; private int height = 1080; private int dpi; @Override public IBinder onBind(Intent intent) { return new RecordBinder(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { return START_STICKY; } @Override public void onCreate() { super.onCreate(); HandlerThread serviceThread = new HandlerThread("service_thread", android.os.Process.THREAD_PRIORITY_BACKGROUND); serviceThread.start(); running = false; mediaRecorder = new MediaRecorder(); } @Override public void onDestroy() { super.onDestroy(); } public void setMediaProject(MediaProjection project) { mediaProjection = project; } public boolean isRunning() { return running; } public void setConfig(int width, int height, int dpi) { this.width = width; this.height = height; this.dpi = dpi; } public boolean startRecord() { if (mediaProjection == null || running) { return false; } initRecorder(); createVirtualDisplay(); mediaRecorder.start(); running = true; return true; } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public boolean stopRecord() { if (!running) { return false; } running = false; mediaRecorder.stop(); mediaRecorder.reset(); virtualDisplay.release(); mediaProjection.stop(); return true; } @TargetApi(Build.VERSION_CODES.LOLLIPOP) private void createVirtualDisplay() { virtualDisplay = mediaProjection.createVirtualDisplay("MainScreen", width, height, dpi, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, mediaRecorder.getSurface(), null, null); } private void initRecorder() { mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE); mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); mediaRecorder.setOutputFile(getsaveDirectory() + "123456" + ".mp4"); mediaRecorder.setVideoSize(width, height); mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); mediaRecorder.setVideoEncodingBitRate(5 * 1024 * 1024); mediaRecorder.setVideoFrameRate(30); try { mediaRecorder.prepare(); } catch (IOException e) { e.printStackTrace(); } } public String getsaveDirectory() { if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { String rootDir = Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + "ScreenRecord" + "/"; File file = new File(rootDir); if (!file.exists()) { if (!file.mkdirs()) { return null; } } Toast.makeText(getApplicationContext(), rootDir, Toast.LENGTH_SHORT).show(); return rootDir; } else { return null; } } public class RecordBinder extends Binder { public RecordService getRecordService() { return RecordService.this; } } }
4、UploadUtils.java
package com.baofu.lupin; import android.app.Activity; import android.content.Context; import android.util.Log; import android.widget.TextView; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLEncoder; import java.util.HashMap; import java.util.Map; /** * 上传视频 */ public class UploadUtils { public static String uploadFile(Activity activity, final TextView btn, final TextView content, String serverUrl, final String localFilePath) throws Exception { activity.runOnUiThread(new Runnable() { @Override public void run() { btn.setText("上传中..."); Log.e("上传视频", "地址:" + localFilePath); } }); File file = new File(localFilePath); Map<String, Object> postDataParams = new HashMap<>(); postDataParams.put("api_key", "asdfasf"); postDataParams.put("video_type", "android"); postDataParams.put("fileName", file.getName()); URL url = new URL(serverUrl + "?" + getPostDataString(postDataParams)); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setDoOutput(true); conn.setUseCaches(false); conn.setRequestMethod("POST"); conn.setRequestProperty("Content-Type", "text/html"); conn.setRequestProperty("Cache-Control", "no-cache"); conn.setRequestProperty("Charsert", "UTF-8"); conn.connect(); conn.setConnectTimeout(20000); DataOutputStream out = new DataOutputStream(conn.getOutputStream()); DataInputStream in = new DataInputStream(new FileInputStream(file)); int bytes = 0; byte[] bufferOut = new byte[2048]; while ((bytes = in.read(bufferOut)) != -1) { out.write(bufferOut, 0, bytes); } in.close(); out.flush(); out.close(); InputStream inputStream = conn.getInputStream(); final String respStr = getRespString(inputStream); activity.runOnUiThread(new Runnable() { @Override public void run() { btn.setText("上传完成-点击继续上传"); content.setText(respStr); Log.e("上传视频", "返回:" + respStr); } }); conn.disconnect(); return respStr; } public static String getPostDataString(Map<String, Object> params) throws Exception { StringBuilder result = new StringBuilder(); boolean first = true; for (Map.Entry<String, Object> entry : params.entrySet()) { String key = entry.getKey(); Object value = entry.getValue(); if (first) first = false; else result.append("&"); result.append(URLEncoder.encode(key, "UTF-8")); result.append("="); result.append(URLEncoder.encode(value.toString(), "UTF-8")); } return result.toString(); } public static String getRespString(InputStream is) throws Exception { StringBuffer strBuf = new StringBuffer(); byte[] bytes = new byte[4096]; int r = 0; while ((r = is.read(bytes)) > 0) { strBuf.append(new String(bytes, 0, r, "UTF-8")); } return strBuf.toString(); } }