尚硅谷实战项目---手机影音APP

项目总结

1.Activity 横竖屏切换的生命周期

默认情况:(会导致重新播放)
onPause—>onStop—>onDestroy—>onCreate—>onStart—>onResume

屏幕横竖切换导致生命周期重新执行

解决:

        

2.视频的SeekBar更新

视频的总时长和SeekBar的setMax(总时长)
实例化Handler,每秒得到当前视频播放的进度,SeekBar,setProgress(当前进度);

3.SeekBar的拖拽

视频的总时长和SeekBar的setMax(总时长)
设置SeekBar状态变化的监听

4.注册广播有俩种方式:动态注册和静态注册

静态注册:在功能清淡文件注册,只要软件安装在手机上,就算软件不启动,也能收到对应的广播;
…___________…
但是有局限:比如电量,锁屏开屏等监听就不能再静态中注册
(频繁变化的,底层系统不允许)

动态注册:只有注册的代码被执行后,才能收到对应的广播

5.释放资源时

 @Override
    protected void onDestroy() {

        //取消广播
        if (receiver!=null){
            unregisterReceiver( receiver );
            receiver=null;
        }
        super.onDestroy();

    }

要在super()之前
因为要先释放子类,后释放父类

6.swich 和 if

swich可以的if都可以,反之不可以
因为if可以取范围

7.传递列表数据到Activity中,列表中的数据有可能是对象,或者是String,int;如果要传列表数据是对象 需要 序列化

报错提示:Parcel: unable to marshal…
自定义的对象可以通过bundle传递,前提条件就是自定义的类实现Parcelable,Serializable俩个接口之一

在Activity之间传递对象有俩个选择

Parcelable和Serializable的选择
Parcelable是android自带的,Serializable兼容性更好,也更简便
1.在使用内存的时候,Parcelable类比Serializable性能高,所以推荐使用Parcelable类
Serializable在序列化的时候会产生大量的临时变量,从而引起频繁的GC
Parcelable不能使用在要将数据存储在磁盘上的情况

对象为什么要序列化

1.永久性保存对象,保存对象的字节序列到本地文件
2.通过序列化对象在网络中传递对象
3.通过序列化对象在进程中传递对象

8.手势识别器

1.定义:private GestureDetector detector;
2.实例化(重写方法:双击,单击,长按)
3.onTouchEvent():方法中把事件传递给手势识别器(重要)

解决BUG:拖动进度条,音量条的时候,超过4秒,控制面板隐藏

9.调节音量

1.实例化AudioManger :当前音量,最大音量
2.SeekBar.setMax(最大音量)
SeeKBar.setProGress(当前的音量)
3.设置SeekBar状态变化,

10.在屏幕滑动改变声音

从上往下
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的时候重新发消息

11.让其它软件调用自己写的播放器

方法:参照系统源代码

 			
                

                
                

                
            
            
                
                

                
                
            
            
                

                
                

                
                
                
                
                
            

12.点击调起所有播放器

		Intent intent=new Intent(  );

        //重要!不加这个不行
        intent.setAction(Intent.ACTION_VIEW);

        intent.setDataAndType( 		  Uri.parse("http://192.168.137.1:8080/zjq.mp4"),"video/*");
        startActivity( intent );

13.手机连接电脑播放的tomcat的配置

1.用手机连上pc的wifi
2.开启电脑tomcat,并且把一个视频放入
3.查看ip地址(cmd-ipconfig),找Ipv4

14.设置监听播放网络视频卡setOnInfoListener

方法一(适用于4.2之后)

前提:播放视频的时候,网络比较慢才会出现
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 -------------卡

一秒钟刷新一次进度,下一次比这次刷新出的进度少了,就证明卡顿

能得到当前的播放进度的话,建议用校验播放进度判断是否监听卡这种方式(直播不能得到当前播放进度)
如果不能得到当前播放进度,可以用系统的监听卡

15.Vitamio的集成

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.关闭系统播放器

16.让Activity在桌面显示图标, 并且在点击图标时进入软件


17.xUtils

.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() {
            @Override
            public void onSuccess(Object result) {

                LogUtil.e( "联网成功=="+result );

                //解析数据 主线程 很快
                processData(result);
            }

            @Override
            public void onError(Throwable ex, boolean isOnCallback) {
                LogUtil.e( "联网失败=="+ex.getMessage() );

            }

            @Override
            public void onCancelled(CancelledException cex) {

                LogUtil.e( "onCancelled=="+cex.getMessage() );

            }

            @Override
            public void onFinished() {
                LogUtil.e( "联网完成==" );

            }
        } );
 
  

4.用xUtils显示图片
x.image().bind( iv_icon,mediaItem.getImageUrl() );

18.关于 Android 9 Http访问

在Android P 使用HttpUrlConnection进行http请求会出现以下异常
java.net.UnknownServiceException: CLEARTEXT communication ** not permitted by network security policy
在Android P系统的设备上,如果应用使用的是非加密的明文流量的http网络请求,则会导致该应用无法进行网络请求,https则不会受影响



> 解决方法:

在application标签里面加个usesCleartextTraffic然后新增一条 uses-library标签
usesCleartextTraffic 是允许明文请求

   
        
        
   



19.保存数据,用作缓存

 public void putString(Context context,String key,String values){
        SharedPreferences sharedPreferences=context.getSharedPreferences( "VideoCache",Context.MODE_PRIVATE );
        sharedPreferences.edit().putString( key,values ).commit();
    }

20.俩种直播

1.直播网络电视
从服务器一边下载一边播放

2.当前环境播出去
手机生成视频,上传到服务器,由服务器分发到不同的手机上

21.视频播放器的俩种做法

