美文网首页程序员
OSCHINA客户端完全剖析(二)

OSCHINA客户端完全剖析(二)

作者: 郭非文 | 来源:发表于2016-02-21 14:30 被阅读473次

接上期,着重分析主干逻辑,先看第一个综合页面。

综合

五个Tab页定义可以看MainTab ,虽然enum占用存储较多,但可读性的提升也同样显而易见,以下是代码片段:

public enum MainTab {

    NEWS(0, R.string.main_tab_name_news, R.drawable.tab_icon_new,
            NewsViewPagerFragment.class),

    TWEET(1, R.string.main_tab_name_tweet, R.drawable.tab_icon_tweet,
            TweetsViewPagerFragment.class),

    QUICK(2, R.string.main_tab_name_quick, R.drawable.tab_icon_new,
            null),

    EXPLORE(3, R.string.main_tab_name_explore, R.drawable.tab_icon_explore,
            ExploreFragment.class),

    ME(4, R.string.main_tab_name_my, R.drawable.tab_icon_me,
            MyInformationFragment.class);

综合页当然就是NewsViewPagerFragment了,我们自顶向下逐步分析。

BaseFragment extends Fragment

预定义了多种状态,有可能会在子类中用到:

public static final int STATE_NONE = 0;
    public static final int STATE_REFRESH = 1;
    public static final int STATE_LOADMORE = 2;
    public static final int STATE_NOMORE = 3;
    public static final int STATE_PRESSNONE = 4;// 正在下拉但还没有到刷新的状态
    public static int mState = STATE_NONE;

主要是调用Activity,提供ProgressDialog显隐功能:

protected void hideWaitDialog() {
        FragmentActivity activity = getActivity();
        if (activity instanceof DialogControl) {
            ((DialogControl) activity).hideWaitDialog();
        }
    }

    protected ProgressDialog showWaitDialog() {
        return showWaitDialog(R.string.loading);
    }

BaseViewPagerFragment extends BaseFragment

官方描述:带有导航条的基类。
这一点从其持有的域也可以看出:

protected PagerSlidingTabStrip mTabStrip;
    protected ViewPager mViewPager;
    protected ViewPageFragmentAdapter mTabsAdapter;
    protected EmptyLayout mErrorLayout;

onCreateView()中加载以下布局:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="fill_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <!-- 导航标题栏 -->
    <net.oschina.app.widget.PagerSlidingTabStrip
        android:id="@+id/pager_tabstrip"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="?attr/windows_bg"
        app:allowWidthFull="true"
        app:slidingBlock="?attr/sliding_block_bg" >
    </net.oschina.app.widget.PagerSlidingTabStrip>

    <View
        android:id="@+id/view_pager_line"
        android:layout_width="match_parent"
        android:layout_below="@id/pager_tabstrip"
        android:layout_height="1px"
        android:background="?attr/lineColor" />

    <android.support.v4.view.ViewPager
        android:id="@+id/pager"
        style="@style/fill_fill"
        android:layout_below="@id/view_pager_line">
    </android.support.v4.view.ViewPager>

    <net.oschina.app.ui.empty.EmptyLayout
        android:id="@+id/error_layout"
        style="@style/fill_fill"
        android:visibility="gone" />

</RelativeLayout>

其中PagerSlidingTabStrip extends HorizontalScrollView,同样的一个效果,还可以参见Google Contacts源码中在PeopleActivity使用的ViewPagerTabs extends HorizontalScrollView:
http://androidxref.com/6.0.1_r10/xref/packages/apps/ContactsCommon/src/com/android/contacts/common/list/ViewPagerTabs.java

onViewCreated()中调用了如下两行代码,不用说,当然是留给子类覆盖的了:

        setScreenPageLimit();
        onSetupTabAdapter(mTabsAdapter);

NewsViewPagerFragment extends BaseViewPagerFragment

实现刚才说到的方法:

@Override
    protected void onSetupTabAdapter(ViewPageFragmentAdapter adapter) {
        String[] title = getResources().getStringArray(
                R.array.news_viewpage_arrays);
        adapter.addTab(title[0], "news", NewsFragment.class,
                getBundle(NewsList.CATALOG_ALL)); // 注一,用Bundle提供信息给Fragment
        adapter.addTab(title[1], "news_week", NewsFragment.class,
                getBundle(NewsList.CATALOG_WEEK));
        adapter.addTab(title[2], "latest_blog", BlogFragment.class,
                getBundle(BlogList.CATALOG_LATEST));
        adapter.addTab(title[3], "recommend_blog", BlogFragment.class,
                getBundle(BlogList.CATALOG_RECOMMEND));
    }

    private Bundle getBundle(int newType) {
        Bundle bundle = new Bundle();
        bundle.putInt(BaseListFragment.BUNDLE_KEY_CATALOG, newType);
        return bundle;
    }

    @Override
    protected void setScreenPageLimit() {
        mViewPager.setOffscreenPageLimit(3);
    }

可以看出,资讯和热点使用的同一个Fragment,博客和推荐也是同一个。
从mViewPager.setOffscreenPageLimit(3);来看,所有Tab页保留了下来。

比较有意思的是,它还实现了这样一个接口:

/** 
 * 当tabHost再次被点击时
 * @author FireAnt(http://my.oschina.net/LittleDY)
 * @version 创建时间:2014年11月17日 上午11:00:15 
 * 
 */
public interface OnTabReselectListener {
    
    public void onTabReselect();
}

具体来说是看该Fragment中嵌套的Fragment是否有实现此接口然后决定是否调用:

@Override
    public void onTabReselect() {
        try {
            int currentIndex = mViewPager.getCurrentItem();
            Fragment currentFragment = getChildFragmentManager().getFragments()
                    .get(currentIndex);
            if (currentFragment != null
                    && currentFragment instanceof OnTabReselectListener) {
                OnTabReselectListener listener = (OnTabReselectListener) currentFragment;
                listener.onTabReselect();
            }
        } catch (NullPointerException e) {
        }
    }

现在,我们来看综合页中的NewsFragment,同样是自顶向下。

BaseListFragment<T extends Entity> extends BaseFragment

这是一个泛型类,Bean包下有大量extends Entity的实体。
关于泛型可以参考这篇:http://www.jianshu.com/p/f258c907019d

使用了可以进行下拉刷新的布局,以下为代码片段:

<!-- google 官方下拉刷新 -->

    <android.support.v4.widget.SwipeRefreshLayout
        android:id="@+id/swiperefreshlayout"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:visibility="visible" >

        <ListView
            android:id="@+id/listview"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:divider="?attr/lineColor"
            android:listSelector="@color/transparent"
            android:dividerHeight="1px"
            android:scrollbars="none"
            android:scrollingCache="true" />
    </android.support.v4.widget.SwipeRefreshLayout>

提供了一个ParserTask extends AsyncTask,从代码中可以看到,其doInBackground中有如下调用:

new SaveCacheTask(getActivity(), data, getCacheKey()).execute();

这并不是一个好的实现,因为AsyncTask应该在主线程中创建,否则其内部的静态Handler将无法正常工作,具体可以参考《Android开发艺术探索》。
这里该应用的事件流中应该是ParserTask 本身有确保在主线程加载。

在onScrollStateChanged中提供滚动事件的特判,代码较长就不贴了。

提供上一篇中提到的已读列表功能:

/**
     * 保存已读的文章列表
     *
     * @param view
     * @param prefFileName
     * @param key
     */
    protected void saveToReadedList(final View view, final String prefFileName,
            final String key) {
        // 放入已读列表
        AppContext.putReadedPostList(prefFileName, key, "true");
        TextView tvTitle = (TextView) view.findViewById(R.id.tv_title);
        if (tvTitle != null) {
            tvTitle.setTextColor(AppContext.getInstance().getResources().getColor(ThemeSwitchUtils.getTitleReadedColor()));
        }
    }

onCreate()中读取前文注一提供的信息,实际使用当然会是在子类中了:

@Override
    public void onCreate(android.os.Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Bundle args = getArguments();
        if (args != null) {
            mCatalog = args.getInt(BUNDLE_KEY_CATALOG, 0); // 注二,子类中会使用此信息
        }
    }

onResume()中判断了是否要进行定时刷新操作:

@Override
    public void onResume() {
        super.onResume();
        if (onTimeRefresh()) {
            onRefresh();
        }
    }

NewsFragment extends BaseListFragment<News> implements OnTabReselectListener

明显使用了News这个Bean,取决于具体业务情况,略去不表。

Bean解析,可以看出数据使用的是xml格式,这一方法的实际调用位置在基类的ParserTask doInBackground中:

@Override
    protected NewsList parseList(InputStream is) throws Exception {
        NewsList list = null;
        try {
            list = XmlUtils.toBean(NewsList.class, is);
        } catch (NullPointerException e) {
            list = new NewsList();
        }
        return list;
    }

覆盖获取数据的方法,使用注二中提供的信息调用MobileAPI:

@Override
    protected void sendRequestData() {
        OSChinaApi.getNewsList(mCatalog, mCurrentPage, mHandler);
    }

之前提过的OnTabReselectListener有进行实现,可以看出是提供了一个刷新功能:

@Override
    public void onTabReselect() {
        onRefresh();
    }

而这个onRefresh()的实现是在基类BaseListFragment当中,也就是设置状态,ListView回到顶部,然后获取数据:

    // 下拉刷新数据
    @Override
    public void onRefresh() {
        if (mState == STATE_REFRESH) {
            return;
        }
        // 设置顶部正在刷新
        mListView.setSelection(0);
        setSwipeRefreshLoadingState();
        mCurrentPage = 0;
        mState = STATE_REFRESH;
        requestData(true);
    }

具体获取数据的时候,视情况看是读缓存还是发请求获取。
缓存的实现是CacheManager,非常纯粹的文件读写,略去不表。

根据业务情况,提供定时刷新的阈值给基类:

@Override
    protected long getAutoRefreshTime() {
        // 最新资讯两小时刷新一次
        if (mCatalog == NewsList.CATALOG_ALL) {

            return 2 * 60 * 60;
        }
        return super.getAutoRefreshTime();
    }

另外三个页面也是采用完全类似的方式,读者可以自行分析。
至此,综合页面分析完毕。下期再见。

相关文章

  • OSCHINA客户端完全剖析(二)

    接上期,着重分析主干逻辑,先看第一个综合页面。 五个Tab页定义可以看MainTab ,虽然enum占用存储较多,...

  • OSCHINA客户端完全剖析(一)

    首先感谢开源中国为我们带来了学习资源:http://www.oschina.net/p/oschina-andro...

  • OSCHINA客户端完全剖析(四)动弹

    跟前篇完全一样的方法追踪源码进行分析。而在这之前也不妨先进行一下预估:UI呈现上与综合是相同的,仅在单个数据项上多...

  • OSCHINA客户端完全剖析(三)分页加载和详情

    分页加载 Meizitu的实现 这个功能的实现跟MobileAPI的返回大有关联,我曾经在学习Meizitu的时候...

  • 超实用 Android 开发实例

    一、项目名称:开源中国 Android 客户端 项目简介:这是 OSCHINA 官方开发的 Android 客户端...

  • ArrayMap完全剖析

    ArrayMap是一种通用的key-value映射的数据结构,旨在提高内存效率,它与传统的HashMap有很大的不...

  • https完全剖析

    SSL/TLS协议运行机制的概述 图解SSL/TLS协议 https 客户端(即浏览器)是如何校验公钥证书合法性的...

  • oschina学习笔记(02) -- 引用框架

    开源中国oschina客户端源码依赖框架如下。 libs文件夹下的本地jar包

  • 完全剖析自己内心

    我是一个很适合也很擅长帮助其他人分析问题的小女生,我知道这是我强项,但是这也往往会忘记窥探自己内心世界 长的不如花...

  • 逻辑回归完全剖析

    算法原理 概述:算法通过梯度下降法去极大化似然函数,得到极大化时的权值向量。似然的本质就是根据已观察到的数据(现象...

网友评论

    本文标题:OSCHINA客户端完全剖析(二)

    本文链接:https://www.haomeiwen.com/subject/nznvkttx.html