对于搜索页面“热门搜索网络请求”的总结
网络请求
历史记录以及热门搜索
整个过程主要涉及4个类,(SearchActivity仅仅起到显示作用所以不算),至于Adaptor中数据添加不放在此时总结之中。
热搜网络总体思路
Fragment(activity)中调用viewmodel声明的livedata的获取方法,然后添加observer,在响应方法中进行adaptor的数据添加,(observer的响应方法会将livedata的数据作为参数传递)。在viewmodel中用addSource实现livedata之间的拼接和方法响应问题,然而在实际应用中的这个livedata不是原生的,是自定义livedata,在livedata的生命周期中进行一些操作,例如自动触发请求并进行数据处理。在SearchSuggestFragment中调用SearchSuggestViewmodel的getHistoryAndRank,返回的事一个LiveData,但是在这个方法中,为两个中转livedata创造了分别的获取方法和响应者processHistoryAndRank()来负责最终livedata的拼接,拼接之后Fragment中的observer才会响应并赋值给adapter。
类图 调用流程其实真正实现请求的在JceRequestLiveData中,fireRequest方法在onActive()时就会触发,至于请求的url、对请求回来的数据进行解析和包装还有成功和失败的响应方法都可以由针对不同需求的子类来实现。
下面是具体过程的代码。
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的数据,每一份都应该是独立的,千万不要随便复用对象,容易产生"不明原因"的奇怪现象
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;
}
// 到此,算是请求成功了。下面,需要再做数据转换,不清楚到底有没有数据
final ListData data = rsp.data;
final ArrayList<RowItem> ret = new ArrayList<>();
.....
}
JceRequestLiveData
⑤
@Override
protected void onActive()
{
super.onActive();
if (mLatestRequest == null && getValue() == null)
{
// 自动请求数据
fireRequest(false);
}
}
@Override
protected void onInactive()
{
super.onInactive();
if (mLatestRequest != null && !mForcingRequest)
{
//当LiveData感知的view的生命周期结束的时候取消请求
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就是从内存中获取搜索历史的LiveData,实际上这个单例是软引用的,
解释一下各种引用:
⑴强引用(StrongReference)
强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。
⑵软引用(SoftReference)
如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
⑶弱引用(WeakReference)
弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
⑷虚引用(PhantomReference)
“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中。
具体获取数据库中信息的代码为以下:(DBQueryRequest是封装的数据库查询类,keys是一个含有一个string类型标记值的数组)
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();
为什么这么写呢?
其实在类图和流程图中个各类的分工已经很明确,完全符合MVVM架构结合liveData的实现方式,
- View(activity、fragment)
负责了数据在数据出口输出时响应回调,进行数据刷新, - ViewModel
负责实例化各种存储数据的容器,即liveData,包括各个liveData之间的数据传值和包装逻辑 - Model用自定义LiveData代替
实现数据请求等操作
其实主要是想利用livedata的生命周期,从而实现数据的自动传递。