網(wǎng)絡請求
歷史記錄以及熱門搜索
整個過程主要涉及4個類,(SearchActivity僅僅起到顯示作用所以不算)猪杭,至于Adaptor中數(shù)據(jù)添加不放在此時總結(jié)之中。
熱搜網(wǎng)絡總體思路
Fragment(activity)中調(diào)用viewmodel聲明的livedata的獲取方法妥衣,然后添加observer皂吮,在響應方法中進行adaptor的數(shù)據(jù)添加,(observer的響應方法會將livedata的數(shù)據(jù)作為參數(shù)傳遞)税手。在viewmodel中用addSource實現(xiàn)livedata之間的拼接和方法響應問題涮较,然而在實際應用中的這個livedata不是原生的,是自定義livedata冈止,在livedata的生命周期中進行一些操作狂票,例如自動觸發(fā)請求并進行數(shù)據(jù)處理。在SearchSuggestFragment中調(diào)用SearchSuggestViewmodel的getHistoryAndRank,返回的事一個LiveData熙暴,但是在這個方法中闺属,為兩個中轉(zhuǎn)livedata創(chuàng)造了分別的獲取方法和響應者processHistoryAndRank()來負責最終livedata的拼接,拼接之后Fragment中的observer才會響應并賦值給adapter周霉。
其實真正實現(xiàn)請求的在JceRequestLiveData中掂器,fireRequest方法在onActive()時就會觸發(fā),至于請求的url俱箱、對請求回來的數(shù)據(jù)進行解析和包裝還有成功和失敗的響應方法都可以由針對不同需求的子類來實現(xiàn)国瓮。
下面是具體過程的代碼。
SearchSuggestionFragment
②
@NonNull
@Override
public ViewGroup onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)
{
final SearchSuggestionViewModel model = ViewModelProviders.of(this) // 生命周期跟隨Fragment
.get(SearchSuggestionViewModel.class);
model.setRequestingSource(
SearchSuggestionViewModel.getKeywordRequestLoading(activity),
SearchSuggestionViewModel.getKeywordInputEmpty(activity));
model.getSearchResult().observe(this, this::setSearchResult);
model.getHistoryAndRank(mOpenSearchFromFrameType, mOpenSearchFromId).observe(this, this::setHistoryAndRank);
SearchSuggestionViewModel.getSuggestion(activity, mOpenSearchFromFrameType, mOpenSearchFromId).observe(this, this::setSuggestion);
mSearchViewModel.getSearchError().observe(this, this::setSearchError);
return view;
}
11
@MainThread
private void setHistoryAndRank(@Nullable List<RowItem> list)
{
mHistoryAndRank = list;
final ViewSearchSuggestionBinding binding = mBinding;
if (binding != null)
{
final AsyncListVMAdapter<RowItem> historyAndRankAdapter = getHistoryAndRankAdapter();
historyAndRankAdapter.submitList(list);
final boolean hasFocus = binding.getRoot().hasFocus();
setSearchResult(mSearchResult);
if (hasFocus && !binding.getRoot().hasFocus())
{
binding.getRoot().requestFocus();
}
}
}
SearchSuggestionViewModel
③
@NonNull
@MainThread
public LiveData<List<RowItem>> getHistoryAndRank(String openSearchFromFrameType, String openSearchFromId)
{
if (mHistoryAndRankLiveData == null)
{
final MediatorLiveData<List<RowItem>> data = new MediatorLiveData<>();
data.addSource(LocalSearchHistoryManager.getInstance(), this::onLocalHistoryChanged);
data.addSource(new SearchRankLiveData(openSearchFromFrameType, openSearchFromId), this::onRankListChanged);
mHistoryAndRankLiveData = data;
processHistoryAndRank();
}
return mHistoryAndRankLiveData;
}
⑩
@MainThread
private void onRankListChanged(@Nullable List<RowItem> rankList)
{
mRankRowItems = rankList;
processHistoryAndRank();
}
@MainThread
private void processHistoryAndRank()
{
if (mHistoryAndRankRowItems != null && !mHistoryAndRankRowItems.isEmpty())
{
// 給到UI的數(shù)據(jù)狞谱,每一份都應該是獨立的乃摹,千萬不要隨便復用對象,容易產(chǎn)生"不明原因"的奇怪現(xiàn)象
mHistoryAndRankRowItems = new ArrayList<>(mHistoryAndRankRowItems.size());
}
else
{
mHistoryAndRankRowItems = new ArrayList<>();
}
if (mRankRowItems != null && !mRankRowItems.isEmpty())
{
if (getLocalHistoryPosition() == 0)
{
if (mLocalHistoryRowItems != null && !mLocalHistoryRowItems.isEmpty())
{
mHistoryAndRankRowItems.addAll(mLocalHistoryRowItems);
}
if (mRankRowItems != null && !mRankRowItems.isEmpty())
{
mHistoryAndRankRowItems.addAll(mRankRowItems);
}
}
else
{
if (mRankRowItems != null && !mRankRowItems.isEmpty())
{
mHistoryAndRankRowItems.addAll(mRankRowItems);
}
if (mLocalHistoryRowItems != null && !mLocalHistoryRowItems.isEmpty())
{
mHistoryAndRankRowItems.addAll(mLocalHistoryRowItems);
}
}
}
if (mHistoryAndRankLiveData != null)
{
mHistoryAndRankLiveData.postValue(Collections.unmodifiableList(mHistoryAndRankRowItems));
}
else
{
TVCommonLog.w(TAG, "processHistoryAndRank: missing live data");
}
}
SearchRankLiveData
④
public SearchRankLiveData(@NonNull String openSearchFromFrameType, @NonNull String openSearchFromId)
{
mUrl = UrlConstants.CGIPrefix.URL_SEARCH_RANK
+ "&req_size=10"
+ "&raw=1" + "&" + TenVideoGlobal.getCommonUrlSuffix();
}
⑦
@Override
protected String makeRequestUrl()
{
return mUrl;
}
⑧
@Override
protected List<RowItem> parseJce(byte[] bytes) throws JceDecodeException
{
VideoPageRspV2 rsp = null;
try
{
rsp = new JceCommonConvertor<>(VideoPageRspV2.class).convertBytes2JceStruct(bytes);
}
catch (BufferUnderflowException e)
{
TVCommonLog.w(TAG, "parseJce: BufferUnderflowException");
return null;
}
if (rsp == null)
{
TVCommonLog.w(TAG, "parseJce: fail to parse jce");
return null;
}
if (rsp.result != null && rsp.result.ret != RetCode._SUCCESS)
{
TVCommonLog.w(TAG, "parseJce: ret = [" + rsp.result.ret + "], msg = [" + rsp.result.msg + "]");
return null;
}
// 到此跟衅,算是請求成功了孵睬。下面,需要再做數(shù)據(jù)轉(zhuǎn)換伶跷,不清楚到底有沒有數(shù)據(jù)
final ListData data = rsp.data;
final ArrayList<RowItem> ret = new ArrayList<>();
.....
}
JceRequestLiveData
⑤
@Override
protected void onActive()
{
super.onActive();
if (mLatestRequest == null && getValue() == null)
{
// 自動請求數(shù)據(jù)
fireRequest(false);
}
}
@Override
protected void onInactive()
{
super.onInactive();
if (mLatestRequest != null && !mForcingRequest)
{
//當LiveData感知的view的生命周期結(jié)束的時候取消請求
cancelRequest();
}
}
⑥
private void fireRequest(boolean force)
{
mForcingRequest = force;
mLatestRequest = new BaseJceRequest<T>()
{
@Override
protected String makeRequestUrl()
{
return JceRequestLiveData.this.makeRequestUrl();
}
@Override
public String getRequstName()
{
return JceRequestLiveData.this.getRequestName();
}
@Override
public T parseJce(byte[] bytes) throws JceDecodeException
{
return JceRequestLiveData.this.parseJce(bytes);
}
};
if (getValue() == null)
{
mLatestRequest.setRequestMode(mRequestMode);
}
else
{
mLatestRequest.setRequestMode(Request.LoadMode.SERVER);
}
GlobalManager.getInstance().getAppEngine().get(mLatestRequest, mResponseHandler);
}
⑨
private final AppResponseHandler<T> mResponseHandler = new AppResponseHandler<T>()
{
@Override
public void onSuccess(T data, boolean fromCache)
{
final T legalData = JceRequestLiveData.this.onSuccess(data, fromCache);
if (legalData == data)
{
postValue(data);
}
}
@Override
public void onFailure(RespErrorData error)
{
JceRequestLiveData.this.onFailure(error);
}
};
protected abstract T parseJce(byte[] bytes) throws JceDecodeException;
搜索歷史記錄的本地獲取
在上述圖中的getHistoryAndRank中就addSourse兩個LiveData掰读,
data.addSource(LocalSearchHistoryManager.getInstance(), this::onLocalHistoryChanged);
data.addSource(new SearchRankLiveData(openSearchFromFrameType, openSearchFromId), this::onRankListChanged);
其中的SearchRankLiveData是用于獲取熱門搜索的LiveData,而LocalSearchHistoryManager就是從內(nèi)存中獲取搜索歷史的LiveData叭莫,實際上這個單例是軟引用的蹈集,
解釋一下各種引用:
⑴強引用(StrongReference)
強引用是使用最普遍的引用。如果一個對象具有強引用雇初,那垃圾回收器絕不會回收它拢肆。當內(nèi)存空間不足,Java虛擬機寧愿拋出OutOfMemoryError錯誤,使程序異常終止善榛,也不會靠隨意回收具有強引用的對象來解決內(nèi)存不足的問題辩蛋。
⑵軟引用(SoftReference)
如果一個對象只具有軟引用,則內(nèi)存空間足夠移盆,垃圾回收器就不會回收它悼院;如果內(nèi)存空間不足了,就會回收這些對象的內(nèi)存咒循。只要垃圾回收器沒有回收它据途,該對象就可以被程序使用。軟引用可用來實現(xiàn)內(nèi)存敏感的高速緩存叙甸。
軟引用可以和一個引用隊列(ReferenceQueue)聯(lián)合使用颖医,如果軟引用所引用的對象被垃圾回收器回收,Java虛擬機就會把這個軟引用加入到與之關聯(lián)的引用隊列中裆蒸。
⑶弱引用(WeakReference)
弱引用與軟引用的區(qū)別在于:只具有弱引用的對象擁有更短暫的生命周期熔萧。在垃圾回收器線程掃描它所管轄的內(nèi)存區(qū)域的過程中,一旦發(fā)現(xiàn)了只具有弱引用的對象僚祷,不管當前內(nèi)存空間足夠與否佛致,都會回收它的內(nèi)存。不過辙谜,由于垃圾回收器是一個優(yōu)先級很低的線程俺榆,因此不一定會很快發(fā)現(xiàn)那些只具有弱引用的對象。
弱引用可以和一個引用隊列(ReferenceQueue)聯(lián)合使用装哆,如果弱引用所引用的對象被垃圾回收罐脊,Java虛擬機就會把這個弱引用加入到與之關聯(lián)的引用隊列中。
⑷虛引用(PhantomReference)
“虛引用”顧名思義蜕琴,就是形同虛設萍桌,與其他幾種引用都不同,虛引用并不會決定對象的生命周期奸绷。如果一個對象僅持有虛引用梗夸,那么它就和沒有任何引用一樣,在任何時候都可能被垃圾回收器回收号醉。
虛引用主要用來跟蹤對象被垃圾回收器回收的活動。虛引用與軟引用和弱引用的一個區(qū)別在于:虛引用必須和引用隊列 (ReferenceQueue)聯(lián)合使用辛块。當垃圾回收器準備回收一個對象時畔派,如果發(fā)現(xiàn)它還有虛引用,就會在回收對象的內(nèi)存之前润绵,把這個虛引用加入到與之 關聯(lián)的引用隊列中线椰。
具體獲取數(shù)據(jù)庫中信息的代碼為以下:(DBQueryRequest是封裝的數(shù)據(jù)庫查詢類,keys是一個含有一個string類型標記值的數(shù)組)
DBQueryRequest<DataPair> request = new DBQueryRequest<DataPair>();
request.setTableName(LocalCacheConstract.LocalCaches.TABLE_NAME);
request.setSelection(DatabaseUtils.getSelection(LocalCacheConstract.LocalCaches.ID, keys));
ArrayList<DataPair> pairs = request.sendRequestSync();
為什么這么寫呢尘盼?
其實在類圖和流程圖中個各類的分工已經(jīng)很明確憨愉,完全符合MVVM架構(gòu)結(jié)合liveData的實現(xiàn)方式烦绳,
- View(activity、fragment)
負責了數(shù)據(jù)在數(shù)據(jù)出口輸出時響應回調(diào)配紫,進行數(shù)據(jù)刷新径密, - ViewModel
負責實例化各種存儲數(shù)據(jù)的容器,即liveData,包括各個liveData之間的數(shù)據(jù)傳值和包裝邏輯 - Model用自定義LiveData代替
實現(xiàn)數(shù)據(jù)請求等操作
其實主要是想利用livedata的生命周期躺孝,從而實現(xiàn)數(shù)據(jù)的自動傳遞享扔。