項目地址
已經存在的問題: 視頻詳情的接口都掛掉了, 新版bilibili的視頻詳情接口又加密了, 估計只能等哪位大神破解了
項目的整體框架圖
項目中出現的一些知識點
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
項目地址
該項目是為了防止RxJava中subscription導致內存泄漏而誕生的,核心思想是通過監聽Activity、Fragment的生命周期,來自動斷開subscription以防止內存泄漏。
3. java雙冒號是什么操作符?
4. Rxjava相關文章
比如被觀察者產生的事件中只有圖片文件路徑,但是在觀察者這里只想要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家族
rxjava
rxandroid
rxbinding
rxbinding可以參考下面這篇文章:
一些RxBinding使用場景RxLifecycle
Lifecycle handling APIs for Android apps using RxJava
RxLifecyclestetho
stetho使用介紹FlycoTabLayout
一個Android TabLayout庫, 目前有3個TabLayout
FlycoTabLayoutTagFlowLayout: tag標簽的流式布局
https://github.com/hongyangAndroid/FlowLayoutglide-transformations
一個Android轉換庫,為Glide提供各種圖像轉換MagicaSakura
MagicaSakura 是 Android 多主題框架。MaterialSearchView
- leakcanary
這個沒的說, 內存泄漏檢測