TouTiao开源项目 分析笔记15 新闻详情之两种类型的实现


1.预览效果

1.1.首先看一下需要实现的效果。

  第一种,文字类型新闻。

  TouTiao开源项目 分析笔记15 新闻详情之两种类型的实现_第1张图片

   第二种,图片类型新闻。

  TouTiao开源项目 分析笔记15 新闻详情之两种类型的实现_第2张图片

 

 

1.2.在NewsArticleTextViewBinder中设置了点击事件 

            RxView.clicks(holder.itemView)
                    .throttleFirst(1, TimeUnit.SECONDS)
                    .subscribe(new Consumer() {
                        @Override
                        public void accept(@io.reactivex.annotations.NonNull Object o) throws Exception {
                            NewsContentActivity.launch(item);
                        }
                    }); 
      
     

  所以每个item,点击之后会执行accept中的跳转。

 


2.新闻详情之纯文本的活动

2.1.源代码

public class NewsContentActivity extends BaseActivity {

    private static final String TAG = "NewsContentActivity";
    private static final String IMG = "img";

    public static void launch(MultiNewsArticleDataBean bean) {
        InitApp.AppContext.startActivity(new Intent(InitApp.AppContext, NewsContentActivity.class)
                .putExtra(TAG, bean)
                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
    }

    public static void launch(MultiNewsArticleDataBean bean, String imgUrl) {
        InitApp.AppContext.startActivity(new Intent(InitApp.AppContext, NewsContentActivity.class)
                .putExtra(TAG, bean)
                .putExtra(IMG, imgUrl)
                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
    }

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.container);
        Intent intent = getIntent();
        getSupportFragmentManager().beginTransaction()
                .replace(R.id.container,
                        NewsContentFragment.newInstance(intent.getParcelableExtra(TAG), intent.getStringExtra(IMG)))
                .commit();
    }
}

 

 

2.2.外部传进去一个MultiNewsArticleDataBean类型,来执行跳转。

 

 

2.3.外部传进去一个MultiNewsArticleDataBean类型+url,来执行跳转。

 

 

2.4.这个活动的onCreate,将这个活动的布局用某一个碎片来代替。

  所以用哪个碎片来代替是我们即将要考虑的。

 

 

2.5.这里在清单中配置这个活动要特别注意:

  这个风格一定要是没有标题栏的。

  首先在清单中声明:

  <activity
            android:name=".module.news.content.NewsContentActivity"
            android:configChanges="orientation|screenSize|uiMode"
            android:label="@string/title_news_content"
            android:theme="@style/AppTheme.NoActionBar.Slidable"/>

  然后在资源文件的styles.xml进行声明这个style 

  

 


3.新闻详情片段Fragment

3.1.源代码 

package com.jasonjan.headnews.module.news.content;

import android.annotation.SuppressLint;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.design.widget.AppBarLayout;
import android.support.design.widget.CollapsingToolbarLayout;
import android.support.design.widget.Snackbar;
import android.support.v4.widget.ContentLoadingProgressBar;
import android.support.v4.widget.NestedScrollView;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.Toolbar;
import android.text.TextUtils;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.WindowManager;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.ImageView;

import com.jasonjan.headnews.R;
import com.jasonjan.headnews.bean.news.MultiNewsArticleDataBean;
import com.jasonjan.headnews.global.Constant;
import com.jasonjan.headnews.main.ErrorAction;
import com.jasonjan.headnews.main.IntentAction;
import com.jasonjan.headnews.module.base.BaseFragment;
import com.jasonjan.headnews.util.ImageLoader;
import com.jasonjan.headnews.util.SettingUtil;
import com.jasonjan.headnews.widget.AppBarStateChangeListener;

/**
 * Created by JasonJan on 2018/1/8.
 */

public class NewsContentFragment extends BaseFragment implements INewsContent.View{
    private static final String TAG = "NewsContentFragment";
    private static final String IMG = "img";
    // 新闻链接 标题 头条号 文章号 媒体名
    private String shareUrl;
    private String shareTitle;
    private String mediaUrl;
    private String mediaId;
    private String mediaName;
    private String imgUrl;
    private boolean isHasImage;
    private MultiNewsArticleDataBean bean;

    private Toolbar toolbar;
    private WebView webView;
    private NestedScrollView scrollView;
    private INewsContent.Presenter presenter;
    private ContentLoadingProgressBar progressBar;
    private AppBarLayout appBarLayout;
    private CollapsingToolbarLayout collapsingToolbarLayout;
    private ImageView imageView;
    private SwipeRefreshLayout swipeRefreshLayout;

    public static NewsContentFragment newInstance(Parcelable dataBean, String imgUrl) {
        NewsContentFragment instance = new NewsContentFragment();
        Bundle bundle = new Bundle();
        bundle.putParcelable(TAG, dataBean);
        bundle.putString(IMG, imgUrl);
        instance.setArguments(bundle);
        return instance;
    }

    @Override
    protected int attachLayoutId() {
        imgUrl = getArguments().getString(IMG);
        isHasImage = !TextUtils.isEmpty(imgUrl);
        return isHasImage ? R.layout.fragment_news_content_img : R.layout.fragment_news_content;
    }

    @Override
    protected void initView(View view){
        toolbar = view.findViewById(R.id.toolbar);
        initToolBar(toolbar, true, "");
        toolbar.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                scrollView.smoothScrollTo(0, 0);
//                ObjectAnimator anim = ObjectAnimator.ofInt(webView, "scrollY", webView.getScrollY(), 0);
//                anim.setDuration(500).start();
            }
        });

        webView = view.findViewById(R.id.webview);
        initWebClient();

        scrollView = view.findViewById(R.id.scrollView);
        scrollView.setOnScrollChangeListener(new NestedScrollView.OnScrollChangeListener() {
            @Override
            public void onScrollChange(NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
                onHideLoading();
            }
        });

        progressBar = view.findViewById(R.id.pb_progress);
        int color = SettingUtil.getInstance().getColor();
        progressBar.getIndeterminateDrawable().setColorFilter(color, PorterDuff.Mode.MULTIPLY);
        progressBar.show();

        swipeRefreshLayout = view.findViewById(R.id.refresh_layout);
        swipeRefreshLayout.setColorSchemeColors(SettingUtil.getInstance().getColor());
        swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                swipeRefreshLayout.post(new Runnable() {
                    @Override
                    public void run() {
                        swipeRefreshLayout.setRefreshing(true);
                    }
                });
                presenter.doLoadData(bean);
            }
        });

        if (isHasImage) {
            appBarLayout = view.findViewById(R.id.app_bar_layout);
            collapsingToolbarLayout = view.findViewById(R.id.collapsing_toolbar);
            imageView = view.findViewById(R.id.iv_image);
        }
        setHasOptionsMenu(true);
    }

    @Override
    protected void initData(){
        Bundle bundle=getArguments();
        try{
            bean=bundle.getParcelable(TAG);
            presenter.doLoadData(bean);
            shareUrl = !TextUtils.isEmpty(bean.getShare_url()) ? bean.getShare_url() : bean.getDisplay_url();
            shareTitle = bean.getTitle();
            mediaName = bean.getMedia_name();
            mediaUrl = "http://toutiao.com/m" + bean.getMedia_info().getMedia_id();
            mediaId = bean.getMedia_info().getMedia_id();
        }catch (Exception e){
            ErrorAction.print(e);
        }

        if(isHasImage){
            ImageLoader.loadCenterCrop(getActivity(), bundle.getString(IMG), imageView, R.mipmap.error_image, R.mipmap.error_image);
            appBarLayout.addOnOffsetChangedListener(new AppBarStateChangeListener() {
                @Override
                public void onStateChanged(AppBarLayout appBarLayout, AppBarStateChangeListener.State state) {
                    if (state == State.EXPANDED) {
                        // 展开状态
                        collapsingToolbarLayout.setTitle("");
                        toolbar.setBackgroundColor(Color.TRANSPARENT);
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                            getActivity().getWindow().setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
                        }
                    } else if (state == State.COLLAPSED) {
                        // 折叠状态

                    } else {
                        // 中间状态
                        collapsingToolbarLayout.setTitle(mediaName);
                        toolbar.setBackgroundColor(SettingUtil.getInstance().getColor());
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                            getActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
                        }
                    }
                }
            });
        }else{
            toolbar.setTitle(mediaName);
        }
    }

    @Override
    public void onPause() {
        super.onPause();
        if (isHasImage) {
            appBarLayout.setExpanded(false);
        }
    }

    @SuppressLint("SetJavaScriptEnabled")
    private void initWebClient() {
        WebSettings settings = webView.getSettings();
        settings.setJavaScriptEnabled(true);
        // 缩放,设置为不能缩放可以防止页面上出现放大和缩小的图标
        settings.setBuiltInZoomControls(false);
        // 缓存
        settings.setCacheMode(WebSettings.LOAD_DEFAULT);
        // 开启DOM storage API功能
        settings.setDomStorageEnabled(true);
        // 开启application Cache功能
        settings.setAppCacheEnabled(true);
        // 判断是否为无图模式
        settings.setBlockNetworkImage(SettingUtil.getInstance().getIsNoPhotoMode());
        // 不调用第三方浏览器即可进行页面反应
        webView.setWebViewClient(new WebViewClient() {
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                if (!TextUtils.isEmpty(url)) {
                    view.loadUrl(url);
                }
                return true;
            }

            @Override
            public void onPageFinished(WebView view, String url) {
                onHideLoading();
                super.onPageFinished(view, url);
            }
        });

        webView.setOnKeyListener(new View.OnKeyListener() {
            @Override
            public boolean onKey(View view, int i, KeyEvent keyEvent) {
                if ((keyEvent.getKeyCode() == KeyEvent.KEYCODE_BACK) && webView.canGoBack()) {
                    webView.goBack();
                    return true;
                }
                return false;
            }
        });

        webView.setWebChromeClient(new WebChromeClient() {
            @Override
            public void onProgressChanged(WebView view, int newProgress) {
                super.onProgressChanged(view, newProgress);
                if (newProgress >= 90) {
                    onHideLoading();
                } else {
                    onShowLoading();
                }
            }
        });
    }

    @Override
    public void onSetWebView(String url, boolean flag) {
        // 是否为头条的网站
        if (flag) {
            webView.loadDataWithBaseURL(null, url, "text/html", "utf-8", null);
        } else {
            /*
               ScrollView 嵌套 WebView, 导致部分网页无法正常加载
               如:https://temai.snssdk.com/article/feed/index/?id=11754971
               最佳做法是去掉 ScrollView, 或使用 NestedScrollWebView
             */
            if (shareUrl.contains("temai.snssdk.com")) {
                webView.getSettings().setUserAgentString(Constant.USER_AGENT_PC);
            }
            webView.loadUrl(shareUrl);
        }
    }

    @Override
    public void onShowNetError() {
        Snackbar.make(scrollView, R.string.network_error, Snackbar.LENGTH_INDEFINITE).show();
    }

    @Override
    public void setPresenter(INewsContent.Presenter presenter) {
        if (null == presenter) {
            this.presenter = new NewsContentPresenter(this);
        }
    }

    @Override
    public void onShowLoading() {
        progressBar.show();
    }

    @Override
    public void onHideLoading() {
        progressBar.hide();
        swipeRefreshLayout.post(new Runnable() {
            @Override
            public void run() {
                swipeRefreshLayout.setRefreshing(false);
            }
        });
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        inflater.inflate(R.menu.menu_browser, menu);
        super.onCreateOptionsMenu(menu, inflater);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();
        switch (id) {
            case R.id.action_open_comment:
               // NewsCommentActivity.launch(bean.getGroup_id() + "", bean.getItem_id() + "");
                break;

            case R.id.action_share:
                IntentAction.send(getActivity(), shareTitle + "\n" + shareUrl);
                break;

            case R.id.action_open_in_browser:
                startActivity(new Intent(Intent.ACTION_VIEW).setData(Uri.parse(shareUrl)));
                break;

            case android.R.id.home:
                getActivity().onBackPressed();
                break;

            case R.id.action_open_media_home:
               // MediaHomeActivity.launch(mediaId);
                break;
        }
        return super.onOptionsItemSelected(item);
    }
}
View Code

 

 

