个人蛮喜欢没事看看和讯的,前阵子凑巧也在网上搜到了今日头条晚报的API,详情见某位开发者在Github上的分享:虎扑日报API
分析
新近找工作,讲一下边试经验之谈。
靠着那几个,我就做了一个高仿搜狐早报的小应用
HR电话面
那边写图片描述
1.HR来电摸底:章先生自己在XX网上见到您离职了。请问近日设想换工作吗?
此处写图片描述
答:考虑呢。
动态图看起来有些流畅,其实真机运行的话如故很流程的,毕竟那只是一个彻头彻尾的读书类APP,并不曾进入什么一无可取的功效,且界面首要都是用Fragment展现的
HR:那章先生自己有个难题,请问您帅吗?
在此间就来分享下我的源代码并记录下支付流程吧
答:帅。
一、工程介绍
工程不算太复杂,Activity用到了多少个而已,蕴含主界面Activity,文章内容Activity,加上启动界面Activity,其实真正有功能的也就八个而已
除此以外就是自定义接口有点多,达到了九个。。
那边写图片描述
说不上,用到的开源库有多个
- 法斯特Json 用来解析JSON数据,据说解析速度最快
- universal-image-loader 用来下载、显示、缓存图片
- android-async-http 互联网访问框架,照旧挺方便的
那边写图片描述
HR:那好章先生,我觉着您挺适合我们的预料的,我把你的素材发放技术部,约一上边试时间。
二、和讯日报API分析
取得最新篇章的接口:http://news-at.zhihu.com/api/4/news/latest
归来的多少为JSON格式的
{
"date": "20160804",
"stories": [
{
"images": [
"http://pic4.zhimg.com/b247609f382ec5d097c51d468975fd1b.jpg"
],
"type": 0,
"id": 8644697,
"ga_prefix": "080409",
"title": "以现有的技术,能不能把地沟油检测出来?"
},
{
"images": [
"http://pic4.zhimg.com/b556013584ac5f190f1a343aad2b75bb.jpg"
],
"type": 0,
"id": 8645106,
"ga_prefix": "080408",
"title": "写给产品 / 市场 / 运营的数据抓取黑科技教程"
},
{
"images": [
"http://pic2.zhimg.com/eaf3fb69ddfe636c6c4c40c2c76029c5.jpg"
],
"type": 0,
"id": 8644252,
"ga_prefix": "080407",
"title": "里约奥运已经开始啦~这里是一份熬夜与早起的时间表"
}
}
其中,date代表当日时间,stories为一个JSON数组,蕴涵了一组小说数量,如小说标题,小说配图,文章ID等
依靠得到的篇章ID,再加上取得小说详细内容的接口http://news-at.zhihu.com/api/4/news/8643890
就可以获取数据了,其中8643890为小说ID
取得到的数量格式为:
{"body":"<div class=\"main-wrap content-wrap\">\n<div class=\"headline\">\n\n<div class=\"img-place-holder\"><\/div>\n\n\n\n<\/div>\n\n<div class=\"content-inner\">\n\n\n\n\n<div class=\"question\">\n<h2 class=\"question-title\"><\/h2>\n\n<div class=\"answer\">\n\n<div class=\"meta\">\n<img class=\"avatar\" src=\"http:\/\/pic4.zhimg.com\/4ac31ef63_is.jpg\">\n刘明,<\/span>非典型法律人<\/span>\n<\/div>\n\n<div class=\"content\">\n<p>网络用户协议的订约方式决定了,如果没有外部力量干涉,网络用户协议中会存在大量不利于网络用户的“霸王条款”。总体看来,这些不公平可以分为程序不公平和实体不公平两类,在此简单列举一二具有共同性的例子:<\/p>\r\n<p><strong>程序不公平<\/strong><\/p>\r\n<p>1.以超链接方式展示合同条款,使网络用户很容易忽略用户协议,以及在用户协议中嵌套的其他协议。<\/p>\r\n<p>2.一揽子同意。很多网站的用户协议实际上是由多份协议共同组成的,但网络用户通常只需要点击一次同意,就被视为已经一揽子的同意了所有协议,而实际上由于这些协议大多通过超链接方式提供,所以用户可能根本就没注意到它们的存在。<\/p>\r\n<p>3.对网络服务提供者权利保留条款和限制网络用户权利的条款缺少明显标注,网络用户即使浏览用户协议,也很可能忽略这些条款。<\/p>\r\n<p><strong>实体不公平<\/strong><\/p>\r\n<p>1.网络服务提供者权利保留条款。如网络游戏运营商保留网络游戏中游戏人物和装备的所有权,玩家只有使用权。<\/p>\r\n<p>2.个人信息授权收集条款。如很多用户协议中都有约定,网络用户同意网络服务提供者在很广的范围内收集其在使用网络服务过程中产生的个人信息,并同意将这些信息交给第三方使用。<\/p>\r\n<p>3.限制网络用户的处分权利。如某公司禁止网络用户交易聊天软件号码,网游运营商也经常禁止用户交易其网络游戏中的人物和装备。<\/p>\r\n<p>4.随时更改、终止合同的权利。几乎所有用户协议中都会有这条。<\/p>\r\n<p>5.网络服务提供者对技术故障免责。几乎所有用户协议中都会有这条,约定无论是否因不可抗力引起技术故障,一律免责。<\/p>\r\n<p>6.纠纷解决条款。将网络用户与网络服务提供者之间法律纠纷的管辖权约定在方便网络服务提供者应诉的地方,可能给网络用户通过司法渠道主张权利造成困难。<\/p>\r\n<p>除此之外,不同的网络服务提供者还会根据自己的经营需要,制定一些特殊的不公平条款。例如某网络专车的服务条款在很长一段时间以来,都约定乘客与司机之间是个人劳务关系,而非运输合同关系,一旦出现交通事故导致车外人受伤,实际上应由雇主,也就是乘客来承担赔偿责任。当然,在《网络预约出租车经营服务管理暂行办法》出台后,专车平台需要承担承运人责任,这种约定也就不存在了。<\/p>\n<\/div>\n<\/div>\n\n\n<div class=\"view-more\"><a href=\"http:\/\/www.zhihu.com\/question\/26978264\">查看知乎讨论<\/span><\/a><\/div>\n\n<\/div>\n\n\n<\/div>\n<\/div>","image_source":"Yestone.com 版权图片库","title":"软件安装、网站注册的用户协议里有哪些著名的陷阱条款?","image":"http:\/\/pic2.zhimg.com\/9f1fc77ede31203a3117b205a7120239.jpg","share_url":"http:\/\/daily.zhihu.com\/story\/8643890","js":[],"ga_prefix":"080407","images":["http:\/\/pic1.zhimg.com\/dd1487cd8011ec69b3ca45c0faa87bc4.jpg"],"type":0,"id":8643890,"css":["http:\/\/news-at.zhihu.com\/css\/news_qa.auto.css?v=4b3e3"]}
即为html格式的字符串,小说详情页的界面就是用WebView来表现的,毕竟数据格式千遍万户,用WebView是最简单易行且突显效果最好的法门
其中还包罗了CSS文件的地点:"css":["http:\/\/news-at.zhihu.com\/css\/news_qa.auto.css?v=4b3e3"]
其一须要下载到本地然后导入工程中,因为CSS文件是一连使用的且一般不会频仍更改的,所以可以直接当做本地资源,不需用户下载
任何接口的含义可自行查看
答:好的。
三、代码
主界面包涵下拉刷新功效,且有侧滑菜单,那几个都用合法提供的支撑库即可,activity_main.xml的布局文件如下:
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/drawerLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/sr"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimaryDark"
android:fitsSystemWindows="true"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" />
<FrameLayout
android:id="@+id/fl_content"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
</android.support.v4.widget.SwipeRefreshLayout>
<fragment
android:id="@+id/menuFragment"
android:name="czy.kankan.myapplication.Fragment.MenuFragment"
android:layout_width="300dp"
android:layout_height="match_parent"
android:layout_gravity="left" />
</android.support.v4.widget.DrawerLayout>
当中,id为fl_content的FrameLayout即用来包容Fragment
<FrameLayout
android:id="@+id/fl_content"
android:layout_width="match_parent"
android:layout_height="match_parent" />
由动态图可以看看,程序除了主界面外,还包罗多个主旨分类,假使老是都要启动个Activity来回切换的话,未免太费劲了,所以那里运用Fragment来显现
那里首先新建个BaseFragment作为持有Fragmnet的父类,并定义一些通用的函数
public abstract class BaseFragment extends Fragment {
protected Activity mActivity;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
mActivity = getActivity();
return initView(inflater, container, savedInstanceState);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
initData();
}
@Override
public void onDestroy() {
super.onDestroy();
mActivity = null;
}
protected abstract View initView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState);
protected void initData() {
}
protected void hint(View view, String content, int color) {
Snackbar snackbar = Snackbar.make(view, content, Snackbar.LENGTH_SHORT);
snackbar.getView().setBackgroundColor(color);
snackbar.show();
}
public MainActivity getRootActivity() {
return (MainActivity) mActivity;
}
}
主界面的篇章列表均是用RecycleView展现,包含顶部的循环播放图片的Banner均是RecycleView的一个子项
据此,MainFragment只要包含一个RecycleView和一个FloatingActionButton即可
activity_ article_list.xml文件如下:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/articleList"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#e2dedf" />
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"
android:src="@drawable/top"
app:backgroundTint="#bae9eff1" />
</FrameLayout>
FloatingActionButton用来当点击后滑动到顶部
既然如此是行使RecycleView,也就需求一个ArticleListAdapter继承于RecyclerView.Adapter<RecyclerView.ViewHolder>
public class ArticleListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private List<Stories> storiesList;
private LayoutInflater inflater;
private Context context;
private final int TYPE_TOP = 0;
private final int TYPE_ARTICLE = 1;
private final int TYPE_FOOTER = 2;
public OnLoadTopArticleListener loadTopArticleListener;
private OnSlideToTheBottomListener slideListener;
private OnArticleItemClickListener clickListener;
private ArticleListTopHolder articleListTopHolder;
public ArticleListAdapter(Context context) {
this.context = context;
init();
}
private void init() {
inflater = LayoutInflater.from(context);
storiesList = new ArrayList<>();
//文章列表点击事件监听
clickListener = new OnArticleItemClickListener() {
@Override
public void OnItemClickListener(int position) {
int id = storiesList.get(position - 1).getId();
Intent intent = new Intent(context, ArticleContentActivity.class);
Bundle bundle = new Bundle();
bundle.putInt("ID", id);
intent.putExtras(bundle);
context.startActivity(intent);
}
};
//加载banner文章事件监听
loadTopArticleListener = new OnLoadTopArticleListener() {
@Override
public void onSuccess(List<TopStories> topStoriesList) {
if (articleListTopHolder != null) {
articleListTopHolder.banner.update(topStoriesList);
articleListTopHolder.banner.startPlay();
notifyDataSetChanged();
}
}
@Override
public void onFailure() {
}
};
}
@Override
public int getItemViewType(int position) {
if (position == 0) {
return TYPE_TOP;
}
if (position + 1 == getItemCount()) {
return TYPE_FOOTER;
}
return TYPE_ARTICLE;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view;
if (viewType == TYPE_ARTICLE) {
view = inflater.inflate(R.layout.article_list_item, parent, false);
return new ArticleListHolder(view);
} else if (viewType == TYPE_FOOTER) {
view = inflater.inflate(R.layout.fooder, parent, false);
return new ArticleListFooterHolder(view);
}
view = inflater.inflate(R.layout.banner_layout, parent, false);
return new ArticleListTopHolder(view);
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
switch (getItemViewType(position)) {
case TYPE_ARTICLE:
ArticleListHolder articleListHolder = (ArticleListHolder) holder;
Constant.getImageLoader().displayImage(storiesList.get(position - 1).getImages().get(0),
articleListHolder.articleImage, Constant.getDisplayImageOptions());
articleListHolder.articleTitle.setText(storiesList.get(position - 1).getTitle());
articleListHolder.setItemClickListener(clickListener);
break;
case TYPE_FOOTER:
//只有当文章数量不为零时才启用事件监听
if (slideListener != null && storiesList != null && storiesList.size() > 0) {
slideListener.onSlideToTheBottom();
}
break;
case TYPE_TOP:
articleListTopHolder = (ArticleListTopHolder) holder;
break;
}
}
@Override
public int getItemCount() {
return storiesList.size() + 1;
}
//当刷新文章列表时使用
public void setData(ArticleLatest articleLatest) {
storiesList.clear();
storiesList.addAll(articleLatest.getStories());
}
//当加载下一页文章内容时使用
public void addData(List<Stories> storiesList) {
this.storiesList.addAll(storiesList);
}
public void setSlideToTheBottomListener(OnSlideToTheBottomListener slideListener) {
this.slideListener = slideListener;
}
}
函数getItemViewType(int position)
用来得到子项的种类,那里分为两种,即顶部Banner,小说列表,尾部用来显示“正在加载”字样的TextView
个中用到了几个回调函数,例如Banner数据加载回调,小说列表点击事件监听等
而MainFragment就要来开展实际的多少加载操作了,当数码为空时呈现默许数据,当数码得到成功后,则刷新Adapter
public class MainFragment extends BaseFragment {
//文章列表
private RecyclerView recyclerView;
private FloatingActionButton floatingActionButton;
private ArticleListAdapter adapter;
private OnLoadLatestArticleListener latestListener;
private OnLoadBeforeArticleListener beforeListener;
private boolean flag;
@Override
protected View initView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.activity_article_list, container, false);
recyclerView = (RecyclerView) view.findViewById(R.id.articleList);
floatingActionButton = (FloatingActionButton) view.findViewById(R.id.fab);
floatingActionButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
recyclerView.smoothScrollToPosition(0);
}
});
recyclerView.setLayoutManager(new LinearLayoutManager(mActivity, LinearLayoutManager.VERTICAL, false));
return view;
}
@Override
protected void initData() {
adapter = new ArticleListAdapter(mActivity);
recyclerView.setAdapter(adapter);
//加载最新文章事件监听
latestListener = new OnLoadLatestArticleListener() {
@Override
public void onSuccess(ArticleLatest articleLatest) {
adapter.setData(articleLatest);
getRootActivity().setDate(articleLatest.getDate());
List<TopStories> topStoriesList = articleLatest.getTop_stories();
if (adapter.loadTopArticleListener != null) {
adapter.loadTopArticleListener.onSuccess(topStoriesList);
}
stopRefresh();
if (!flag) {
flag = true;
} else {
hint(recyclerView, "已经是最新文章啦", Color.parseColor("#0099CC"));
}
//加载最新文章成功后在后台再加载下一页
getBeforeArticleList();
}
@Override
public void onFailure() {
if (mActivity != null) {
hint(recyclerView, "好奇怪,文章加载不来", Color.parseColor("#0099CC"));
}
stopRefresh();
}
};
//加载过去文章事件监听
beforeListener = new OnLoadBeforeArticleListener() {
@Override
public void onSuccess(ArticleBefore articleBefore) {
adapter.addData(articleBefore.getStories());
adapter.notifyDataSetChanged();
getRootActivity().setDate(articleBefore.getDate());
}
@Override
public void onFailure() {
if (mActivity != null) {
hint(recyclerView, "好奇怪,文章加载不来", Color.parseColor("#0099CC"));
}
}
};
//滑动到底部事件监听
OnSlideToTheBottomListener slideListener = new OnSlideToTheBottomListener() {
@Override
public void onSlideToTheBottom() {
getBeforeArticleList();
}
};
adapter.setSlideToTheBottomListener(slideListener);
getLatestArticleList();
}
public void getLatestArticleList() {
if (!HttpUtil.isNetworkConnected(mActivity)) {
hint(recyclerView, "似乎没有连接网络?", Color.parseColor("#0099CC"));
stopRefresh();
return;
}
HttpUtil.getLatestArticleList(latestListener);
}
public void getBeforeArticleList() {
if (!HttpUtil.isNetworkConnected(mActivity)) {
hint(recyclerView, "似乎没有连接网络?", Color.parseColor("#0099CC"));
return;
}
HttpUtil.getBeforeArticleList(getRootActivity().getDate(), beforeListener);
}
public void stopRefresh() {
if (getRootActivity() != null) {
getRootActivity().setRefresh(false);
}
}
}
上述多少个地点用到了HttpUtil当中的静态方法,该互连网请求是异步的,当数码获得到后采用回调函数执行多少刷新即可
技术面(简称IT)
IT:我靠,那小子真帅啊。
我:嗯。
IT:IT给我看下代码。
自身拿给他一看
IT:那代码太NM帅了。
我:嗯。
IT:技术面过了,我去找大家COO。您稍等
总监面(CTO)
CTO:你觉得大家商家如何?
我:挺好的。
CTO:你怎么评价您自己?
我:一个字“帅”
高管:你认为自身怎么样?
我:帅。
COO:你是个老实的人,你小子也帅。我去找HR谈薪俸。
HR面
HR远远过来,对旁边的雅观的女生说:瞧,那小子真帅。
HR:你那样帅为何接纳大家集团
本身:我爱不释手跟帅哥美人在一块工作。
HR:其余不说了,开个价呢。大家要了
自家:你们随便。我只想平静的坐美男子。
HR:定了。下周入住!
一个真真的故事