1、关于总体上的分析,可以看看这个链接
http://www.lightskystreet.com/2015/02/10/philm_mvp/
2、这里对philm进行代码层面上的分析
在androidmanifest.xml中找到主activity为MainActivity,我们就从这里开始分析一些主要的逻辑部分
MainActivity继承于BasePhilmActivity,
先看BasePhilmActivity:
在onCreate中
有初始化MainController和Display
- /** 获取总的controller */
- mMainController = PhilmApplication.from(this).getMainController();
- /** 创建Display 对象 */
- mDisplay = new AndroidDisplay(this, mDrawerLayout);
然后在onResume
中为MainController设置了Display(mMainController.attachDisplay(mDisplay);)
以及对MainController进行初始化的操作(mMainController.init();)
当然在onPause
中肯定会有相关的解除资源的操作了
总的来看,BasePhilmActivity完成的工作主要是初始化Controller和Display
然后看MainActivity:
根据MVP的思想,activity/fragment是一个view(UI), UI的逻辑由Controller通过持有UI的UC来处理(UC用来响应UI的动作), 这里MainActivity对应的UI为MainController.MainUi
:
- public interface MainUi extends MainControllerUi {
- void showLoginPrompt();
- }
- public interface MainControllerUi extends BaseUiController.Ui<MainControllerUiCallbacks> {
- }
- public interface Ui<UC> {
- void setCallbacks(UC callbacks);
- boolean isModal();
- }
所以这里的MainUi
总共有三个方法:
void showLoginPrompt();void setCallbacks(UC callbacks); boolean isModal();
MainUi对应的UC为MainControllerUiCallbacks
在onResume中把当前的UIattach到Controller中
- protected void onResume() {
- super.onResume();
- getMainController().attachUi(this);
- }
attach
的实现在BaseUiController
中
- public synchronized final void attachUi(U ui) {
- /**
- * 如果ui==null,那么就报错:ui cannot be null
- */
- Preconditions.checkArgument(ui != null, "ui cannot be null");
- /**
- * 如果mUis.contains(ui),那么就报错:UI is already attached
- */
- Preconditions.checkState(!mUis.contains(ui), "UI is already attached");
- /**
- * 新加UI
- */
- mUis.add(ui);
- /**
- * 给UI添加UC
- */
- ui.setCallbacks(createUiCallbacks(ui));
- if (isInited()) {/** Controller初始化成功了 */
- if (!ui.isModal() && !(ui instanceof SubUi)) {
- /** 给actionbar设置标题 */
- updateDisplayTitle(getUiTitle(ui));
- }
- onUiAttached(ui);
- /** UI展示、刷新 */
- populateUi(ui);
- }
- }
这里UI通过ui.setCallbacks(createUiCallbacks(ui));
绑定了UC
对于MainActivity
来说,它对应的Controller为MainController
,所以我们可以在MainController
中找到createUiCallbacks(ui)
的实现:
- @Override
- protected MainControllerUiCallbacks createUiCallbacks(final MainControllerUi ui) {
- return new MainControllerUiCallbacks() {
- @Override
- public void onSideMenuItemSelected(SideMenuItem item) {
- Display display = getDisplay();
- if (display != null) {
- showUiItem(display, item);
- display.closeDrawerLayout();
- }
- }
- @Override
- public void addAccountRequested() {
- Display display = getDisplay();
- if (display != null) {
- display.startAddAccountActivity();
- display.closeDrawerLayout();
- }
- }
- @Override
- public void showMovieCheckin() {
- Display display = getDisplay();
- WatchingMovie checkin = mState.getWatchingMovie();
- if (display != null && checkin != null) {
- display.closeDrawerLayout();
- display.startMovieDetailActivity(checkin.movie.getImdbId(), null);
- }
- }
- @Override
- public void setShownLoginPrompt() {
- mPreferences.setShownTraktLoginPrompt();
- }
- };
- }
可以看到,UC正是MainControllerUiCallbacks
MainActivity
的布局是一个DrawerLayout
,有MenuFragment(SideMenuFragment
) 和显示内容Fragment,首次展示的Fragment是通过BasePhilmActivity
中handleIntent(Intent intent, Display display)
来确定的
- @Override
- protected void handleIntent(Intent intent, Display display) {
- if (Intent.ACTION_MAIN.equals(intent.getAction())) {
- if (!display.hasMainFragment()) {
- getMainController().setSelectedSideMenuItem(MainController.SideMenuItem.DISCOVER);
- display.showDiscover();
- }
- }
- }
在Philm项目中,通过查看AndroidDisplay可以知道,整个项目的activity跳转、fragment切换、DrawerLayout的 闭和开、actionbar的控制都是通过这个Display来实现的。
通过查看display.showDiscover()
可以发现,Mainactivity中的那个fragment对应的类 为DiscoverTabFragment
,这个fragment中也没有什么东西,也就是一个viewpager管理着几个fragment, 这里不再做具体的分析了(主要是上次分析的笔记丢失了......)
PopularMoviesFragment
通过分析,DiscoverTabFragment
中的对于流行的电影对应的fragment为PopularMoviesFragment
. 我们就分析一下数据的交互问题。 PopularMoviesFragment->MovieGridFragment->BasePhilmMovieListFragment->BaseMovieControllerListFragment, 这是这个fragment对应的继承关系,在BaseMovieControllerListFragment
的onResume
中进行了UI的attach, 这个UI 对应的Controller为MovieController
,在MovieController中先是执行BaseUiController中的 attachUi
,然后紧接着执行onUiAttached
,这个方法的实现在MovieController
中
- @Override
- protected void onUiAttached(final MovieUi ui) {
- final MovieQueryType queryType = ui.getMovieQueryType();
- if (queryType.requireLogin() && !isLoggedIn()) {
- return;
- }
- String title = null;
- String subtitle = null;
- final int callingId = getId(ui);
- switch (queryType) {
- case TRENDING:
- fetchTrendingIfNeeded(callingId);
- break;
- case POPULAR:
- fetchPopularIfNeeded(callingId);
- break;
- .....
- }
- final Display display = getDisplay();
- if (display != null) {
- if (!ui.isModal()) {
- display.showUpNavigation(queryType != null && queryType.showUpNavigation());
- display.setColorScheme(getColorSchemeForUi(ui));
- }
- display.setActionBarSubtitle(subtitle);
- }
- }
可以看到,获取数据的方法为fetchPopularIfNeeded(callingId);
- private void fetchPopularIfNeeded(final int callingId) {
- MoviesState.MoviePaginatedResult popular = mMoviesState.getPopular();
- if (popular == null || PhilmCollections.isEmpty(popular.items)) {
- fetchPopular(callingId, TMDB_FIRST_PAGE);
- }
- }
- private void fetchPopular(final int callingId, final int page) {
- executeTask(new FetchTmdbPopularRunnable(callingId, page));
- }
通过FetchTmdbPopularRunnable
来获取数据了,FetchTmdbPopularRunnable
是类似于asynctask的东东, 有一些基本的生命周期函数了。
- public abstract class NetworkCallRunnable<R> {
- public void onPreTraktCall() {}
- public abstract R doBackgroundCall() throws RetrofitError;
- public abstract void onSuccess(R result);
- public abstract void onError(RetrofitError re);
- public void onFinished() {}
- }
这里主要分析doBackgroundCall
和onSuccess
.
- @Override
- public MovieResultsPage doBackgroundCall() throws RetrofitError {
- return getTmdbClient().moviesService().popular(
- getPage(),
- getCountryProvider().getTwoLetterLanguageCode());
- }
- @Override
- public final void onSuccess(TR result) {
- if (result != null) {
- R paginatedResult = getResultFromState();
- if (paginatedResult == null) {
- paginatedResult = createPaginatedResult();
- paginatedResult.items = new ArrayList<>();
- }
- updatePaginatedResult(paginatedResult, result);
- updateState(paginatedResult);
- }
- }
- @Override
- protected void updatePaginatedResult(
- MoviesState.MoviePaginatedResult result,
- MovieResultsPage tmdbResult) {
- result.items.addAll(getTmdbMovieEntityMapper().mapAll(tmdbResult.results));
- result.page = tmdbResult.page;
- if (tmdbResult.total_pages != null) {
- result.totalPages = tmdbResult.total_pages;
- }
- }
- @Override
- protected MoviesState.MoviePaginatedResult createPaginatedResult() {
- return new MoviesState.MoviePaginatedResult();
- }
- @Override
- protected MoviesState.MoviePaginatedResult getResultFromState() {
- return mMoviesState.getPopular();
- }
- @Override
- protected void updateState(MoviesState.MoviePaginatedResult result) {
- mMoviesState.setPopular(result);
- }
主要的逻辑都在这里了。在doBackgroundCall
中通过一个接口获取数据,然后在onSuccess
中 对获取到的数据进行处理:通过updateState()
保存数据到MoviesState
中。
这里牵扯到该项目中的又一个角色,Status
.在Philm项目中,Status负责保存HTTP中获取的数据, 然后通过eventbus把获取的数据post给UI,注意,status仅限于保存数据和转发数据,没有获取数据等复杂操作。 在ApplicationState
中找到setPopular
:
- @Override
- public void setPopular(MoviePaginatedResult items) {
- mPopular = items;
- mEventBus.post(new PopularChangedEvent());
- }
通过eventbus来告知Controller,在MovieController
中找到了接收这个事件的方法:
- @Subscribe
- public void onPopularChanged(MoviesState.PopularChangedEvent event) {
- populateUiFromQueryType(MovieQueryType.POPULAR);
- }
- private final void populateUiFromQueryType(MovieQueryType queryType) {
- MovieUi ui = findUiFromQueryType(queryType);
- if (ui != null) {
- populateUi(ui);
- }
- }
- @Override
- protected void populateUi(final MovieUi ui) {
- if (!isLoggedIn() && ui.getMovieQueryType().requireLogin()) {
- ui.showError(NetworkError.UNAUTHORIZED_TRAKT);
- return;
- }
- if (mMoviesState.getTmdbConfiguration() == null) {
- mLogger.i(LOG_TAG, "TMDB Configuration not downloaded yet.");
- return;
- }
- if (Constants.DEBUG) {
- mLogger.d(LOG_TAG, "populateUi: " + ui.getClass().getSimpleName());
- }
- // Set the color scheme
- ui.setColorScheme(getColorSchemeForUi(ui));
- if (ui instanceof SearchMovieUi) {
- populateSearchMovieUi((SearchMovieUi) ui);
- } else if (ui instanceof MovieListUi) {
- //PopularMoviesFragment是一个MovieListUi
- populateMovieListUi((MovieListUi) ui);
- }
- ......
- }
- private void populateMovieListUi(MovieListUi ui) {
- final MovieQueryType queryType = ui.getMovieQueryType();
- Set<MovieFilter> filters = null;
- if (isLoggedIn()) {
- if (queryType.supportFiltering()) {
- ui.setFiltersVisibility(true);
- filters = mMoviesState.getFilters();
- ui.showActiveFilters(filters);
- }
- } else {
- ui.setFiltersVisibility(false);
- }
- List<PhilmMovie> items = null;
- List<MovieFilter> sections = queryType.getSections();
- List<MovieFilter> sectionProcessingOrder = queryType.getSectionsProcessingOrder();
- switch (queryType) {
- case TRENDING:
- items = mMoviesState.getTrending();
- break;
- case POPULAR:
- MoviesState.MoviePaginatedResult popular = mMoviesState.getPopular();
- if (popular != null) {
- items = popular.items;
- }
- break;
- // ......
- }
- if (!PhilmCollections.isEmpty(items)) {
- // Always filter movies (for adult)
- items = filterMovies(items, filters);
- }
- if (items == null) {
- ui.setItems(null);
- } else if (PhilmCollections.isEmpty(sections)) {
- ui.setItems(createListItemList(items));
- if (isLoggedIn()) {
- ui.allowedBatchOperations(MovieOperation.MARK_SEEN,
- MovieOperation.ADD_TO_COLLECTION, MovieOperation.ADD_TO_WATCHLIST);
- } else {
- ui.disableBatchOperations();
- }
- } else {
- ui.setItems(createSectionedListItemList(items, sections, sectionProcessingOrder));
- }
- }
最后,在MovieGridFragment
中对数据进行了显示
- @Override
- public void setItems(List<ListItem<PhilmMovie>> items) {
- mMovieGridAdapter.setItems(items);
- moveListViewToSavedPositions();
- }