3.2.一个新建片段实例,供外部调用。

  ==>返回一个片段Fragment。

 

 

3.3.决定两种布局。

  一种是外部布局没有图片==>fragment_news_content.xml。

  一种是外部布局有图片的==>fragment_news_content_img.xml。

  

  fragment_news_content.xml源代码:


<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/news_content_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical">

    

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fadeScrollbars="true"
        android:scrollbarFadeDuration="1"
        android:scrollbars="vertical"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <android.support.v4.widget.SwipeRefreshLayout
            android:id="@+id/refresh_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <android.support.v4.widget.NestedScrollView
                android:id="@+id/scrollView"
                android:layout_width="match_parent"
                android:layout_height="match_parent">

                <WebView
                    android:id="@+id/webview"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:layout_marginBottom="16dp"
                    android:layout_marginLeft="8dp"
                    android:layout_marginRight="8dp"
                    android:layout_marginTop="16dp"
                    android:background="@color/windowBackground"/>

            

        

        <android.support.v4.widget.ContentLoadingProgressBar
            android:id="@+id/pb_progress"
            style="?android:attr/progressBarStyle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"/>
    

View Code

  页面预览:

  TouTiao开源项目 分析笔记15 新闻详情之两种类型的实现_第3张图片

 

  fragment_news_content_img.xml


<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/news_content_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/app_bar_layout"
        android:layout_width="match_parent"
        android:layout_height="192dp"
        android:fitsSystemWindows="true"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/collapsing_toolbar"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fitsSystemWindows="true"
            app:layout_scrollFlags="scroll|enterAlwaysCollapsed|enterAlways">

            <ImageView
                android:id="@+id/iv_image"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:fitsSystemWindows="true"
                android:minHeight="192dp"
                android:scaleType="centerCrop"
                android:scrollbarAlwaysDrawVerticalTrack="true"
                android:scrollbarStyle="insideInset"
                android:src="@mipmap/error_image"
                tools:ignore="ContentDescription"/>

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:layout_collapseMode="pin"
                app:popupTheme="@style/AppTheme.PopupOverlay"/>

        
    

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fadeScrollbars="true"
        android:scrollbarFadeDuration="1"
        android:scrollbars="vertical"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <android.support.v4.widget.SwipeRefreshLayout
            android:id="@+id/refresh_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <android.support.v4.widget.NestedScrollView
                android:id="@+id/scrollView"
                android:layout_width="match_parent"
                android:layout_height="match_parent">

                <WebView
                    android:id="@+id/webview"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:layout_marginBottom="16dp"
                    android:layout_marginLeft="8dp"
                    android:layout_marginRight="8dp"
                    android:layout_marginTop="16dp"
                    android:background="@color/windowBackground"/>

            

        

        <android.support.v4.widget.ContentLoadingProgressBar
            android:id="@+id/pb_progress"
            style="?android:attr/progressBarStyle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"/>
    

View Code

  预览图片:

  TouTiao开源项目 分析笔记15 新闻详情之两种类型的实现_第4张图片

 

 

3.4.初始化视图。

  获取toolbar+它的点击监听事件。

  获取webView+初始化WebClient()。

  获取scrollView+它的滑动监听事件。 

  获取progressBar+它的颜色过滤器设置。

  获取swipeRefreshLayout+它的刷新事件。

  如果前面的新闻有图片类型,

  则获取appBarLayout+collapsingToolbarLayout+imageView

  设置菜单。

 

 

3.5.重写onSetWebView,

  这个onSetWebView是继承自定义接口INewsContent中的View接口。

  里面还有一个Presenter是定义处理器的接口。

  判断是否为头条的网址,看情况loadUrl。

 

 

3.6.重写onShowNetError。判断网络不给力的情况。

 

 

3.7.重写setPresenter,添加处理器。

 

 

3.8.重写onShowLoading,调用进度条的展示函数。

 

 

3.9.重写onHideLoading,调用进度条+刷新圈的隐藏。

 

 

3.10.重写菜单加载函数。



      xmlns:app="http://schemas.android.com/apk/res-auto">

    <item
        android:id="@+id/action_open_comment"
        android:icon="@drawable/ic_comment_white_24dp"
        android:title="@string/action_open_comment"
        app:showAsAction="ifRoom"/>

    <item
        android:id="@+id/action_open_media_home"
        android:icon="@drawable/ic_account_circle_white_24dp"
        android:title="@string/action_open_media_home"
        app:showAsAction="ifRoom"/>

    
    
    
    
    

    <item
        android:id="@+id/action_share"
        android:icon="@drawable/ic_share_white_24dp"
        android:title="@string/action_share"
        app:showAsAction="ifRoom"/>

    <item
        android:id="@+id/action_open_in_browser"
        android:title="@string/action_open_in_browser"
        app:showAsAction="never"/>

  菜单预览:

  TouTiao开源项目 分析笔记15 新闻详情之两种类型的实现_第5张图片

 

 

3.11.菜单的点击事件处理。

  

 

3.12.实现appBarLayout的折叠效果。

  首先要有一个监听AppBar状态的监听器==>AppBarStateChangeListener。

package com.meiji.toutiao.widget.helper;

import android.support.design.widget.AppBarLayout;

/**
 * Created by Meiji on 2017/7/20.
 * 监听CollapsingToolbarLayout的展开与折叠
 * https://stackoverflow.com/questions/31682310/android-collapsingtoolbarlayout-collapse-listener
 */

public abstract class AppBarStateChangeListener implements AppBarLayout.OnOffsetChangedListener {

