[Android] (Android(安卓)视频悬浮窗)
自己最近再做Android相关的项目,所以闲下来的时候摸索了一下悬浮窗播放视频。
# 1【准备工作】
## 1.1 MediaPlayer的使用
既然是悬浮窗播放视频,那么首先就要了解Android怎么播放视频。这里做简单的介绍。
视频播放的调用步骤:
(1). 装载播放资源 MediaPlayer.setDataSource() , 也何以使用 MediaPlay.oncreate(context, R.raw.video);
(2). 在layout.xml中创建surfaceView;
(3). 调用MediaPlayer.setDisplay()设置surfaceHolder, surfaceHolder可以通过surfaceView.getHolder()方法获得;
(4). 调用MediaPlayer.prepare()来准备;
(5). 调用MediaPlayer.start()来播放视频。
在第三步之前要确保surfaceHolder已经准备好。因此 需要给surfaceHolder设置一个callBack,调用addCallback(),调用addCallBack()方法. callBack有三个回调函数:
SurfaceHolder.Callback { @Override // 待surfaceCreated回调,做MediaPlayer.setDisplay() public void surfaceCreated(SurfaceHolder holder) { MediaPlayer.setDisplay(); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceDestroyed(SurfaceHolder holder) { } }
以上是准备工作,简介介绍,如果看不懂可以参考 https://www.cnblogs.com/yxx123/p/5720907.html, 也可以后面看源码。
【实现原理】
1.1 设计思想:
悬浮窗画面需要继承Service,而不是Activity,一旦Activity退出就onStop掉了,所以需要继承Service后台运行。
1.2 权限设置和申请:
在Android 6.0之后大部分权限都是需要自行申请的,不能随意获取。我们这里需要申请两个权限,一个是启动悬浮窗 ,另一个由于我们播放的是网络资源,需要权限 。
1.3 悬浮窗口设置:
这个是Android 8.0之后的又一个坑,需要对不同版本的Android系统进行适配
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
layoutParams.type = WindowManager.LayoutParams.TYPE_PHONE;
}
在Android 8.0之前,悬浮窗口设置可以为TYPE_PHONE,这种类型是用于提供用户交互操作的非应用窗口。
而Android 8.0对系统和API行为做了修改,包括使用SYSTEM_ALERT_WINDOW权限的应用无法再使用以下窗口类型来在其他应用和窗口上方显示提醒窗口。
- TYPE_PHONE
- TYPE_PRIORITY_PHONE
- TYPE_SYSTEM_ALERT
- TYPE_SYSTEM_OVERLAY
- TYPE_SYSTEM_ERROR
如果需要实现在其他应用和窗口上方显示提醒窗口,那么必须该为TYPE_APPLICATION_OVERLAY的新类型。
如果在Android 8.0以上版本仍然使用TYPE_PHONE类型的悬浮窗口,则会出现如下异常信息
android.view.WindowManager B a d T o k e n E x c e p t i o n : U n a b l e t o a d d w i n d o w a n d r o i d . v i e w . V i e w R o o t I m p l BadTokenException: Unable to add window android.view.ViewRootImpl BadTokenException:Unabletoaddwindowandroid.view.ViewRootImplW@f8ec928 – permission denied for window type 2006
【具体实现】
3.1 在MainActivity中启动悬浮窗并开启权限
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 获取权限。如果当前获取了权限,直接就启动悬浮窗画面FloatingWindowService.class if (!Settings.canDrawOverlays(this)) { Toast.makeText(this, "当前无权限,请授权", Toast.LENGTH_SHORT); startActivityForResult(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName())), 1111); } else { startService(new Intent(MainActivity.this, FloatingWindowService.class)); } // 炮灰activity,启动悬浮窗之后就退掉 finish(); }}@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) { // 判断获取权限是否成功 if (requestCode == 1111) { if (!Settings.canDrawOverlays(this)) { Toast.makeText(this, "授权失败", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(this, "授权成功", Toast.LENGTH_SHORT).show(); startService(new Intent(MainActivity.this, FloatingService.class)); } }
3.2 悬浮窗画面
public class FloatingWindowService extends Service { private WindowManager windowManager; private WindowManager.LayoutParams layoutParams; private View display; private SurfaceHolder surfaceHolder; private SurfaceView surfaceView; public FloatingWindowService() { } @Override public void onCreate() { super.onCreate(); windowManager = (WindowManager) getSystemService(WINDOW_SERVICE); layoutParams = new WindowManager.LayoutParams(); Log.d("悬浮窗", "Build.VERSION.SDK_INT" + Build.VERSION.SDK_INT); if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O) { // android 8.0及以后使用 layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; } else { // android 8.0以前使用 layoutParams.type = WindowManager.LayoutParams.TYPE_PHONE; } layoutParams.gravity = Gravity.LEFT | Gravity.TOP; //该flags描述的是窗口的模式,是否可以触摸,可以聚焦等 layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; // 设置视频的播放窗口大小 layoutParams.width = 800; layoutParams.height = 450; layoutParams.x = 300; layoutParams.y = 300; } @Override public int onStartCommand(Intent intent, int flags, int startId) { showFloatingWindow(); return super.onStartCommand(intent, flags, startId); } @Override public IBinder onBind(Intent intent) { // TODO: Return the communication channel to the service. return null; } private void showFloatingWindow(){ if (Settings.canDrawOverlays(this)) { LayoutInflater layoutInflater = LayoutInflater.from(this); display = layoutInflater.inflate(R.layout.video_display, null); surfaceView = display.findViewById(R.id.videoplayer_display); // 获取surfaceView的sourfaceHolder surfaceHolder = surfaceView.getHolder(); final MediaPlayer mediaPlayer = new MediaPlayer(); // 设置视频播放流类型 mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); // 视频资源网址 Uri uri = Uri.parse("https://raw.githubusercontent.com/dongzhong/ImageAndVideoStore/master/Bruno%20Mars%20-%20Treasure.mp4"); try { // 设置视频播放资源,这里如果前面调用了MediaPlayer.create(contex, R.raw.video),就不用再次调用了 mediaPlayer.setDataSource(this,uri); } catch (IOException e) { e.printStackTrace(); } surfaceHolder.addCallback(new SurfaceHolder.Callback() { @Override public void surfaceCreated(SurfaceHolder holder) { // 视频播放设置 mediaPlayer.setDisplay(surfaceHolder); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceDestroyed(SurfaceHolder holder) { } }); mediaPlayer.prepareAsync(); mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { @Override public void onPrepared(MediaPlayer mp) { // 待视频资源准备好了,回调中播放视频资源, mediaPlayer.start(); //循环播放 mediaPlayer.setLooping(true); } }); windowManager.addView(display, layoutParams); display.setOnTouchListener(new FloatingOnTouchListener()); } }// touch移动视频窗口 private class FloatingOnTouchListener implements View.OnTouchListener { private int x; private int y; @Override public boolean onTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: x = (int) event.getRawX(); y = (int) event.getRawY(); break; case MotionEvent.ACTION_MOVE: int nowX = (int) event.getRawX(); int nowY = (int) event.getRawY(); int movedX = nowX - x; int movedY = nowY - y; Log.d("悬浮窗", "movedX = " + movedX + ", movedY =" + movedY); x = nowX; y = nowY; layoutParams.x = layoutParams.x + movedX; layoutParams.y = layoutParams.y + movedY; windowManager.updateViewLayout(view, layoutParams); break; default: break; } return false; } }}
3.3 AndroidManifest.xml
3.4 video_display.xml
以上就是主要代码
完整代码,可直接执行 https://download.csdn.net/download/lisiwei1994/10751626
参考借鉴 https://blog.csdn.net/dongzhong1990/article/details/80512706
更多相关文章
- Android(安卓)结束进程的方法
- Android带返回值的窗口跳转
- Android生成keystore是报错拒绝访问
- Android(安卓)SDK下载和更新失败的解决方法
- Android预制APP第一次打开时不弹权限提示页面
- [转] 软键盘android:windowSoftInputMode属性使用
- Android(安卓)常用的权限
- 关于android在Service中弹出Dialog对话框
- android唤醒屏幕--保持屏幕唤醒-Reprinted