bilibili項目學習

項目地址

https://github.com/HotBitmapGG/bilibili-android-client

已經存在的問題: 視頻詳情的接口都掛掉了, 新版bilibili的視頻詳情接口又加密了, 估計只能等哪位大神破解了

項目的整體框架圖


Paste_Image.png

項目中出現的一些知識點

1. lambda表達式

可以參考以下2篇文章

http://blog.csdn.net/future234/article/details/51919545
http://zh.lucida.me/blog/java-8-lambdas-insideout-language-features/

1. (int x, int y) -> x + y
2. () -> 42
3. (String s) -> { System.out.println(s); }

第一個 lambda 表達式接收 x 和 y 這兩個整形參數并返回它們的和;
第二個 lambda 表達式不接收參數,返回整數42;
第三個 lambda表達式接收一個字符串并把它打印到控制臺,不返回值。

以下程序是一種典型的寫法:

FileFilter java = (File f) -> f.getName().endsWith("*.java");
String user = doPrivileged( () -> System.getProperty("user.name") );
new Thread(() -> {
  connectToService();
  sendNotification();
}).start();

下面看看使用lambda表達式是如何簡化代碼的。
這是原始代碼:

List<Person> people = ...
Collections.sort(people, new Comparator<Person>() {
  public int compare(Person x, Person y) {
    return x.getLastName().compareTo(y.getLastName());
  }
})

冗余代碼實在太多了!
有了lambda表達式,我們可以去掉冗余的匿名類:

Collections.sort(people, (Person x, Person y) -> x.getLastName().compareTo(y.getLastName()));

再來看另一個例子:

Button clickButton = 初始化 button;
clickButton.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View v) {
        System.out.println("你點擊了按鈕");
    }
});

Lambda表達式的寫法:

Button clickButton = 初始化button;
clickButton.setOnClickListener((View v)-> System.out.println("你點擊了按鈕");

2. RxLifecycle

項目地址

https://github.com/trello/RxLifecycle

該項目是為了防止RxJava中subscription導致內存泄漏而誕生的,核心思想是通過監聽Activity、Fragment的生命周期,來自動斷開subscription以防止內存泄漏。

3. java雙冒號是什么操作符?

https://www.zhihu.com/question/28565691

4. Rxjava相關文章

http://www.lxweimin.com/p/6fd8640046f1

比如被觀察者產生的事件中只有圖片文件路徑,但是在觀察者這里只想要bitmap,那么就需要類型變換。

  Observable.just(getFilePath()
            //使用map操作來完成類型轉換
            .map(new Func1<String, Bitmap>() {
              @Override
              public Bitmap call(String s) {
                //顯然自定義的createBitmapFromPath(s)方法,是一個極其耗時的操作
                  return createBitmapFromPath(s);
              }
          })
            .subscribe(
                 //創建觀察者,作為事件傳遞的終點處理事件    
                  new Subscriber<Bitmap>() {
                        @Override
                        public void onCompleted() {
                            Log.d("DDDDDD","結束觀察...\n");
                        }

                        @Override
                        public void onError(Throwable e) {
                            //出現錯誤會調用這個方法
                        }
                        @Override
                        public void onNext(Bitmap s) {
                            //處理事件
                            showBitmap(s)
                        }
                    );

實際上在使用map操作時,new Func1<String,Bitmap>()就對應了類型的轉換方向,String是原類型,Bitmap是轉換后的類型。在call()方法中,輸入的是原類型,返回轉換后的類型
你認真看完上面的代碼就會覺得,何必在過程中變換類型呢?我直接在事件傳遞的終點,在觀察者中變換就行咯。老實說,你這個想法沒毛病,但實際上,上面寫的代碼是不合理的。
我在代碼中也提到,讀取文件,創建bitmap可能是一個耗時操作,那么就應該在子線程中執行,主線程應該僅僅做展示。那么線程切換一般就會是比較復雜的事情了。但是在Rxjava中,是非常方便的,如下代碼所示:

 Observable.just(getFilePath()
           //指定了被觀察者執行的線程環境為newThread
          .subscribeOn(Schedulers.newThread())
          //將接下來執行的線程環境指定為io線程
          .observeOn(Schedulers.io())
            //使用map操作來完成類型轉換
            .map(new Func1<String, Bitmap>() {
              @Override
              public Bitmap call(String s) {
                //顯然自定義的createBitmapFromPath(s)方法,是一個極其耗時的操作
                  return createBitmapFromPath(s);
              }
          })
            //將后面執行的線程環境切換為主線程
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(
                 //創建觀察者,作為事件傳遞的終點處理事件    
                  new Subscriber<Bitmap>() {
                        @Override
                        public void onCompleted() {
                            Log.d("DDDDDD","結束觀察...\n");
                        }

                        @Override
                        public void onError(Throwable e) {
                            //出現錯誤會調用這個方法
                        }
                        @Override
                        public void onNext(Bitmap s) {
                            //處理事件
                            showBitmap(s)
                        }
                    );

由上面的代碼可以看到,使用操作符將事件處理逐步分解,通過線程調度為每一步設置不同的線程環境,完全解決了你線程切換的煩惱。可以說線程調度和操作符,才真正展現了RxJava無與倫比的魅力。

再看一個例子:

//創建被觀察者,獲取所有班級
 Observable.from(getSchoolClasses())
                .flatMap(new Func1<SingleClass, Observable<Student>>() {
                    @Override
                    public Observable<Student> call(SingleClass singleClass) {
                        //將每個班級的所有學生作為一列表包裝成一列Observable<Student>,將學生一個一個傳遞出去
                        return Observable.from(singleClass.getStudents());
                    }
                })
                .subscribe(
                //創建觀察者,作為事件傳遞的終點處理事件    
                  new Subscriber<Student>() {
                        @Override
                        public void onCompleted() {
                            Log.d("DDDDDD","結束觀察...\n");
                        }

                        @Override
                        public void onError(Throwable e) {
                            //出現錯誤會調用這個方法
                        }
                        @Override
                        public void onNext(Student student) {
                            //接受到每個學生類
                            Log.d("DDDDDD",student.getName())
                        }
                    );

subscribeOn()它指示Observable在一個指定的調度器上創建(只作用于被觀察者創建階段)。只能指定一次,如果指定多次則以第一次為準。
observeOn()指定在事件傳遞(加工變換)和最終被處理(觀察者)的發生在哪一個調度器。可指定多次,每次指定完都在下一步生效。

在bilibili項目中,是使用Retrofit+RxJava來進行網絡訪問,以下是一個典型的代碼片段:

RetrofitHelper.getBiliAppAPI().getRecommendedBannerInfo().compose(bindToLifecycle())
                .map(RecommendBannerInfo::getData)
                // RecommendBannerInfo::getData獲取數據的結果就是
                // List<RecommendBannerInfo.DataBean>
                .flatMap(new Func1<List<RecommendBannerInfo.DataBean>, Observable<RecommendInfo>>() {

            @Override
            public Observable<RecommendInfo> call(List<RecommendBannerInfo.DataBean> dataBeans) {
                recommendBanners.addAll(dataBeans);
                return RetrofitHelper.getBiliAppAPI().getRecommendedInfo();
            }
        })
        .compose(bindToLifecycle())// 這句應該不需要吧
        .map(RecommendInfo::getResult)
        /*.subscribeOn(Schedulers.io())*/
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(resultBeans -> {
            results.addAll(resultBeans);
            finishTask();
        }, throwable -> {
            initEmptyView();
        });

再看另外一個例子, 在VideoPlayerActivity中:

RetrofitHelper.getBiliGoAPI().getHDVideoUrl(cid, 4, ConstantUtil.VIDEO_TYPE_MP4)
                .compose(bindToLifecycle())
                .map(videoInfo -> Uri.parse(videoInfo.getDurl().get(0).getUrl()))
                .observeOn(AndroidSchedulers.mainThread())
                .flatMap(new Func1<Uri, Observable<BaseDanmakuParser>>() {
            @Override
            public Observable<BaseDanmakuParser> call(Uri uri) {
                mPlayerView.setVideoURI(uri);
                mPlayerView.setOnPreparedListener(mp -> {
                    mLoadingAnim.stop();
                    startText = startText + "【完成】\n視頻緩沖中...";
                    mPrepareText.setText(startText);
                    mVideoPrepareLayout.setVisibility(View.GONE);
                });
                String url = "http://comment.bilibili.com/" + cid + ".xml";
                return BiliDanmukuDownloadUtil.downloadXML(url);
            }
        }).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(baseDanmakuParser -> {
            mDanmakuView.prepare(baseDanmakuParser, danmakuContext);
            mDanmakuView.showFPS(false);
            mDanmakuView.enableDanmakuDrawingCache(false);
            mDanmakuView.setCallback(new DrawHandler.Callback() {
                @Override
                public void prepared() {
                    mDanmakuView.start();
                }

                @Override
                public void updateTimer(DanmakuTimer danmakuTimer) {
                }

                @Override
                public void danmakuShown(BaseDanmaku danmaku) {
                }

                @Override
                public void drawingFinished() {
                }
            });
            mPlayerView.start();
        }, throwable -> {
            startText = startText + "【失敗】\n視頻緩沖中...";
            mPrepareText.setText(startText);
            startText = startText + "【失敗】\n" + throwable.getMessage();
            mPrepareText.setText(startText);
        });

項目使用的開源庫

  • Glide
  • jsoup
  • OkHttp
  • retrofit2
  • ijkplayer

rx家族

https://github.com/MiguelCatalan/MaterialSearchView

  • leakcanary
    這個沒的說, 內存泄漏檢測
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,316評論 25 708
  • 我從去年開始使用 RxJava ,到現在一年多了。今年加入了 Flipboard 后,看到 Flipboard 的...
    Jason_andy閱讀 5,570評論 7 62
  • Github:https://github.com/ReactiveX/RxJavahttps://github....
    才兄說閱讀 1,662評論 2 10
  • 這次的女主角和之前大多數女人有點不一樣,我感覺她和她老公的感情基礎不是很穩,男追女,女主角在該結婚的年齡接受了男主...
    西式風閱讀 377評論 0 0
  • 大學是個做夢的季節,背起行囊,遠離家鄉,城市霓虹的璀璨,夜晚川流的人群,和路燈下孤獨的自己。唯一不變的是頭頂的...
    墨子涵閱讀 1,345評論 0 2