    private State mCurrentState = State.IDLE;

    @Override
    public final void onOffsetChanged(AppBarLayout appBarLayout, int i) {
        if (i == 0) {
            if (mCurrentState != State.EXPANDED) {
                onStateChanged(appBarLayout, State.EXPANDED);
            }
            mCurrentState = State.EXPANDED;
        } else if (Math.abs(i) >= appBarLayout.getTotalScrollRange()) {
            if (mCurrentState != State.COLLAPSED) {
                onStateChanged(appBarLayout, State.COLLAPSED);
            }
            mCurrentState = State.COLLAPSED;
        } else {
            if (mCurrentState != State.IDLE) {
                onStateChanged(appBarLayout, State.IDLE);
            }
            mCurrentState = State.IDLE;
        }
    }

    public abstract void onStateChanged(AppBarLayout appBarLayout, State state);

    protected enum State {EXPANDED, COLLAPSED, IDLE}
}

  然后调用的时候,分别实现不同状态的需求。

appBarLayout.addOnOffsetChangedListener(new AppBarStateChangeListener() {
                @Override
                public void onStateChanged(AppBarLayout appBarLayout, AppBarStateChangeListener.State state) {
                    if (state == State.EXPANDED) {
                        // 展开状态
                        collapsingToolbarLayout.setTitle("");
                        toolbar.setBackgroundColor(Color.TRANSPARENT);
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                            getActivity().getWindow().setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
                        }
                    } else if (state == State.COLLAPSED) {
                        // 折叠状态

                    } else {
                        // 中间状态
                        collapsingToolbarLayout.setTitle(mediaName);
                        toolbar.setBackgroundColor(SettingUtil.getInstance().getColor());
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                            getActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
                        }
                    }
                }
            });

  具体效果:

  

 


4.处理器的实现

4.1.处理器源代码 

package com.jasonjan.headnews.module.news.content;

import android.text.TextUtils;

import com.jasonjan.headnews.api.INewsApi;
import com.jasonjan.headnews.bean.news.MultiNewsArticleDataBean;
import com.jasonjan.headnews.bean.news.NewsContentBean;
import com.jasonjan.headnews.main.ErrorAction;
import com.jasonjan.headnews.main.RetrofitFactory;
import com.jasonjan.headnews.util.SettingUtil;

import io.reactivex.Observable;
import io.reactivex.ObservableEmitter;
import io.reactivex.ObservableOnSubscribe;
import io.reactivex.ObservableSource;
import io.reactivex.Observer;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.annotations.NonNull;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Function;
import io.reactivex.schedulers.Schedulers;
import okhttp3.ResponseBody;
import retrofit2.Response;

/**
 * Created by JasonJan on 2018/1/8.
 */

public class NewsContentPresenter implements INewsContent.Presenter {
    private static final String TAG = "NewsContentPresenter";
    private INewsContent.View view;
    private String groupId;
    private String itemId;

    NewsContentPresenter(INewsContent.View view) {
        this.view = view;
    }
    
    @Override
    public void doLoadData(MultiNewsArticleDataBean dataBean){
        final String url = dataBean.getDisplay_url();

        Observable
                .create(new ObservableOnSubscribe() {
                    @Override
                    public void subscribe(@NonNull ObservableEmitter e) throws Exception {
                        try {
                            Response response = RetrofitFactory.getRetrofit().create(INewsApi.class)
                                    .getNewsContentRedirectUrl(url).execute();
                            // 获取重定向后的 URL 用于拼凑API
                            if (response.isSuccessful()) {
                                String httpUrl = response.raw().request().url().toString();
                                if (!TextUtils.isEmpty(httpUrl) && httpUrl.contains("toutiao")) {
                                    String api = httpUrl + "info/";
                                    e.onNext(api);
                                } else {
                                    e.onError(new Throwable());
                                }
                            } else {
                                e.onError(new Throwable());
                            }
                        } catch (Exception e1) {
                            e.onComplete();
                            ErrorAction.print(e1);
                        }
                    }
                })
                .subscribeOn(Schedulers.io())
                .switchMap(new Function>() {
                    @Override
                    public ObservableSource apply(@NonNull String s) throws Exception {
                        return RetrofitFactory.getRetrofit().create(INewsApi.class).getNewsContent(s);
                    }
                })
                .map(new Function() {
                    @Override
                    public String apply(@NonNull NewsContentBean bean) throws Exception {
                        return getHTML(bean);
                    }
                })
                .observeOn(AndroidSchedulers.mainThread())
                .compose(view.bindToLife())
                .subscribe(new Observer() {
                    @Override
                    public void onSubscribe(@NonNull Disposable d) {

                    }

                    @Override
                    public void onNext(@NonNull String s) {
                        view.onSetWebView(s, true);
                    }

                    @Override
                    public void onError(@NonNull Throwable e) {
                        view.onSetWebView(null, false);
                        ErrorAction.print(e);
                    }

                    @Override
                    public void onComplete() {
                        doShowNetError();
                    }
                }); 
    }
    