第一种方式:直接使用VideoView(建议)
第二种方式:在Activity中封装MediaPlayer和surfaceView

22.Activity和服务交互

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.服务类

23.让歌曲在Service中播放

1.创建MusicPlayerSerivce 继承 Service
把常用的方法先写出来,但不实现
2.参照服务的方法,写对应的Aidl文件
3.把服务在功能清单文件中注册
4.把AIDL文件生成的类在服务中绑定
5.绑定方式启动Service
6.在MusicPlayerSerivce 中得到音乐列表
7.当绑定成功的时候,播放音乐
8.在服务中openAudio的功能
9.实现音乐的播放和暂停

24.让垃圾回收站快速回收

关闭后,在设置为NULL,有助于垃圾回收站快速回收
 			unregisterReceiver( receiver );
            receiver=null;

25.沉浸式

	if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            //透明状态栏
            getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
            //透明导航栏
            getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); }

26.移除handler消息,否则影响性能

 	 @Override
   	 protected void onDestroy() {

        handler.removeCallbacksAndMessages( null );

    }

27.在状态栏播放歌曲的时候,点击进入

		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();

28.Android 8.0 适配通知

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 );*/

29.EventBus

  • 老版

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反射执行

30.歌词同步

1.创建歌词显示控件 showLyric

  • 继承TextView

2.创建歌词列表

  • 假设的歌词
  • 歌词列表

3.绘制歌词

  • 绘制前面部分
  • 绘制当前部分
  • 绘制后面部分

4.根据当前播放进度,发消息实时更新
…歌词绘制分位置,

  • handler 刷新

5.解析歌词

  • 解析每一句
  • 加入到列表中
  • 排序
  • 计算每句歌词的高亮时间
  • 实现歌词同步
  • 根据不同的歌曲,加载不同的歌词文件
					1.用死循环,发消息,注意先移除,否则会使系统变慢
						handler.removeMessages( SHOW_LYRIC );
                   		handler.sendEmptyMessage( SHOW_LYRIC );
                   	2.重新绘制
   					   	invalidate();//在主线程中调用
     				    postInvalidate();  //在子线程中
       

31.Collections.sort(list,new Comparator<>)用法和要点:

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;
    }
});

32.让歌词缓缓移动

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;
    }

33.让音乐跳动

注意加权限:



34.gosnformat 工具使用

1、下载gosnformat.jar(百度网盘下载地址:https://pan.baidu.com/s/1Ln5uhKP0R0VDoM3p1RTKHg)

2、从本地安装,选择刚才下载好的gosnformat.jar。
https://upload-images.jianshu.io/upload_images/5311514-8119016150d8e81d.png

35. adapter.notifyDataSetChanged();adapter 数据刷新

36.ViewHolder的意义

将convertView中的View以ViewHolder的实例存入tag中复用,从而减少findViewById的调用,避免了资源浪费。(findViewById每次都会创建一个新的实例)

37.版本问题 强制解决

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'
                }
            }
        }
    }

38.Glide找不到placeholder,error等方法

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);
                    

39.知识总回顾

  1. 启动页面延迟俩秒进入主页
  2. 快速进入主页面,会实例化多个页面,设置单例模式,从源头控制只能启动一次(boolean值取反)
  3. 实现主页面的布局
  4. 自定义RadioButton
  5. 标题栏
  6. TitleBar自定义类继承线性布局,把标题栏布局包在里面
  7. 写一个公共的页面BasePager
  8. 实现各个页面(并初始化页面)
  9. 设置Radiogroup的监听切换不同的页面
  10. 软件的架构分析
  11. 读取本地的视频,在6.0以后需要动态获取权限
  12. 简单的播放器
  13. 把手机中所有的播放器调起(加隐式意图)
  14. MediaPlayer,和底层交互(c/c++)通过JNI
  15. VideoView,封装了MediaPlayer,继承SurfaceView(在子线程渲染)
  16. 视频的播放完成,播放出错,准备好的监听
  17. 自定义VideoView实现视频全屏
  18. 手势识别器,双击,单击。长按,5秒自动隐藏控制面板
  19. 屏幕屏蔽横竖屏切换生命周期
  20. Activity的生命周期
  21. Activity的Service交互(用AIDL)
  22. EvenBus
  23. 声音的调节
  24. 亮度的调节
  25. 滑动屏幕去改变声音和亮度
  26. 俩种实现播放器的方式
  27. 视频进度的更新
  28. 万能播放器和系统播放器的切换(取消切换动画)
  29. 万能解码框架简介(ffmpeg(开源),VLC,Vitmaio)
  30. Vitmaio的集成
  31. 网络视频的缓冲效果
  32. 监听卡,认为监听卡,拖动卡
  33. 显示网速
  34. 加载视频等待效果
  35. 读取播放本地音乐,
  36. 播放音乐—Service
  37. Activity和服务交互–AIDL
  38. 帧动画
  39. 播放歌曲时候上一首,下一首,
  40. 设置播放模式
  41. 通过广播获取当前播放歌曲的和名称(列表点击)
  42. 进度更新
  43. **歌词显示控件ShowLyricView继承TextView(**容易出错用listVewi)
  44. 设置假歌词
  45. 绘制歌词
  46. 解析歌词,一行一行读取,排序,计算每句高亮显示的时间
  47. 解决乱码问题(根据文件判断编码格式)
  48. 网络视频–-xUtils3请求网络
  49. 使用系统API解析Json数据
  50. xLIstview:下拉刷新
  51. 分类型的ListView
  52. 使用节操播放器在listView中播放视频
  53. 使用Glide加载图片(尤其GIF)
  54. 使用科大讯飞语音输入
  55. Gson解析和GsonFormat生成Bean对象
  56. 软件退出

你可能感兴趣的:(项目实训)