項目地址
已經(jīng)存在的問題: 視頻詳情的接口都掛掉了, 新版bilibili的視頻詳情接口又加密了, 估計只能等哪位大神破解了
項目的整體框架圖

項目中出現(xiàn)的一些知識點
1. lambda表達(dá)式
可以參考以下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 表達(dá)式接收 x 和 y 這兩個整形參數(shù)并返回它們的和;
第二個 lambda 表達(dá)式不接收參數(shù),返回整數(shù)42;
第三個 lambda表達(dá)式接收一個字符串并把它打印到控制臺,不返回值。
以下程序是一種典型的寫法:
FileFilter java = (File f) -> f.getName().endsWith("*.java");
String user = doPrivileged( () -> System.getProperty("user.name") );
new Thread(() -> {
connectToService();
sendNotification();
}).start();
下面看看使用lambda表達(dá)式是如何簡化代碼的。
這是原始代碼:
List<Person> people = ...
Collections.sort(people, new Comparator<Person>() {
public int compare(Person x, Person y) {
return x.getLastName().compareTo(y.getLastName());
}
})
冗余代碼實在太多了!
有了lambda表達(dá)式,我們可以去掉冗余的匿名類:
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表達(dá)式的寫法:
Button clickButton = 初始化button;
clickButton.setOnClickListener((View v)-> System.out.println("你點擊了按鈕");
2. RxLifecycle
項目地址
該項目是為了防止RxJava中subscription導(dǎo)致內(nèi)存泄漏而誕生的,核心思想是通過監(jiān)聽Activity、Fragment的生命周期,來自動斷開subscription以防止內(nèi)存泄漏。
3. java雙冒號是什么操作符?
4. Rxjava相關(guān)文章
比如被觀察者產(chǎn)生的事件中只有圖片文件路徑,但是在觀察者這里只想要bitmap,那么就需要類型變換。
Observable.just(getFilePath()
//使用map操作來完成類型轉(zhuǎn)換
.map(new Func1<String, Bitmap>() {
@Override
public Bitmap call(String s) {
//顯然自定義的createBitmapFromPath(s)方法,是一個極其耗時的操作
return createBitmapFromPath(s);
}
})
.subscribe(
//創(chuàng)建觀察者,作為事件傳遞的終點處理事件
new Subscriber<Bitmap>() {
@Override
public void onCompleted() {
Log.d("DDDDDD","結(jié)束觀察...\n");
}
@Override
public void onError(Throwable e) {
//出現(xiàn)錯誤會調(diào)用這個方法
}
@Override
public void onNext(Bitmap s) {
//處理事件
showBitmap(s)
}
);
實際上在使用map操作時,new Func1<String,Bitmap>()就對應(yīng)了類型的轉(zhuǎn)換方向,String是原類型,Bitmap是轉(zhuǎn)換后的類型。在call()方法中,輸入的是原類型,返回轉(zhuǎn)換后的類型
你認(rèn)真看完上面的代碼就會覺得,何必在過程中變換類型呢?我直接在事件傳遞的終點,在觀察者中變換就行咯。老實說,你這個想法沒毛病,但實際上,上面寫的代碼是不合理的。
我在代碼中也提到,讀取文件,創(chuàng)建bitmap可能是一個耗時操作,那么就應(yīng)該在子線程中執(zhí)行,主線程應(yīng)該僅僅做展示。那么線程切換一般就會是比較復(fù)雜的事情了。但是在Rxjava中,是非常方便的,如下代碼所示:
Observable.just(getFilePath()
//指定了被觀察者執(zhí)行的線程環(huán)境為newThread
.subscribeOn(Schedulers.newThread())
//將接下來執(zhí)行的線程環(huán)境指定為io線程
.observeOn(Schedulers.io())
//使用map操作來完成類型轉(zhuǎn)換
.map(new Func1<String, Bitmap>() {
@Override
public Bitmap call(String s) {
//顯然自定義的createBitmapFromPath(s)方法,是一個極其耗時的操作
return createBitmapFromPath(s);
}
})
//將后面執(zhí)行的線程環(huán)境切換為主線程
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
//創(chuàng)建觀察者,作為事件傳遞的終點處理事件
new Subscriber<Bitmap>() {
@Override
public void onCompleted() {
Log.d("DDDDDD","結(jié)束觀察...\n");
}
@Override
public void onError(Throwable e) {
//出現(xiàn)錯誤會調(diào)用這個方法
}
@Override
public void onNext(Bitmap s) {
//處理事件
showBitmap(s)
}
);
由上面的代碼可以看到,使用操作符將事件處理逐步分解,通過線程調(diào)度為每一步設(shè)置不同的線程環(huán)境,完全解決了你線程切換的煩惱??梢哉f線程調(diào)度和操作符,才真正展現(xiàn)了RxJava無與倫比的魅力。
再看一個例子:
//創(chuàng)建被觀察者,獲取所有班級
Observable.from(getSchoolClasses())
.flatMap(new Func1<SingleClass, Observable<Student>>() {
@Override
public Observable<Student> call(SingleClass singleClass) {
//將每個班級的所有學(xué)生作為一列表包裝成一列Observable<Student>,將學(xué)生一個一個傳遞出去
return Observable.from(singleClass.getStudents());
}
})
.subscribe(
//創(chuàng)建觀察者,作為事件傳遞的終點處理事件
new Subscriber<Student>() {
@Override
public void onCompleted() {
Log.d("DDDDDD","結(jié)束觀察...\n");
}
@Override
public void onError(Throwable e) {
//出現(xiàn)錯誤會調(diào)用這個方法
}
@Override
public void onNext(Student student) {
//接受到每個學(xué)生類
Log.d("DDDDDD",student.getName())
}
);
subscribeOn()它指示Observable在一個指定的調(diào)度器上創(chuàng)建(只作用于被觀察者創(chuàng)建階段)。只能指定一次,如果指定多次則以第一次為準(zhǔn)。
observeOn()指定在事件傳遞(加工變換)和最終被處理(觀察者)的發(fā)生在哪一個調(diào)度器??芍付ǘ啻?,每次指定完都在下一步生效。
在bilibili項目中,是使用Retrofit+RxJava來進(jìn)行網(wǎng)絡(luò)訪問,以下是一個典型的代碼片段:
RetrofitHelper.getBiliAppAPI().getRecommendedBannerInfo().compose(bindToLifecycle())
.map(RecommendBannerInfo::getData)
// RecommendBannerInfo::getData獲取數(shù)據(jù)的結(jié)果就是
// 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())// 這句應(yīng)該不需要吧
.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標(biāo)簽的流式布局
https://github.com/hongyangAndroid/FlowLayoutglide-transformations
一個Android轉(zhuǎn)換庫,為Glide提供各種圖像轉(zhuǎn)換MagicaSakura
MagicaSakura 是 Android 多主題框架。MaterialSearchView
- leakcanary
這個沒的說, 內(nèi)存泄漏檢測