默认情况:(会导致重新播放)
onPause—>onStop—>onDestroy—>onCreate—>onStart—>onResume
屏幕横竖切换导致生命周期重新执行
解决:
视频的总时长和SeekBar的setMax(总时长)
实例化Handler,每秒得到当前视频播放的进度,SeekBar,setProgress(当前进度);
视频的总时长和SeekBar的setMax(总时长)
设置SeekBar状态变化的监听
静态注册:在功能清淡文件注册,只要软件安装在手机上,就算软件不启动,也能收到对应的广播;
…___________…
但是有局限:比如电量,锁屏开屏等监听就不能再静态中注册
(频繁变化的,底层系统不允许)
动态注册:只有注册的代码被执行后,才能收到对应的广播
@Override
protected void onDestroy() {
//取消广播
if (receiver!=null){
unregisterReceiver( receiver );
receiver=null;
}
super.onDestroy();
}
要在super()之前
因为要先释放子类,后释放父类
swich可以的if都可以,反之不可以
因为if可以取范围
报错提示:Parcel: unable to marshal…
自定义的对象可以通过bundle传递,前提条件就是自定义的类实现Parcelable,Serializable俩个接口之一
在Activity之间传递对象有俩个选择
Parcelable和Serializable的选择
Parcelable是android自带的,Serializable兼容性更好,也更简便
1.在使用内存的时候,Parcelable类比Serializable性能高,所以推荐使用Parcelable类
Serializable在序列化的时候会产生大量的临时变量,从而引起频繁的GC
Parcelable不能使用在要将数据存储在磁盘上的情况
对象为什么要序列化
1.永久性保存对象,保存对象的字节序列到本地文件
2.通过序列化对象在网络中传递对象
3.通过序列化对象在进程中传递对象
1.定义:private GestureDetector detector;
2.实例化(重写方法:双击,单击,长按)
3.onTouchEvent():方法中把事件传递给手势识别器(重要)
解决BUG:拖动进度条,音量条的时候,超过4秒,控制面板隐藏
1.实例化AudioManger :当前音量,最大音量
2.SeekBar.setMax(最大音量)
SeeKBar.setProGress(当前的音量)
3.设置SeekBar状态变化,
从上往下
float distanceY=startY-endY<0
start+负数:音量变小
…
从上往下
float distanceY=startY-endY>0
start+正数:音量变大
滑动屏幕的总距离:总距离=改变声音:音量最大值(比值)
改变声音=(滑动屏幕的距离:总距离)*音量最大值
最终声音=原来的+改变声音
实现
1.onTouchEvent方法里,在down的时候:startY touchRang:总距离
移除消息
mVol
2.在move
float endy=envent.getY();
float distanceY=endY-startY;
updataVoice();
3.在up的时候重新发消息
方法:参照系统源代码
Intent intent=new Intent( );
//重要!不加这个不行
intent.setAction(Intent.ACTION_VIEW);
intent.setDataAndType( Uri.parse("http://192.168.137.1:8080/zjq.mp4"),"video/*");
startActivity( intent );
1.用手机连上pc的wifi
2.开启电脑tomcat,并且把一个视频放入
3.查看ip地址(cmd-ipconfig),找Ipv4
前提:播放视频的时候,网络比较慢才会出现
1.Android 4.2.2 中才把监听卡封装在videoView中
2.代码
3.设置监听
class MyOnInfoListener implements MediaPlayer.OnInfoListener {
@Override
public boolean onInfo(MediaPlayer mp, int what, int extra) {
switch (what) {
case MediaPlayer.MEDIA_INFO_BUFFERING_START://视频卡了,开始缓存
Toast.makeText( SystemVideoPlayer.this, "卡了", Toast.LENGTH_SHORT ).show();
ll_buffer.setVisibility( View.VISIBLE );
break;
case MediaPlayer.MEDIA_INFO_BUFFERING_END://卡结束了
Toast.makeText( SystemVideoPlayer.this, "卡结束了", Toast.LENGTH_SHORT ).show();
ll_buffer.setVisibility( View.GONE );
break;
}
return true;
}
}
校验播放进度判断是否监听卡
原理:
当前播放进度 —上一次播放进度<0 -------------卡一秒钟刷新一次进度,下一次比这次刷新出的进度少了,就证明卡顿
能得到当前的播放进度的话,建议用校验播放进度判断是否监听卡这种方式(直播不能得到当前播放进度)
如果不能得到当前播放进度,可以用系统的监听卡
1.下载地址: https://www.vitamio.org/Download/
2.解压,运行,参照案例集成(案例)
1.关联Vitamio库 compile project(’:vitamio’)
2.把功能清单文件对应的配置拷贝过去,
权限
配置
3.把系统的SystemVideoPlayer 复制一份,改名为VitamioVideoPlayer ,导入的包全部换成Vitamio的包
4.布局文件改名
5.初始化Vitamio库 Vitamio.isInitialized(this);
6.当系统播放器播放出错的时候跳转到万能播放器播放
注意:
1.把数据传入VitamioVideo播放器、
2.关闭系统播放器
.1.在AppLication 中初始化xUtils
创建MyApplication类
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
x.Ext.init( this );
x.Ext.setDebug( BuildConfig.DEBUG );
}
}
清单文件配置
2.用xUtils实例化布局
x.view().inject(this,view );
…
注意:xUtils实例化布局 第一个参数不是上下文,第二个参数是实例化的布局
注解方式,实例化布局中内容
@ViewInject( R.id.listview )
private ListView mListView;
注意:需要先 x.view().inject(this,view );
3.用xUtils连网请求
//视频内容
RequestParams params=new RequestParams( Constants.NET_URL );
x.http().get( params, new Callback.CommonCallback
4.用xUtils显示图片
x.image().bind( iv_icon,mediaItem.getImageUrl() );
在Android P 使用HttpUrlConnection进行http请求会出现以下异常
java.net.UnknownServiceException: CLEARTEXT communication ** not permitted by network security policy
在Android P系统的设备上,如果应用使用的是非加密的明文流量的http网络请求,则会导致该应用无法进行网络请求,https则不会受影响
> 解决方法:
在application标签里面加个usesCleartextTraffic然后新增一条 uses-library标签
usesCleartextTraffic 是允许明文请求
public void putString(Context context,String key,String values){
SharedPreferences sharedPreferences=context.getSharedPreferences( "VideoCache",Context.MODE_PRIVATE );
sharedPreferences.edit().putString( key,values ).commit();
}
1.直播网络电视
从服务器一边下载一边播放
2.当前环境播出去
手机生成视频,上传到服务器,由服务器分发到不同的手机上
第一种方式:直接使用VideoView(建议)
第二种方式:在Activity中封装MediaPlayer和surfaceView
1.方式:
广播;Intent;Handler;接口回调;Application;EventBus,AIDL
案例:AIDL
> 1.开启服务
Intent intent = new Intent(this,MyService.class);
intent.setAction("com.xxx.binder.action.AIDLService");
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
// startService(intent);
2.得到服务的引用
private AIDLService mService;//服务的代理类
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
//重要
mService = AIDLService.Stub.asInterface(service);
try {
mService.registerTestCall(mCallback);
} catch (RemoteException e) {
}
}
public void onServiceDisconnected(ComponentName className) {
mService = null;
}
};
3.配置服务
service android:name="com.yanguangfu.binder.MyService" >
4.aidl文件
5.服务类
1.创建MusicPlayerSerivce 继承 Service
把常用的方法先写出来,但不实现
2.参照服务的方法,写对应的Aidl文件
3.把服务在功能清单文件中注册
4.把AIDL文件生成的类在服务中绑定
5.绑定方式启动Service
6.在MusicPlayerSerivce 中得到音乐列表
7.当绑定成功的时候,播放音乐
8.在服务中openAudio的功能
9.实现音乐的播放和暂停
关闭后,在设置为NULL,有助于垃圾回收站快速回收
unregisterReceiver( receiver );
receiver=null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
//透明状态栏
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
//透明导航栏
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); }
@Override
protected void onDestroy() {
handler.removeCallbacksAndMessages( null );
}
manager= (NotificationManager) getSystemService( NOTIFICATION_SERVICE );
Intent intent=new Intent( this,AudioPlayerActivity.class );
intent.putExtra( "Notification",true );
PendingIntent pendingIntent=PendingIntent.getActivity(this, 1,intent,PendingIntent.FLAG_CANCEL_CURRENT );
Notification notification=new Notification.Builder(this)
.setSmallIcon( R.drawable.notification_music_playing )
.setContentText("正在播放:"+getName() )
.setContentTitle( "手机影音" )
.setContentIntent( pendingIntent )
.build();
manager.notify( 1,notification );
mediaPlayer.start();
8.0
String channelID = "1";
String channelName = "channel_name";
NotificationChannel channel = new NotificationChannel(channelID, channelName, NotificationManager.IMPORTANCE_HIGH);
NotificationManager manager = (NotificationManager) getSystemService( Context.NOTIFICATION_SERVICE );
manager.createNotificationChannel(channel);
Notification.Builder builder =new Notification.Builder(this);
Intent intent = new Intent( this, AudioPlayerActivity.class );
intent.putExtra( "Notification", true );
PendingIntent pendingIntent = PendingIntent.getActivity( this, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT);
builder.setSmallIcon( R.drawable.notification_music_playing );
builder.setContentText("正在播放:" + getName());
builder.setContentTitle("手机影音" );
//使用过后,如何取消呢?Android为我们提供两种方式移除通知,一种是Notification自己维护,使用setAutoCancel()方法设置是否维护
builder.setAutoCancel( true );
builder.setContentIntent( pendingIntent );
//创建通知时指定channelID
builder.setChannelId(channelID);
Notification notification = builder.build();
manager.notify( 1,notification );
8.0之前
/* manager = (NotificationManager) getSystemService( NOTIFICATION_SERVICE );
Intent intent = new Intent( this, AudioPlayerActivity.class );
intent.putExtra( "Notification", true );
PendingIntent pendingIntent = PendingIntent.getActivity( this, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT);
Notification notification = new Notification.Builder( this )
.setSmallIcon( R.drawable.notification_music_playing )
.setContentText( "正在播放:" + getName() )
.setContentTitle( "手机影音" )
.setContentIntent( pendingIntent )
.build();
manager.notify( 1, notification );*/
1.针对Android优化的发布、订阅事件总线
主要功能:代替Intent,Handler,BroadCast在Frament,Activity,Service,线程之间传递消息,
优点:是开销小,代码少;发送者和接受者解耦
2.方法:
onEvent:在子线程发布,就在子线程运行,在主线程发布,就在主线程运行
···································································································onEventMainThread:(可以替代Handler)不论事件是在什么线程中发布, onEventMainThread都在主线程中执行,所以不能再找个方法中执行耗时操作
···································································································onEventBackground:在主线程发出,在子线程执行;在子线程发出,在该子线程执行
···································································································onEventAsync:(可以做耗时操作)使用找个函数作为订阅函数,那么无论事件在哪个线程发布,都会创建新的子线程再执行onEventAsync
新特性:
- 方法名,不需要onEven开头
- 方法的线程模式ThreadMode 可以配置,俩个方法通用一个参数,可以设置优先级(值大优先收到)
- 使用EventBus 3.1
1.关联
2.注册
EventBus.getDefault().register( this );
3.订阅方法
/**
* //EventBus
* sticky = false 是否粘性
* 需要传一个参数
* 方法必须公有
*/
@Subscribe(threadMode = ThreadMode.MAIN,sticky = false,priority = 0)
public void showData(MediaItem mediaItem) {
showViewData();
checkPlaymode();
}
4.取消注册
EventBus.getDefault().unregister( this );
5.发消息
EventBus.getDefault().post( mediaItem );
EventBus 源码
接受类,把方法放在一个集合里就注册,然后订阅,把所有东西放在集合中,post取出来,swich分别操作,invoke反射执行
1.创建歌词显示控件 showLyric
- 继承TextView
2.创建歌词列表
- 假设的歌词
- 歌词列表
3.绘制歌词
- 绘制前面部分
- 绘制当前部分
- 绘制后面部分
4.根据当前播放进度,发消息实时更新
…歌词绘制分位置,
- handler 刷新
5.解析歌词
- 解析每一句
- 加入到列表中
- 排序
- 计算每句歌词的高亮时间
- 实现歌词同步
- 根据不同的歌曲,加载不同的歌词文件
1.用死循环,发消息,注意先移除,否则会使系统变慢
handler.removeMessages( SHOW_LYRIC );
handler.sendEmptyMessage( SHOW_LYRIC );
2.重新绘制
invalidate();//在主线程中调用
postInvalidate(); //在子线程中
1.对String或Integer这些已经实现Comparable接口的类来说,可以直接使用
Collections.sort方法传入list参数来实现默认方式(正序)排序;
2.如果不想使用默认方式(正序)排序,可以通过Collections.sort传入第二个参数类型为Comparator来自定义排序规则;
Collections.sort(objects, new Comparator() {
@Override
public int compare(TestEntry t1, TestEntry t2) {
//当返回0的时候排序方式是 t1,t2
//当返回1的时候排序方式是 t2,t1
//当返回-1的时候排序方式是t1,t2
//注意
//返回值大于1效果等同于1
//返回值小于1 效果等同于0,-1
return 0;
}
});
1.注意设置float类型,因为平移就那么小数点一点,如果是int直接省略,所以没有移动效果
//歌词缓缓推移
float plush=0;
if (sleepTime==0){
plush=0;
}else {
//平移
//这一句所花时间:休眠时间=移动的距离:总距离(行高)
//移动的距离=(这一句所花时间:休眠时间)*总距离(行高)
// float delta=((currentPosition-timePoint)/sleepTime)*textHight;
//屏幕的坐标=行高+移动的距离
plush=textHight+((currentPosition-timePoint)/sleepTime)*textHight;
}
canvas.translate( 0,-plush );
2.歌词乱码问题:根据歌词文件不同,设置不同的编码格式
判断文件编码(工具类)
/**
* 判断文件编码
* @param file 文件
* @return 编码:GBK,UTF-8,UTF-16LE
*/
public String getCharset(File file) {
String charset = "GBK";
byte[] first3Bytes = new byte[3];
try {
boolean checked = false;
BufferedInputStream bis = new BufferedInputStream(
new FileInputStream(file));
bis.mark(0);
int read = bis.read(first3Bytes, 0, 3);
if (read == -1)
return charset;
if (first3Bytes[0] == (byte) 0xFF && first3Bytes[1] == (byte) 0xFE) {
charset = "UTF-16LE";
checked = true;
} else if (first3Bytes[0] == (byte) 0xFE
&& first3Bytes[1] == (byte) 0xFF) {
charset = "UTF-16BE";
checked = true;
} else if (first3Bytes[0] == (byte) 0xEF
&& first3Bytes[1] == (byte) 0xBB
&& first3Bytes[2] == (byte) 0xBF) {
charset = "UTF-8";
checked = true;
}
bis.reset();
if (!checked) {
int loc = 0;
while ((read = bis.read()) != -1) {
loc++;
if (read >= 0xF0)
break;
if (0x80 <= read && read <= 0xBF)
break;
if (0xC0 <= read && read <= 0xDF) {
read = bis.read();
if (0x80 <= read && read <= 0xBF)
continue;
else
break;
} else if (0xE0 <= read && read <= 0xEF) {
read = bis.read();
if (0x80 <= read && read <= 0xBF) {
read = bis.read();
if (0x80 <= read && read <= 0xBF) {
charset = "UTF-8";
break;
} else
break;
} else
break;
}
}
}
bis.close();
} catch (Exception e) {
e.printStackTrace();
}
return charset;
}
注意加权限:
1、下载gosnformat.jar(百度网盘下载地址:https://pan.baidu.com/s/1Ln5uhKP0R0VDoM3p1RTKHg)
2、从本地安装,选择刚才下载好的gosnformat.jar。
https://upload-images.jianshu.io/upload_images/5311514-8119016150d8e81d.png
将convertView中的View以ViewHolder的实例存入tag中复用,从而减少findViewById的调用,避免了资源浪费。(findViewById每次都会创建一个新的实例)
app gradle 中添加
configurations.all {
resolutionStrategy.eachDependency { DependencyResolveDetails details ->
def requested = details.requested
if (requested.group == 'com.android.support') {
if (!requested.name.startsWith("multidex")) {
details.useVersion '28.0.0'
}
}
}
}
RequestOptions requestOptions = new RequestOptions();
requestOptions.placeholder(R.drawable.bg_item);
requestOptions.error( R.drawable.bg_item );
requestOptions.diskCacheStrategy( DiskCacheStrategy.ALL );
Glide.with(context).load(mediaItem.getImage().getBig().get(0)).apply(requestOptions).into(viewHolder.iv_image_icon);