    private String getHTML(NewsContentBean bean){
        String title = bean.getData().getTitle();
        String content = bean.getData().getContent();
        if (content != null) {

            String css = "";
            if (SettingUtil.getInstance().getIsNightMode()) {
                css = css.replace("toutiao_light", "toutiao_dark");
            }

            String html = "\n" +
                    "\n" +
                    "\n" +
                    "    " +
                    css +
                    "\n" +
                    "
\n" + "
" + "

" + title + "

" + content + "
\n" + "
\n" + "\n" + ""; return html; } else { return null; } } @Override public void doRefresh() { } @Override public void doShowNetError() { view.onHideLoading(); view.onShowNetError(); } }

 

 

4.2.处理器构造函数传来了一个及时的View。

  这样方便调用及时的View的一些界面函数。

 

 

4.3.重写加载数据的函数,参数是一个bean。

  这个bean是之前页面传递进来的,可以知道某一个新闻要用的显示url。

  有四个关键点:

  首先是subscribe定阅函数中,要去请求API,得到重定向后的URL。

  然后是switchMap函数,将String转换为一个ObservableSource类型。

  然后是map函数,将NewsContentBean转换为String,调用了自定义函数。

  最后是subscribe函数,参数编程Observer了,写4个回调即可。

 

 

4.4.自定义getHTML函数。

  传进去一个NewsContentBean

  得到一个html的String。

 

 

4.5.重写doRefresh函数,里面什么都没做。

  因为swipeRefresh可以刷新。没必要重复。

 

 

4.5.重写doShowNetError函数,用于显示网络错误。

  这里需要调用及时的view的显示函数。


5.Bean类

5.1.这里先分析数据类型。 

    /**
     * _ck : {}
     * data : {"detail_source":"北青深一度","media_user":{"avatar_url":"http://p1.pstatp.com/large/ef5001669ce12ead37e","id":51914057866,"screen_name":"北青深一度"},"publish_time":1482146605,"title":"深度调查|寻找冥婚凶案现场的\u201c第四个男人\u201d","url":"http://toutiao.com/group/6365769989571821826/","is_original":true,"is_pgc_article":true,"content":"

记者/张倩<\/strong><\/p>

编辑/倪家宁<\/strong><\/p>

http://p1.pstatp.com/large/12dd0005e16c3b748a52\" img_width=\"450\" img_height=\"800\" alt=\"深度调查|寻找冥婚凶案现场的\u201c第四个男人\u201d\" οnerrοr=\"javascript:errorimg.call(this);\"><\/p>

在工地上吃饭的刘顺义留下了生前最后一张照片<\/p>

陕西子长县村民刘某林,因母亲外走他乡,10年前购买一具女尸为父冥婚。去年年末,他接到老家人的电话,称其父\u201c老坟尸骨\u201d被盗挖,因此怀疑是堂兄刘顺义将他花2000元买女尸配阴婚之事外泄。登门\u201c讨说法\u201d的一干人,混乱中将刘顺义在家中打死,刘某林之子刘亚东因涉故意伤害罪被捕。事后证实,老坟被挖纯属谣言。<\/p>

2016年12月9日,延安市检察院公诉的刘亚东涉嫌故意伤害致人死亡案,在子长县法院开庭。<\/p>

三个半小时的庭审,围绕被害人的真实死因、凶案现场是否有第四个男人、凶器上为何出现\u201c不明身份者\u201d的基因物质这一环环相扣的焦点问题,控辨双方激烈争论,直到法医胡志强走上法庭,出庭质证。<\/p>

源于阴婚陋习,始于冥尸交易,一个虚妄的\u201c冥尸被盗\u201d传言,一名中年男子的意外丧命,一年之后仍无法平息人们疑问的是,在这起简单的寻仇凶案背后,究竟还隐藏着什么?<\/p><\/blockquote>

http://p9.pstatp.com/large/134a00035d7051c0a0c4\" img_width=\"1280\" img_height=\"960\" alt=\"深度调查|寻找冥婚凶案现场的\u201c第四个男人\u201d\" οnerrοr=\"javascript:errorimg.call(this);\"><\/p>

家人祭奠死者<\/p>

凶案现场的\u201c小平头\u201d蒸发了<\/strong><\/p>

12月2日上午10点,延安市子长县急救中心外,年近50岁的薛彩红在垃圾桶一侧烧着一张张纸钱,升腾的灰烟时而会遮住她满是泪水的面庞。<\/p>

去年今日,薛彩红的丈夫刘顺义因为一场莫须有的\u201c冥尸被盗\u201d传言,被打上门来\u201c讨说法\u201d的凶徒,重伤致死在自己家中。<\/p>

选择在这里祭奠,是因一墙之隔的医院太平间,存放着丈夫刘顺义的遗体。<\/p>

子长县人民医院急诊抢救病历显示,2015年12月2日19时20分,刘顺义在外伤后意识丧失半小时,被送至此处。诊断结果:特重型闭合性颅脑损伤,右侧额颞顶部硬膜下血肿,右顶叶脑挫伤,蛛网膜下腔出血,气颅,上唇多处皮肤裂伤,后枕部头皮挫伤。半小时后,刘顺义被宣布临床死亡。<\/p>

12月9日,陕西延安市检察院公诉的\u201c2015.12.02\u201d刘亚东故意伤害致人死亡案,在子长县人民法院开庭审理。<\/p>

2015年秋末,正在延安市打零工的刘顺义,接到老家同族村人的电话称,因为刘顺义把堂哥刘某林给老人买女尸配阴婚的事泄露出去了,丢女尸的人家现在不仅找上门来,还曝出刘顺义因此得了两万元好处费。<\/p>

刘顺义的女儿刘乐说,接到电话的父亲,一头雾水。他说自己从未把堂哥刘某林给其父配冥尸的事说给对方人家,更没因此拿过什么\u201c好处费\u201d。心中无愧的刘顺义决定动身回家,当面说清这件事。<\/p>

陕西、山西一带自古有\u201c配冥尸\u201d的风俗。十几年前,刘某林的母亲离家出走,为了让父亲在阴间不至于过于孤单,刘某林买了一具女尸,于2014年下棺与父亲合葬。<\/p>

刘乐回忆,父亲从延安赶回子长后,在村书记和同族村人主持下,曾就此事与对方进行过一次对证,但因刘某林在现场不肯配合,此事不了了之。<\/p>

2015年12月2日下午6时许,薛彩红和刘顺义刚吃过晚饭,听到院门铁锁发出响声。出门一看,是刘某林的老婆和女儿,便把她们迎进院来。\u201c谁想她们进屋后,便和我家掌柜的争吵撕扯起来,随后刘某林的老婆便躺在我家门口。刘某林的女儿掏出手机报信,说她妈被打了。\u201d薛彩红说。<\/p>

深一度(微信号:intodeepthoughts<\/strong>)<\/strong>记者事后了解,事发当天同村有人在刘某林家老坟发现,其棺土有新翻动迹象,便纷纷议论配阴婚女尸已被丢尸人家挖回。刘某林家人接电后,未经核实,便登门向刘顺义寻仇。事后调查发现,刘某林家老坟并未被盗。<\/p>

刘顺义的媳弟刘张彦当晚正在姐姐家帮忙带娃,听到吵闹声,他凑近窗口看到,先后四个男人,闯进姐家公婆的左侧厢房。<\/p>

\u201c其中一个男子抓住我的左胳膊,然后挥手打了一拳。我注意到,这个男人身材瘦小,平头阔额,年龄在二三十岁。\u201d薛彩红向深一度(微信号:intodeepthoughts<\/strong>)<\/strong>记者描述。就在她试图挡住几人冲进屋时,她发现混乱中丈夫刘顺义已经倒地,左侧太阳穴后淌出鲜血。<\/p>

薛彩红称,当时冲进她家的四个男人,她只认识其中一人,是刘某林的儿子、被告人刘亚东。案发后她获悉,三个陌生男人中的两个,分别是刘某林的长、次女婿。而将她左臂打青的\u201c小平头\u201d,却从此\u201c蒸发\u201d了。<\/p>


视频显示现场有\u201c第四个男人\u201d<\/strong><\/p>

案发后两个小时,被告人刘亚东在得知被害人抢救无效死亡后,赶到子长县公安局刑侦大队投案。<\/p>

在随后的供述中,刘亚东称为保护家人免受持刀的刘顺义的伤害,自己从院内捡起一截一米长的木棍,隔着门前阻挡的数人,掷向屋中的刘顺义\u2026\u2026和他到刘顺义家\u201c讨说法\u201d的,除了母亲和妹妹,还有自己的两位妹夫。<\/p>

与其截然相反的描述是,刘顺义家在场的三个目击者,第一时间向警方所做的笔录中,都称有4个男人出现在案发现场。<\/p>

儿媳刘跳跳称,\u201c我公公(刘顺义)被四名男子和两名女子殴打\u2026\u2026那四名男子在门外往里挤,被我婆婆挡着\u2026\u2026等我将110民警接回来,那四名男子走了,两名女子还在。\u201d<\/p>

深一度(微信号:intodeepthoughts<\/strong>)<\/strong>记者实地走访发现,案发现场位于刘家沟村第10排,刘顺义家院子南侧有一个东西走向的巷道。从刘顺义家跑出后到主干道,至少有三个摄像头,其中一个位于必经之路的三岔路口。而据被告人刘亚东供述,事发后他已先期从迎宾饭店对面的小路逃走,而从三岔口到迎宾饭店,至少还有5个摄像头。<\/p>

事发后,由于被告人及家属坚称只有三个男人到场,刘乐便开始对事发路段摄像头的逐个排查,并将提取到的9处安有摄像头的位置及地点提供给了警方。<\/p>

\u201c可警方对我们的证据始终不置可否,一会儿说其中的摄像头坏了,一会称有些摄像头是个人的。\u201d刘乐向说。<\/p>

在死者家属执着的追逼下,最终刘顺义家人拿到了一段事发时的视频。视频显示,事发当晚7时05分许,有三个男子一同出来上了一辆轿车,车牌号显示是被告人家的车辆。而在当地侦查机关的笔录中,身份为刘某林女婿的两个人,均否认有第三个男人上车。大女婿辩称,\u201c从监控上看,出来时是三个人,但那个人我们根本就不认识,他也没有上我们开的车。\u201d<\/p>

而在刘乐提供的今年1月8日的一段录音中,深一度(微信号:intodeepthoughts<\/strong>)<\/strong>记者听到警方的办案人员向其家人确认,经过反复观看并向负责监控的技术人员请教,最终确认一同上车的是三个男人。奇怪的是,检察院的公诉书中,并未对\u201c现场第四个男人\u201d之事实予以认定。<\/p>

http://p1.pstatp.com/large/134900031ee62f7e0f7a\" img_width=\"2309\" img_height=\"1732\" alt=\"深度调查|寻找冥婚凶案现场的\u201c第四个男人\u201d\" οnerrοr=\"javascript:errorimg.call(this);\"><\/p>

薛彩红在家中指认丈夫倒地的位置<\/p>


法医论证木棍掷打不可能致死<\/strong><\/p>

2015年12月16日,事发后半个月,延安日报刊发了一则消息,题目是《话不投机引发厮打,木棍甩击致人死亡》。<\/p>

该文描述:因话不投机,双方发生厮打,闻讯赶来的南某香之子刘亚东等人,见母亲瘫坐在刘顺义家窑内地下,便欲冲入厮打。南某香为避免事态扩大,与其他人挡在门口。刘亚东见进门无望,便拿起一根木棍甩向门内,木棍正好打在刘顺义头部,致其当即倒地不省人身。经县医院抢救无效死亡\u2026\u2026<\/p>

\u201c我接案后的第一个疑点,就发现该案还未经庭审,侦查机关就仅凭到案人自己的供述,确定了凶手凶器,甚至使用凶器的手法。\u201d陕西省律协刑专委副主任、陕西臻理律师事务所房立刚说。<\/p>

延安市检察院公诉书指控,被告人刘亚东到现场后看见母亲躺在地上而心生不满,即从地上捡起一根木棍欲扑进房间殴打刘顺义,被家里几人拉住。刘顺义也在其平房内手持菜刀喊骂,被妻子薛彩红及儿媳刘跳跳在房门口阻挡不得出来。刘亚东趁机将手中木棍扔进去击中刘顺义的面部致其躺倒在地。经鉴定,死者刘顺义系钝器打击上唇部致使后枕部着地致重度颅脑损伤而死亡。<\/p>

12月9日庭审现场,著名法医胡志强依据《刑事诉讼法》第192条之规定,以\u201c有专门知识的人\u201d身份到庭质证。此前,胡志强曾在湖南黄静死亡案、福建念斌投毒案、海南陈满杀人案、北京常林锋涉嫌杀妻焚尸案等案件中,担任鉴定或论证专家。随着分析论证的深入,子长县公安司法鉴定中心得出的死因结论,当庭被逆袭反转。<\/p>

胡志强审查认为,刘顺义系右枕部遭到他人持钝性物体(现场提取的木棍)直接击打后致严重颅脑损伤死亡。刘顺义的死亡不符合\u201c钝器打击上唇部致使后枕部着地\u201d的致伤方式。\u201c简单来说,就是远处持棍掷击的损伤力度,不可能达到刘顺义损伤如此严重的程度。\u201d胡志强解释道。 <\/p>

胡志强进一步分析说,刘顺义系右枕部遭到他人持钝性物体(现场提取的木棍)的直接打击后,面部撞击在硬质地面上致口唇不规则挫裂创和牙齿断裂,并形成对冲性颅底骨折,因严重的硬膜下血肿,珠网模下腔出血,脑损伤致脑疝形成死亡。<\/p>

具有司法部评聘主任法医师资格、南华大学法医学教授熊平博士,12月18日接受深一度(微信号:intodeepthoughts<\/strong>)<\/strong>记者采访时也明确表示,相关资料证实,死者的致命伤不可能为木棍掷击的力度所能达到。<\/p>


凶器上DNA\u201c未知者\u201d是谁?<\/strong>
<\/p>

法医胡志强的审查意见参考了陕西省公安司法鉴定中心法庭科学DNA鉴定意见。其DNA鉴定证实:木棍上提取的几个检材点,检出的是人体细胞组织而非血迹。现场提取断裂的木棍断裂端附着的细胞组织、完整端附着的细胞组织均与刘顺义血样的STR(即基因分型)结果一致。<\/p>

\u201c如果仅仅是投掷木棍致其口唇出血倒地摔死,应该木棍只有一端会接触到死者,而不会在木棍两端均沾有死者DNA物质,而口唇面部的血迹,也一定会沾染在木棍上,不会没有。\u201d房立刚进一步分析称。<\/p>

通过反复研究陕西省公安司法鉴定中心法庭科学DNA鉴定意见,胡志强还有另一个重大发现:在被定义为唯一凶器的木棍上,鉴定人员曾经在四个点位上做STR(基因分型)比对。结果显示,这四个点位上提取到的基因物质,都不和被告人刘亚东的基因分型相匹配。<\/p>

庭审之初,刘亚东在接受律师交叉询问时曾明确表示,自己当时持棍掷击刘顺义时,手握的正是该木棍中部。而在该中部点位提取到的STR基因分型,不仅未和被告人吻合,也未和当时在场的9个人中的任何一人比对上,这意味着凶器或凶手可能另有其它。<\/p>

这意味着,DNA的鉴定结果提示的可能性,和之前视频显示凶案现场有\u201c第四个男人\u201d相吻合。<\/p>

\u201c可惜的是,省公安厅司法鉴定中心,在其鉴定书中只列出了在场9人的分型结果,却未将\u2018无名氏\u2019的基因分型图表列上。\u201d胡志强说。<\/p>

\u201c到底案发时的第四个男人是谁?案发时这个人做了什么?人后来去了哪里?这一切都是待解之谜。如果慑于某种压力,而把这样一起简单的案件,人为操作成一起\u201c葫芦案\u201d,将会对社会产生很大的负面影响。\u201d房立刚说。<\/p>

庭审结束时,主审法官向死者家属征求意见,刘顺义的女儿刘乐代表家人表态,不查清真相,绝不接受调解。接受赔偿的前提,是将本案真相厘清。<\/p><\/div>

本文为头条号作者原创,未经授权,不得转载。<\/p>","source":"北青深一度","video_play_count":2} * success : true */

 

 

5.2.根据数据类型,然后写出这个简单的Bean类。

  这里用于新闻内容Bean数据。

package com.meiji.toutiao.bean.news;

/**
 * Created by Meiji on 2016/12/19.
 */

public class NewsContentBean {


    /**
     * _ck : {}
     * data : {"detail_source":"北青深一度","media_user":{"avatar_url":"http://p1.pstatp.com/large/ef5001669ce12ead37e","id":51914057866,"screen_name":"北青深一度"},"publish_time":1482146605,"title":"深度调查|寻找冥婚凶案现场的\u201c第四个男人\u201d","url":"http://toutiao.com/group/6365769989571821826/","is_original":true,"is_pgc_article":true,"content":"

记者/张倩<\/strong><\/p>

编辑/倪家宁<\/strong><\/p>

http://p1.pstatp.com/large/12dd0005e16c3b748a52\" img_width=\"450\" img_height=\"800\" alt=\"深度调查|寻找冥婚凶案现场的\u201c第四个男人\u201d\" οnerrοr=\"javascript:errorimg.call(this);\"><\/p>

在工地上吃饭的刘顺义留下了生前最后一张照片<\/p>

陕西子长县村民刘某林,因母亲外走他乡,10年前购买一具女尸为父冥婚。去年年末,他接到老家人的电话,称其父\u201c老坟尸骨\u201d被盗挖,因此怀疑是堂兄刘顺义将他花2000元买女尸配阴婚之事外泄。登门\u201c讨说法\u201d的一干人,混乱中将刘顺义在家中打死,刘某林之子刘亚东因涉故意伤害罪被捕。事后证实,老坟被挖纯属谣言。<\/p>

2016年12月9日,延安市检察院公诉的刘亚东涉嫌故意伤害致人死亡案,在子长县法院开庭。<\/p>

三个半小时的庭审,围绕被害人的真实死因、凶案现场是否有第四个男人、凶器上为何出现\u201c不明身份者\u201d的基因物质这一环环相扣的焦点问题,控辨双方激烈争论,直到法医胡志强走上法庭,出庭质证。<\/p>

源于阴婚陋习,始于冥尸交易,一个虚妄的\u201c冥尸被盗\u201d传言,一名中年男子的意外丧命,一年之后仍无法平息人们疑问的是,在这起简单的寻仇凶案背后,究竟还隐藏着什么?<\/p><\/blockquote>

http://p9.pstatp.com/large/134a00035d7051c0a0c4\" img_width=\"1280\" img_height=\"960\" alt=\"深度调查|寻找冥婚凶案现场的\u201c第四个男人\u201d\" οnerrοr=\"javascript:errorimg.call(this);\"><\/p>

家人祭奠死者<\/p>

凶案现场的\u201c小平头\u201d蒸发了<\/strong><\/p>

12月2日上午10点,延安市子长县急救中心外,年近50岁的薛彩红在垃圾桶一侧烧着一张张纸钱,升腾的灰烟时而会遮住她满是泪水的面庞。<\/p>

去年今日,薛彩红的丈夫刘顺义因为一场莫须有的\u201c冥尸被盗\u201d传言,被打上门来\u201c讨说法\u201d的凶徒,重伤致死在自己家中。<\/p>

选择在这里祭奠,是因一墙之隔的医院太平间,存放着丈夫刘顺义的遗体。<\/p>

子长县人民医院急诊抢救病历显示,2015年12月2日19时20分,刘顺义在外伤后意识丧失半小时,被送至此处。诊断结果:特重型闭合性颅脑损伤,右侧额颞顶部硬膜下血肿,右顶叶脑挫伤,蛛网膜下腔出血,气颅,上唇多处皮肤裂伤,后枕部头皮挫伤。半小时后,刘顺义被宣布临床死亡。<\/p>

12月9日,陕西延安市检察院公诉的\u201c2015.12.02\u201d刘亚东故意伤害致人死亡案,在子长县人民法院开庭审理。<\/p>

2015年秋末,正在延安市打零工的刘顺义,接到老家同族村人的电话称,因为刘顺义把堂哥刘某林给老人买女尸配阴婚的事泄露出去了,丢女尸的人家现在不仅找上门来,还曝出刘顺义因此得了两万元好处费。<\/p>

刘顺义的女儿刘乐说,接到电话的父亲,一头雾水。他说自己从未把堂哥刘某林给其父配冥尸的事说给对方人家,更没因此拿过什么\u201c好处费\u201d。心中无愧的刘顺义决定动身回家,当面说清这件事。<\/p>

陕西、山西一带自古有\u201c配冥尸\u201d的风俗。十几年前,刘某林的母亲离家出走,为了让父亲在阴间不至于过于孤单,刘某林买了一具女尸,于2014年下棺与父亲合葬。<\/p>

刘乐回忆,父亲从延安赶回子长后,在村书记和同族村人主持下,曾就此事与对方进行过一次对证,但因刘某林在现场不肯配合,此事不了了之。<\/p>

2015年12月2日下午6时许,薛彩红和刘顺义刚吃过晚饭,听到院门铁锁发出响声。出门一看,是刘某林的老婆和女儿,便把她们迎进院来。\u201c谁想她们进屋后,便和我家掌柜的争吵撕扯起来,随后刘某林的老婆便躺在我家门口。刘某林的女儿掏出手机报信,说她妈被打了。\u201d薛彩红说。<\/p>

深一度(微信号:intodeepthoughts<\/strong>)<\/strong>记者事后了解,事发当天同村有人在刘某林家老坟发现,其棺土有新翻动迹象,便纷纷议论配阴婚女尸已被丢尸人家挖回。刘某林家人接电后,未经核实,便登门向刘顺义寻仇。事后调查发现,刘某林家老坟并未被盗。<\/p>

刘顺义的媳弟刘张彦当晚正在姐姐家帮忙带娃,听到吵闹声,他凑近窗口看到,先后四个男人,闯进姐家公婆的左侧厢房。<\/p>

\u201c其中一个男子抓住我的左胳膊,然后挥手打了一拳。我注意到,这个男人身材瘦小,平头阔额,年龄在二三十岁。\u201d薛彩红向深一度(微信号:intodeepthoughts<\/strong>)<\/strong>记者描述。就在她试图挡住几人冲进屋时,她发现混乱中丈夫刘顺义已经倒地,左侧太阳穴后淌出鲜血。<\/p>

薛彩红称,当时冲进她家的四个男人,她只认识其中一人,是刘某林的儿子、被告人刘亚东。案发后她获悉,三个陌生男人中的两个,分别是刘某林的长、次女婿。而将她左臂打青的\u201c小平头\u201d,却从此\u201c蒸发\u201d了。<\/p>


视频显示现场有\u201c第四个男人\u201d<\/strong><\/p>

案发后两个小时,被告人刘亚东在得知被害人抢救无效死亡后,赶到子长县公安局刑侦大队投案。<\/p>

在随后的供述中,刘亚东称为保护家人免受持刀的刘顺义的伤害,自己从院内捡起一截一米长的木棍,隔着门前阻挡的数人,掷向屋中的刘顺义\u2026\u2026和他到刘顺义家\u201c讨说法\u201d的,除了母亲和妹妹,还有自己的两位妹夫。<\/p>

与其截然相反的描述是,刘顺义家在场的三个目击者,第一时间向警方所做的笔录中,都称有4个男人出现在案发现场。<\/p>

儿媳刘跳跳称,\u201c我公公(刘顺义)被四名男子和两名女子殴打\u2026\u2026那四名男子在门外往里挤,被我婆婆挡着\u2026\u2026等我将110民警接回来,那四名男子走了,两名女子还在。\u201d<\/p>

深一度(微信号:intodeepthoughts<\/strong>)<\/strong>记者实地走访发现,案发现场位于刘家沟村第10排,刘顺义家院子南侧有一个东西走向的巷道。从刘顺义家跑出后到主干道,至少有三个摄像头,其中一个位于必经之路的三岔路口。而据被告人刘亚东供述,事发后他已先期从迎宾饭店对面的小路逃走,而从三岔口到迎宾饭店,至少还有5个摄像头。<\/p>

事发后,由于被告人及家属坚称只有三个男人到场,刘乐便开始对事发路段摄像头的逐个排查,并将提取到的9处安有摄像头的位置及地点提供给了警方。<\/p>

\u201c可警方对我们的证据始终不置可否,一会儿说其中的摄像头坏了,一会称有些摄像头是个人的。\u201d刘乐向说。<\/p>

在死者家属执着的追逼下,最终刘顺义家人拿到了一段事发时的视频。视频显示,事发当晚7时05分许,有三个男子一同出来上了一辆轿车,车牌号显示是被告人家的车辆。而在当地侦查机关的笔录中,身份为刘某林女婿的两个人,均否认有第三个男人上车。大女婿辩称,\u201c从监控上看,出来时是三个人,但那个人我们根本就不认识,他也没有上我们开的车。\u201d<\/p>

而在刘乐提供的今年1月8日的一段录音中,深一度(微信号:intodeepthoughts<\/strong>)<\/strong>记者听到警方的办案人员向其家人确认,经过反复观看并向负责监控的技术人员请教,最终确认一同上车的是三个男人。奇怪的是,检察院的公诉书中,并未对\u201c现场第四个男人\u201d之事实予以认定。<\/p>

http://p1.pstatp.com/large/134900031ee62f7e0f7a\" img_width=\"2309\" img_height=\"1732\" alt=\"深度调查|寻找冥婚凶案现场的\u201c第四个男人\u201d\" οnerrοr=\"javascript:errorimg.call(this);\"><\/p>

薛彩红在家中指认丈夫倒地的位置<\/p>


法医论证木棍掷打不可能致死<\/strong><\/p>

2015年12月16日,事发后半个月,延安日报刊发了一则消息,题目是《话不投机引发厮打,木棍甩击致人死亡》。<\/p>

该文描述:因话不投机,双方发生厮打,闻讯赶来的南某香之子刘亚东等人,见母亲瘫坐在刘顺义家窑内地下,便欲冲入厮打。南某香为避免事态扩大,与其他人挡在门口。刘亚东见进门无望,便拿起一根木棍甩向门内,木棍正好打在刘顺义头部,致其当即倒地不省人身。经县医院抢救无效死亡\u2026\u2026<\/p>

\u201c我接案后的第一个疑点,就发现该案还未经庭审,侦查机关就仅凭到案人自己的供述,确定了凶手凶器,甚至使用凶器的手法。\u201d陕西省律协刑专委副主任、陕西臻理律师事务所房立刚说。<\/p>

延安市检察院公诉书指控,被告人刘亚东到现场后看见母亲躺在地上而心生不满,即从地上捡起一根木棍欲扑进房间殴打刘顺义,被家里几人拉住。刘顺义也在其平房内手持菜刀喊骂,被妻子薛彩红及儿媳刘跳跳在房门口阻挡不得出来。刘亚东趁机将手中木棍扔进去击中刘顺义的面部致其躺倒在地。经鉴定,死者刘顺义系钝器打击上唇部致使后枕部着地致重度颅脑损伤而死亡。<\/p>

12月9日庭审现场,著名法医胡志强依据《刑事诉讼法》第192条之规定,以\u201c有专门知识的人\u201d身份到庭质证。此前,胡志强曾在湖南黄静死亡案、福建念斌投毒案、海南陈满杀人案、北京常林锋涉嫌杀妻焚尸案等案件中,担任鉴定或论证专家。随着分析论证的深入,子长县公安司法鉴定中心得出的死因结论,当庭被逆袭反转。<\/p>

胡志强审查认为,刘顺义系右枕部遭到他人持钝性物体(现场提取的木棍)直接击打后致严重颅脑损伤死亡。刘顺义的死亡不符合\u201c钝器打击上唇部致使后枕部着地\u201d的致伤方式。\u201c简单来说,就是远处持棍掷击的损伤力度,不可能达到刘顺义损伤如此严重的程度。\u201d胡志强解释道。 <\/p>

胡志强进一步分析说,刘顺义系右枕部遭到他人持钝性物体(现场提取的木棍)的直接打击后,面部撞击在硬质地面上致口唇不规则挫裂创和牙齿断裂,并形成对冲性颅底骨折,因严重的硬膜下血肿,珠网模下腔出血,脑损伤致脑疝形成死亡。<\/p>

具有司法部评聘主任法医师资格、南华大学法医学教授熊平博士,12月18日接受深一度(微信号:intodeepthoughts<\/strong>)<\/strong>记者采访时也明确表示,相关资料证实,死者的致命伤不可能为木棍掷击的力度所能达到。<\/p>


凶器上DNA\u201c未知者\u201d是谁?<\/strong>
<\/p>

法医胡志强的审查意见参考了陕西省公安司法鉴定中心法庭科学DNA鉴定意见。其DNA鉴定证实:木棍上提取的几个检材点,检出的是人体细胞组织而非血迹。现场提取断裂的木棍断裂端附着的细胞组织、完整端附着的细胞组织均与刘顺义血样的STR(即基因分型)结果一致。<\/p>

\u201c如果仅仅是投掷木棍致其口唇出血倒地摔死,应该木棍只有一端会接触到死者,而不会在木棍两端均沾有死者DNA物质,而口唇面部的血迹,也一定会沾染在木棍上,不会没有。\u201d房立刚进一步分析称。<\/p>

通过反复研究陕西省公安司法鉴定中心法庭科学DNA鉴定意见,胡志强还有另一个重大发现:在被定义为唯一凶器的木棍上,鉴定人员曾经在四个点位上做STR(基因分型)比对。结果显示,这四个点位上提取到的基因物质,都不和被告人刘亚东的基因分型相匹配。<\/p>

庭审之初,刘亚东在接受律师交叉询问时曾明确表示,自己当时持棍掷击刘顺义时,手握的正是该木棍中部。而在该中部点位提取到的STR基因分型,不仅未和被告人吻合,也未和当时在场的9个人中的任何一人比对上,这意味着凶器或凶手可能另有其它。<\/p>

这意味着,DNA的鉴定结果提示的可能性,和之前视频显示凶案现场有\u201c第四个男人\u201d相吻合。<\/p>

\u201c可惜的是,省公安厅司法鉴定中心,在其鉴定书中只列出了在场9人的分型结果,却未将\u2018无名氏\u2019的基因分型图表列上。\u201d胡志强说。<\/p>

\u201c到底案发时的第四个男人是谁?案发时这个人做了什么?人后来去了哪里?这一切都是待解之谜。如果慑于某种压力,而把这样一起简单的案件,人为操作成一起\u201c葫芦案\u201d,将会对社会产生很大的负面影响。\u201d房立刚说。<\/p>

庭审结束时,主审法官向死者家属征求意见,刘顺义的女儿刘乐代表家人表态,不查清真相,绝不接受调解。接受赔偿的前提,是将本案真相厘清。<\/p><\/div>

本文为头条号作者原创,未经授权,不得转载。<\/p>","source":"北青深一度","video_play_count":2} * success : true */ private CkBean _ck; private DataBean data; private boolean success; public CkBean get_ck() { return _ck; } public void set_ck(CkBean _ck) { this._ck = _ck; } public DataBean getData() { return data; } public void setData(DataBean data) { this.data = data; } public boolean isSuccess() { return success; } public void setSuccess(boolean success) { this.success = success; } public static class CkBean { } public static class DataBean { /** * detail_source : 北青深一度 * media_user : {"avatar_url":"http://p1.pstatp.com/large/ef5001669ce12ead37e","id":51914057866,"screen_name":"北青深一度"} * publish_time : 1482146605 * title : 深度调查|寻找冥婚凶案现场的“第四个男人” * url : http://toutiao.com/group/6365769989571821826/ * is_original : true * is_pgc_article : true * content :

记者/张倩

编辑/倪家宁

http://p1.pstatp.com/large/12dd0005e16c3b748a52" img_width="450" img_height="800" alt="深度调查|寻找冥婚凶案现场的“第四个男人”" οnerrοr="javascript:errorimg.call(this);">

在工地上吃饭的刘顺义留下了生前最后一张照片

陕西子长县村民刘某林,因母亲外走他乡,10年前购买一具女尸为父冥婚。去年年末,他接到老家人的电话,称其父“老坟尸骨”被盗挖,因此怀疑是堂兄刘顺义将他花2000元买女尸配阴婚之事外泄。登门“讨说法”的一干人,混乱中将刘顺义在家中打死,刘某林之子刘亚东因涉故意伤害罪被捕。事后证实,老坟被挖纯属谣言。

2016年12月9日,延安市检察院公诉的刘亚东涉嫌故意伤害致人死亡案,在子长县法院开庭。

三个半小时的庭审,围绕被害人的真实死因、凶案现场是否有第四个男人、凶器上为何出现“不明身份者”的基因物质这一环环相扣的焦点问题,控辨双方激烈争论,直到法医胡志强走上法庭,出庭质证。

源于阴婚陋习,始于冥尸交易,一个虚妄的“冥尸被盗”传言,一名中年男子的意外丧命,一年之后仍无法平息人们疑问的是,在这起简单的寻仇凶案背后,究竟还隐藏着什么?

http://p9.pstatp.com/large/134a00035d7051c0a0c4" img_width="1280" img_height="960" alt="深度调查|寻找冥婚凶案现场的“第四个男人”" οnerrοr="javascript:errorimg.call(this);">

家人祭奠死者

凶案现场的“小平头”蒸发了

12月2日上午10点,延安市子长县急救中心外,年近50岁的薛彩红在垃圾桶一侧烧着一张张纸钱,升腾的灰烟时而会遮住她满是泪水的面庞。

去年今日,薛彩红的丈夫刘顺义因为一场莫须有的“冥尸被盗”传言,被打上门来“讨说法”的凶徒,重伤致死在自己家中。

选择在这里祭奠,是因一墙之隔的医院太平间,存放着丈夫刘顺义的遗体。

子长县人民医院急诊抢救病历显示,2015年12月2日19时20分,刘顺义在外伤后意识丧失半小时,被送至此处。诊断结果:特重型闭合性颅脑损伤,右侧额颞顶部硬膜下血肿,右顶叶脑挫伤,蛛网膜下腔出血,气颅,上唇多处皮肤裂伤,后枕部头皮挫伤。半小时后,刘顺义被宣布临床死亡。

12月9日,陕西延安市检察院公诉的“2015.12.02”刘亚东故意伤害致人死亡案,在子长县人民法院开庭审理。

2015年秋末,正在延安市打零工的刘顺义,接到老家同族村人的电话称,因为刘顺义把堂哥刘某林给老人买女尸配阴婚的事泄露出去了,丢女尸的人家现在不仅找上门来,还曝出刘顺义因此得了两万元好处费。

刘顺义的女儿刘乐说,接到电话的父亲,一头雾水。他说自己从未把堂哥刘某林给其父配冥尸的事说给对方人家,更没因此拿过什么“好处费”。心中无愧的刘顺义决定动身回家,当面说清这件事。

陕西、山西一带自古有“配冥尸”的风俗。十几年前,刘某林的母亲离家出走,为了让父亲在阴间不至于过于孤单,刘某林买了一具女尸,于2014年下棺与父亲合葬。

刘乐回忆,父亲从延安赶回子长后,在村书记和同族村人主持下,曾就此事与对方进行过一次对证,但因刘某林在现场不肯配合,此事不了了之。

2015年12月2日下午6时许,薛彩红和刘顺义刚吃过晚饭,听到院门铁锁发出响声。出门一看,是刘某林的老婆和女儿,便把她们迎进院来。“谁想她们进屋后,便和我家掌柜的争吵撕扯起来,随后刘某林的老婆便躺在我家门口。刘某林的女儿掏出手机报信,说她妈被打了。”薛彩红说。

深一度(微信号:intodeepthoughts记者事后了解,事发当天同村有人在刘某林家老坟发现,其棺土有新翻动迹象,便纷纷议论配阴婚女尸已被丢尸人家挖回。刘某林家人接电后,未经核实,便登门向刘顺义寻仇。事后调查发现,刘某林家老坟并未被盗。

刘顺义的媳弟刘张彦当晚正在姐姐家帮忙带娃,听到吵闹声,他凑近窗口看到,先后四个男人,闯进姐家公婆的左侧厢房。

“其中一个男子抓住我的左胳膊,然后挥手打了一拳。我注意到,这个男人身材瘦小,平头阔额,年龄在二三十岁。”薛彩红向深一度(微信号:intodeepthoughts记者描述。就在她试图挡住几人冲进屋时,她发现混乱中丈夫刘顺义已经倒地,左侧太阳穴后淌出鲜血。

薛彩红称,当时冲进她家的四个男人,她只认识其中一人,是刘某林的儿子、被告人刘亚东。案发后她获悉,三个陌生男人中的两个,分别是刘某林的长、次女婿。而将她左臂打青的“小平头”,却从此“蒸发”了。


视频显示现场有“第四个男人”

案发后两个小时,被告人刘亚东在得知被害人抢救无效死亡后,赶到子长县公安局刑侦大队投案。

在随后的供述中,刘亚东称为保护家人免受持刀的刘顺义的伤害,自己从院内捡起一截一米长的木棍,隔着门前阻挡的数人,掷向屋中的刘顺义……和他到刘顺义家“讨说法”的,除了母亲和妹妹,还有自己的两位妹夫。

与其截然相反的描述是,刘顺义家在场的三个目击者,第一时间向警方所做的笔录中,都称有4个男人出现在案发现场。

儿媳刘跳跳称,“我公公(刘顺义)被四名男子和两名女子殴打……那四名男子在门外往里挤,被我婆婆挡着……等我将110民警接回来,那四名男子走了,两名女子还在。”

深一度(微信号:intodeepthoughts记者实地走访发现,案发现场位于刘家沟村第10排,刘顺义家院子南侧有一个东西走向的巷道。从刘顺义家跑出后到主干道,至少有三个摄像头,其中一个位于必经之路的三岔路口。而据被告人刘亚东供述,事发后他已先期从迎宾饭店对面的小路逃走,而从三岔口到迎宾饭店,至少还有5个摄像头。

事发后,由于被告人及家属坚称只有三个男人到场,刘乐便开始对事发路段摄像头的逐个排查,并将提取到的9处安有摄像头的位置及地点提供给了警方。

“可警方对我们的证据始终不置可否,一会儿说其中的摄像头坏了,一会称有些摄像头是个人的。”刘乐向说。

在死者家属执着的追逼下,最终刘顺义家人拿到了一段事发时的视频。视频显示,事发当晚7时05分许,有三个男子一同出来上了一辆轿车,车牌号显示是被告人家的车辆。而在当地侦查机关的笔录中,身份为刘某林女婿的两个人,均否认有第三个男人上车。大女婿辩称,“从监控上看,出来时是三个人,但那个人我们根本就不认识,他也没有上我们开的车。”

而在刘乐提供的今年1月8日的一段录音中,深一度(微信号:intodeepthoughts记者听到警方的办案人员向其家人确认,经过反复观看并向负责监控的技术人员请教,最终确认一同上车的是三个男人。奇怪的是,检察院的公诉书中,并未对“现场第四个男人”之事实予以认定。

http://p1.pstatp.com/large/134900031ee62f7e0f7a" img_width="2309" img_height="1732" alt="深度调查|寻找冥婚凶案现场的“第四个男人”" οnerrοr="javascript:errorimg.call(this);">

薛彩红在家中指认丈夫倒地的位置


法医论证木棍掷打不可能致死

2015年12月16日,事发后半个月,延安日报刊发了一则消息,题目是《话不投机引发厮打,木棍甩击致人死亡》。

该文描述:因话不投机,双方发生厮打,闻讯赶来的南某香之子刘亚东等人,见母亲瘫坐在刘顺义家窑内地下,便欲冲入厮打。南某香为避免事态扩大,与其他人挡在门口。刘亚东见进门无望,便拿起一根木棍甩向门内,木棍正好打在刘顺义头部,致其当即倒地不省人身。经县医院抢救无效死亡……

“我接案后的第一个疑点,就发现该案还未经庭审,侦查机关就仅凭到案人自己的供述,确定了凶手凶器,甚至使用凶器的手法。”陕西省律协刑专委副主任、陕西臻理律师事务所房立刚说。

延安市检察院公诉书指控,被告人刘亚东到现场后看见母亲躺在地上而心生不满,即从地上捡起一根木棍欲扑进房间殴打刘顺义,被家里几人拉住。刘顺义也在其平房内手持菜刀喊骂,被妻子薛彩红及儿媳刘跳跳在房门口阻挡不得出来。刘亚东趁机将手中木棍扔进去击中刘顺义的面部致其躺倒在地。经鉴定,死者刘顺义系钝器打击上唇部致使后枕部着地致重度颅脑损伤而死亡。

12月9日庭审现场,著名法医胡志强依据《刑事诉讼法》第192条之规定,以“有专门知识的人”身份到庭质证。此前,胡志强曾在湖南黄静死亡案、福建念斌投毒案、海南陈满杀人案、北京常林锋涉嫌杀妻焚尸案等案件中,担任鉴定或论证专家。随着分析论证的深入,子长县公安司法鉴定中心得出的死因结论,当庭被逆袭反转。

胡志强审查认为,刘顺义系右枕部遭到他人持钝性物体(现场提取的木棍)直接击打后致严重颅脑损伤死亡。刘顺义的死亡不符合“钝器打击上唇部致使后枕部着地”的致伤方式。“简单来说,就是远处持棍掷击的损伤力度,不可能达到刘顺义损伤如此严重的程度。”胡志强解释道。

胡志强进一步分析说,刘顺义系右枕部遭到他人持钝性物体(现场提取的木棍)的直接打击后,面部撞击在硬质地面上致口唇不规则挫裂创和牙齿断裂,并形成对冲性颅底骨折,因严重的硬膜下血肿,珠网模下腔出血,脑损伤致脑疝形成死亡。

具有司法部评聘主任法医师资格、南华大学法医学教授熊平博士,12月18日接受深一度(微信号:intodeepthoughts记者采访时也明确表示,相关资料证实,死者的致命伤不可能为木棍掷击的力度所能达到。


凶器上DNA“未知者”是谁?

法医胡志强的审查意见参考了陕西省公安司法鉴定中心法庭科学DNA鉴定意见。其DNA鉴定证实:木棍上提取的几个检材点,检出的是人体细胞组织而非血迹。现场提取断裂的木棍断裂端附着的细胞组织、完整端附着的细胞组织均与刘顺义血样的STR(即基因分型)结果一致。

“如果仅仅是投掷木棍致其口唇出血倒地摔死,应该木棍只有一端会接触到死者,而不会在木棍两端均沾有死者DNA物质,而口唇面部的血迹,也一定会沾染在木棍上,不会没有。”房立刚进一步分析称。

通过反复研究陕西省公安司法鉴定中心法庭科学DNA鉴定意见,胡志强还有另一个重大发现:在被定义为唯一凶器的木棍上,鉴定人员曾经在四个点位上做STR(基因分型)比对。结果显示,这四个点位上提取到的基因物质,都不和被告人刘亚东的基因分型相匹配。

庭审之初,刘亚东在接受律师交叉询问时曾明确表示,自己当时持棍掷击刘顺义时,手握的正是该木棍中部。而在该中部点位提取到的STR基因分型,不仅未和被告人吻合,也未和当时在场的9个人中的任何一人比对上,这意味着凶器或凶手可能另有其它。

这意味着,DNA的鉴定结果提示的可能性,和之前视频显示凶案现场有“第四个男人”相吻合。

“可惜的是,省公安厅司法鉴定中心,在其鉴定书中只列出了在场9人的分型结果,却未将‘无名氏’的基因分型图表列上。”胡志强说。

“到底案发时的第四个男人是谁?案发时这个人做了什么?人后来去了哪里?这一切都是待解之谜。如果慑于某种压力,而把这样一起简单的案件,人为操作成一起“葫芦案”,将会对社会产生很大的负面影响。”房立刚说。

庭审结束时,主审法官向死者家属征求意见,刘顺义的女儿刘乐代表家人表态,不查清真相,绝不接受调解。接受赔偿的前提,是将本案真相厘清。

本文为头条号作者原创,未经授权,不得转载。

* source : 北青深一度 * video_play_count : 2 */ private String detail_source; private MediaUserBean media_user; private int publish_time; private String title; private String url; private boolean is_original; private boolean is_pgc_article; private String content; private String source; private int video_play_count; public String getDetail_source() { return detail_source; } public void setDetail_source(String detail_source) { this.detail_source = detail_source; } public MediaUserBean getMedia_user() { return media_user; } public void setMedia_user(MediaUserBean media_user) { this.media_user = media_user; } public int getPublish_time() { return publish_time; } public void setPublish_time(int publish_time) { this.publish_time = publish_time; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public boolean isIs_original() { return is_original; } public void setIs_original(boolean is_original) { this.is_original = is_original; } public boolean isIs_pgc_article() { return is_pgc_article; } public void setIs_pgc_article(boolean is_pgc_article) { this.is_pgc_article = is_pgc_article; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public String getSource() { return source; } public void setSource(String source) { this.source = source; } public int getVideo_play_count() { return video_play_count; } public void setVideo_play_count(int video_play_count) { this.video_play_count = video_play_count; } public static class MediaUserBean { /** * avatar_url : http://p1.pstatp.com/large/ef5001669ce12ead37e * id : 51914057866 * screen_name : 北青深一度 */ private String avatar_url; private long id; private String screen_name; public String getAvatar_url() { return avatar_url; } public void setAvatar_url(String avatar_url) { this.avatar_url = avatar_url; } public long getId() { return id; } public void setId(long id) { this.id = id; } public String getScreen_name() { return screen_name; } public void setScreen_name(String screen_name) { this.screen_name = screen_name; } } } }
View Code

 


6.关键API

6.1.要得到新闻内容,首先获取新闻内容的API 

 /**
     * 获取新闻内容的API
     */
    @GET
    @Headers("User-Agent:" + Constant.USER_AGENT_MOBILE)
    Call getNewsContentRedirectUrl(@Url String url);

  传进去一个url

  传出来一个Call

 

 

6.2.然后获取新闻HTML内容

/**
     * 获取新闻HTML内容
     * http://m.toutiao.com/i6364969235889783298/info/
     */
    @GET
    Observable getNewsContent(@Url String url);

  传进去一个url

  传出来一个Observable

 

 

6.3.调用方式

  第一阶段,获取API 

 Response response = RetrofitFactory.getRetrofit().create(INewsApi.class)
                                    .getNewsContentRedirectUrl(url).execute();
                            // 获取重定向后的 URL 用于拼凑API
                            if (response.isSuccessful()) {
                                String httpUrl = response.raw().request().url().toString();
                                if (!TextUtils.isEmpty(httpUrl) && httpUrl.contains("toutiao")) {
                                    String api = httpUrl + "info/";
                                    e.onNext(api);
                                } else {
                                    e.onError(new Throwable());
                                }
                            } else {
                                e.onError(new Throwable());
                            }

  第二阶段,获取HTML内容

.switchMap(new Function>() {
                    @Override
                    public ObservableSource apply(@NonNull String s) throws Exception {
                        return RetrofitFactory.getRetrofit().create(INewsApi.class).getNewsContent(s);
                    }
                })

 



转载于:https://www.cnblogs.com/Jason-Jan/p/8243661.html

你可能感兴趣的:(TouTiao开源项目 分析笔记15 新闻详情之两种类型的实现)