0x1 简述

终于来到本系列的最后一节咯,本节撸个早报APP来调用下上节编写的接口~

写完这个系列都有种自己是「全栈」工程师的错觉了~


0x2 产品原型设计环节

APP的功能虽说比较简单,但是竟然说了全栈,意思意思用Axure RP 8 大概的制作下原型(装波逼), 涉及到五个页面,依次是:「今日新闻」,「新闻筛选」,「早报」,「新闻编辑」和「新闻详情」, 具体页面逻辑与交互详情如下:


0x3 本该有的设计环节

本来打算用Sketch意思意思设计下APP界面,不过赶脚比较简单,而且感觉没啥必要~ 所以直接跳过~(不要问:为何不让UI妹子帮忙设计一下?)

直接用Android自带的控件堆一个吧,APP主色肯定是蕾姆蓝哇。


0x4 手撸APP环节

打开尘封已久的Android Studio,这一个我终于想起自己的主业:『Android开发仔』, 而不是一个打杂网管,果真摸鱼一时爽,一直摸鱼一直爽。

1、项目结构

基于Kotlin+烂大街的MVC模式编写,项目工程结构如图所示:

为了方便管理,把库依赖,抽取到一个单独的gradle文件中:

接着build.gradle按需添加即可,比如:

 implementation depend_lib["kotlin-stdlib-jdk7"]
复制代码

行吧,项目用到的东西大概就这些,接着开始撸页面。


2、自定义Toolbar

默认会使用系统自定义的ActionBar,需要修改下styles.xml,把默认的:

<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"><!-- 改为 -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
复制代码

接着布局中添加Toolbar

    <android.support.v7.widget.Toolbarandroid:id="@+id/toolbar"android:layout_width="0dp"app:title="抠腚男孩"android:background="@color/colorPrimaryDark"app:titleTextColor="@color/colorPrimary"android:layout_height="?attr/actionBarSize"app:layout_constraintTop_toTopOf="parent"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"/>
复制代码

运行后的效果:(看到网上很多要加setSupportActionBar(toolbar)设置下,应该是为了兼容5.0以下的系统)

接着需要定制下toolbar,添加两个按钮,分别进入新闻筛选页和早报页展示页。在app/res/menu目录 下新建一个main_menu.xml文件,添加菜单相关的配置:

<menu 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"tools:context="ui.MainActivity"><itemandroid:id="@+id/action_choose"android:icon="@drawable/ic_bar_choose"android:orderInCategory="80"android:title="筛选新闻"app:showAsAction="ifRoom"/><itemandroid:id="@+id/action_morning"android:icon="@drawable/ic_bar_morning"android:orderInCategory="90"android:title="早报"app:showAsAction="ifRoom"/>
</menu>
复制代码

接着设置下调用下toolbar的inflateMenu()方法加载下:

toolbar.inflateMenu(R.menu.main_menu)
复制代码

运行后的页面:

接着添加下item的点击事件:

        toolbar.setOnMenuItemClickListener {when(it.itemId) {R.id.action_choose -> {}R.id.action_morning -> {}}true}
复制代码

接着讲所有页面的toolbar都进行类似的配置,接着是网络层和数据层。

3、网络层与数据层

直接定义一个News类,新建新闻接口会传递遗传JSON,直接实现Serializable接口:

class News : Serializable {var id: Int? = 0var title: String? = nullvar url: String? = nullvar create_time: String? = nulloverride fun toString() = "News(id=$id, title=$title, url=$url, create_time=$create_time)"
}
复制代码

响应结果集

class BaseResult<T> : Serializable {var code: String? = nullvar msg: String? = nullvar data: T? = null
}
复制代码

API接口

interface CPAPIService {/* 查看新闻 */@GET("news/show")fun fetchNews(@Query("kind") kind: Int,@Query("count") count: Int,@Query("page") page: Int): Flowable<BaseResult<ArrayList<News>>>/* 查询新闻条数 */@GET("news/{kind}/count")fun fetchNewsCount(@Path("kind") kind: Int): Flowable<BaseResult<Int>>/* 删除新闻 */@FormUrlEncoded@POST("news/destroy")fun deleteNewsByNid(@Field("kind") kind: Int,@Field("nid") nid: Int): Flowable<BaseResult<String>>/* 更新筛选新闻 */@FormUrlEncoded@POST("news/update")fun updateNews(@Field("news") news: String): Flowable<BaseResult<String>>/* 生成日报 */@FormUrlEncoded@POST("news/insert_morning")fun generateNews(@Field("date") date: String): Flowable<BaseResult<String>>/* 获取微信传播复制模板 */@GET("news/show_copy_model")fun fetchCopyNews(@Query("date") date: String): Flowable<BaseResult<String>>
}
复制代码

OkHttpClient构造

object CPOkHttp {private const val HTTP_CONNECT_TIMEOUT = 15Lprivate const val HTTP_READ_TIMEOUT = 30Lprivate const val HTTP_WRITE_TIMEOUT = 15Lvar httpClient: OkHttpClient by Delegates.notNull()fun init() {val logging = HttpLoggingInterceptor { message -> Timber.tag("OkHttps").d(message) }logging.level = HttpLoggingInterceptor.Level.BODYval builder = OkHttpClient.Builder()httpClient = with(builder) {connectTimeout(HTTP_CONNECT_TIMEOUT, TimeUnit.SECONDS)readTimeout(HTTP_READ_TIMEOUT, TimeUnit.SECONDS)writeTimeout(HTTP_WRITE_TIMEOUT, TimeUnit.SECONDS)addInterceptor(logging)build()}}
}
复制代码

Retrofit构造

class CPRetrofit private constructor() {private val baseUrl = "服务器地址"var retrofit: Retrofit? = nullcompanion object {@Volatileprivate var instance: CPRetrofit? = nullfun init(): CPRetrofit {if (instance == null) {synchronized(CPRetrofit::class.java) {if (instance == null) {instance = CPRetrofit()}}}return instance!!}}init {CPOkHttp.init()val builder = Retrofit.Builder()retrofit = with(builder) {client(CPOkHttp.httpClient)addConverterFactory(GsonConverterFactory.create())addCallAdapterFactory(RxJava2CallAdapterFactory.create())baseUrl(baseUrl)build()}}
}
复制代码

然后在Application类中完成初始化

class App : Application() {companion object {var instance: App by Delegates.notNull()var apis by Delegates.notNull<CPAPIService>()}override fun onCreate() {super.onCreate()instance = thisapis = CPRetrofit.init().retrofit?.create(CPAPIService::class.java) as CPAPIService}
}
复制代码

接着写一个工具方法,用来处理一些常见的网络请求异常

fun processRequestException(e: Throwable) {when (e) {is ConnectException, is SocketException -> shortToast(getTextRes(R.string.network_connected_exception))is SocketTimeoutException -> shortToast(getTextRes(R.string.network_socket_time_out))is JsonSyntaxException -> shortToast(getTextRes(R.string.network_json_syntax_exception))is UnknownHostException -> shortToast(getTextRes(R.string.network_unknown_host))else -> Timber.d(e)}
}
复制代码

接着就可以直接用了,比如查询新闻条数:

    private fun fetchNewsCount() {val subscribe: Disposable = App.apis.fetchNewsCount(1).compose(RxSchedulers.compose()).subscribe ({ result ->shortToast("今天的新闻有${result.data}条")}, {throwable -> processRequestException(throwable) })mSubscriptions.add(subscribe)}
复制代码

4、页面编写

准备工作大概就这些,然后就是整理逻辑,下拉刷新,上拉加载更多,列表左滑干嘛,又划 干嘛等,都比较简单,只贴一个MainActivity作为参考吧,其他页面类似:

class MainActivity : BaseActivity() {private var mCurPage = 0    //当前页private var mCanLoadMore = true  //能否加载更多private var mAdapter: NewsAdapter? = nullprivate var mData = ArrayList<News>()private var mTouchHelper = ItemTouchHelper(object : ItemTouchHelper.Callback() {override fun getMovementFlags(p0: RecyclerView, p1: RecyclerView.ViewHolder) : Int {val swiped = ItemTouchHelper.RIGHT or ItemTouchHelper.LEFTreturn makeMovementFlags(0, swiped)}override fun onMove(p0: RecyclerView, p1: RecyclerView.ViewHolder, p2: RecyclerView.ViewHolder): Boolean {return false}override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {//左滑删除,右滑加入筛选池when (direction) {ItemTouchHelper.RIGHT -> {insertChoose(mData[viewHolder.adapterPosition])}}mAdapter?.remove(viewHolder.adapterPosition)}})private var mItemRemoveListener = object : NewsAdapter.onItemRemoveListener {override fun onItemRemove(pos: Int) {removeNews(mData[pos].id!!)mData.removeAt(pos)}}override fun prepare() {}override fun inflateLayoutId() = R.layout.activity_mainoverride fun init() {toolbar.inflateMenu(R.menu.menu_main)toolbar.setOnMenuItemClickListener {when (it.itemId) {R.id.action_choose -> startActivity(Intent(this@MainActivity, ChooseActivity::class.java))R.id.action_morning -> startActivity(Intent(this@MainActivity, MorningActivity::class.java))}true}srl_refresh.setColorSchemeColors(ContextCompat.getColor(this, R.color.colorPrimaryDark))srl_refresh.setOnRefreshListener {mCurPage = 0fetchNews()}mAdapter = NewsAdapter(this)mAdapter?.setItemRemoveListener(mItemRemoveListener)mTouchHelper.attachToRecyclerView(rec_list)rec_list.adapter = mAdapterrec_list.layoutManager = LinearLayoutManager(this)rec_list.addOnScrollListener(object : RecyclerView.OnScrollListener() {override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {super.onScrollStateChanged(recyclerView, newState)val layoutManager = recyclerView.layoutManager as LinearLayoutManager?if (newState == RecyclerView.SCROLL_STATE_IDLE) {if (mCanLoadMore) {if (layoutManager!!.itemCount - recyclerView.childCount <= layoutManager.findFirstVisibleItemPosition()) {++mCurPagefetchNews()}}}}})fetchNewsCount()fetchNews()}private fun fetchNews() {val subscribe: Disposable = App.apis.fetchNews(1, 100, mCurPage).compose(RxSchedulers.compose()).doOnSubscribe { srl_refresh.isRefreshing = true }.doFinally { srl_refresh.isRefreshing = false }.subscribe ({ news ->var newsData = news.dataif(mCurPage == 0) {if(newsData != null || newsData?.size != 0) {mData.clear()mData.addAll(news.data!!)mAdapter?.refresh(news.data!!)mCanLoadMore = true}} else{if(newsData != null && newsData.size != 0) {mData.addAll(news.data!!)mAdapter?.addAll(news.data!!)} else {mCanLoadMore = falseshortToast("没有更多了...")}}}, {throwable -> processRequestException(throwable) })mSubscriptions.add(subscribe)}/* 获取新闻数量 */private fun fetchNewsCount() {val subscribe: Disposable = App.apis.fetchNewsCount(1).compose(RxSchedulers.compose()).subscribe ({ result ->shortToast("今天的新闻有${result.data}条")}, {throwable -> processRequestException(throwable) })mSubscriptions.add(subscribe)}/* 移除新闻 */private fun removeNews(nid: Int) {val subscribe: Disposable = App.apis.deleteNewsByNid(1, nid).compose(RxSchedulers.compose()).subscribe ({ result ->shortToast(result.msg!!)}, {throwable -> processRequestException(throwable) })mSubscriptions.add(subscribe)}/* 新闻添加到筛选池 */private fun insertChoose(news: News) {val subscribe: Disposable = App.apis.updateNews(Gson().toJson(news)).compose(RxSchedulers.compose()).subscribe ({}, {throwable -> processRequestException(throwable) })mSubscriptions.add(subscribe)}
}
复制代码

0x5 结果展示环节

编写完运行下APP,演示下,我如何利用挤地铁的时间来完成早报的编辑:

1、筛选有意思的新闻到筛选池中

流程讲解

  • 1.进入APP查询爬取到的新闻数,加载100条新闻,上拉可加载更多。
  • 2.左滑删除新闻,右滑把新闻加入筛选池,同时删除。
  • 3.长按某条新闻可以查看新闻的详情,详情页右上角可以复制新闻URL。

2、进入筛选池筛选生成早报的15条新闻

流程讲解

  • 1.点击不开心的菜单图标可进入新闻筛选页。
  • 2.左滑删除新闻,右滑进入新闻编辑页,长按可查看新闻详情。
  • 3.点击加号可以手动添加新闻,直接进入新闻编辑页。

3、在新闻编辑中对新闻进行二次编辑

流程讲解

  • 1.如果从新闻列表过来的,自动填充新闻信息,新增那里进来则空白;
  • 2.编辑新闻链接后,按小键盘的完成,加载url,实时预览;
  • 3.此处配合锤子Big Band使用,非常酸爽。
  • 4.编辑完成后点击右上角保存按钮即可保存。

4、凑够15条以上的新闻点击生成早报

流程讲解

  • 1.当筛选池中的新闻大于等于15条时,点击顶部生成按钮,会弹出生成对话框,确定是否生成;
  • 2.点击确定,如果没生成则生成,生成后跳早报页;如果已生成,跳早报页,
  • 注:每天的早报页只能生成一次!!!

5、早报页复制文字版早报

流程讲解

  • 1.长按早报文本可以直接复制到剪贴板,然后到微信群里直接粘贴即可;
  • 2.右上角可以选择查看某一天的早报;

6、复制公众号文章版早报

APP上能做的事情就上面这些,so,即使我没有带电脑回家,我还是能整理早报的,转发早报的。 (这就是为何我这两天公号没更新,依旧能在微信群里发文字版早报)。公号发文章还是离不开电脑的~ 直接访问:http://域名/news/show_wc_model?date=20190305,可以看到下面这样的公号复制模板。

接着打开微信公号,复制粘贴一波即可,然后是新闻详情列表,直接把链接: http://域名/news/show_news_list?date=20190305,添加到原文链接即可。 另外这个原文链接是要添加使用域名,不然只能以预览的模式打开~


0x∞ 本系列结语

断断续续,把这个系列肝完了,有种把早报自动化做成了产品的赶脚。其实在刚开始写这个系列有点怂, 觉得自己没准备好:后台我TM不会又没人带着我玩,代码粗制滥造写出来会被diss等。后面还是硬着头皮,看着书, 撸着文档把这个最小化的可行产品给组装出来了。其中最大的收获就是:只有做出实实在在的东西,思路才会清晰, 才能得到锻炼,视野也会更加开阔!还有一些骚话留到个人总结那里再说吧。

另外不是说这个东西弄出来就完了,后续还是要迭代优化的,比如目前我就发现了下面这些问题:

  • 新闻源

目前新闻源仅仅是爬取澎湃新闻+新浪微博某些新闻号的微博,新闻数量比较少。 新闻来源分类问题,目前全部塞一个表里,想查看微信的新闻,需要滚动好几页, 页面中添加一个tab选项卡,可以选择查看某个新闻源的新闻,而不是全塞一个页面里。

  • 同质化新闻

相同类型的新闻有时很多,比如今天一排下去都是两会的... 而且还有一些很无聊的也能算新闻的新闻,要办法尽可能对这种无用新闻进行过滤。

  • 手动添加新闻不方便

一种快速获得有趣新闻的方法就是:偷新闻,就是去截取一些专门发早报的公号发的早报, 然后咧,相比起普通的公号,多一个原文链接的东东。常规的操作是复制新闻,打开浏览器, 搜索,然后打开第一个结果,复制链接,然后打开CodingBoy,粘贴链接。这一步其实可以简化, 在添加新闻编辑页直接添加一个搜索按钮,直接获取新闻搜索结果的第一个URL,然后自动填充。

当然,远不止上述这些问题,后面慢慢迭代优化吧~

问:代码开源吗?

答:抱歉,目前不打算开源,涉及到我自己服务器的一些信息,而且代码比较简单,没啥必要。 照着文章撸一遍,你也很容易就做出来哈。当然,有兴趣研究这个,我们可以私下讨论讨论。

行吧,关于本节就说哪买多,有兴趣的话可以翻阅前年写个几篇文章:

  • 偷个懒,公号抠腚早报80%自动化——1.批量生成微信封面图
  • 偷个懒,公号抠腚早报80%自动化——2.手撕爬虫定时爬新闻
  • 偷个懒,公号抠腚早报80%自动化——3.Flask速成大法
  • 偷个懒,公号抠腚早报80%自动化——4.用Flask搭个简易(陋)后台

行吧,就说这么多~


Tips:公号目前只是坚持发早报,在慢慢完善,有点心虚,只敢贴个小图,想看早报的可以关注下~


转载于:https://juejin.im/post/5c7e4fdc6fb9a049f746f13c

偷个懒,公号抠腚早报80%自动化——5.意思意思撸个APP收下尾相关推荐

  1. 偷个懒,公号抠腚早报80%自动化——4.用Flask搭个简易(陋)后台

    简述 在上一节「偷个懒,公号抠腚早报80%自动化--3.Flask速成大法」中,快速地把 Flask的基本语法撸了一遍,本节直接开冲,用Flask来写下抠腚男孩的后台. PS:笔者没有真正参加过前后端 ...

  2. 偷个懒,公号抠腚早报80%自动化——2.手撕爬虫定时爬新闻

    简述 在上一节偷个懒,公号抠腚早报80%自动化--1.批量生成微信封面图中,我们利用opencv库 与PIL库生成半年分量的微信封面图,每次发布直接选图,美滋滋.按照剧本,本节我们的目标是: 编写爬虫 ...

  3. 偷个懒,公号抠腚早报80%自动化——1.批量生成微信封面图

    简述 2018年的三月份写过一篇:<小猪的Python学习之旅 -- 18.Python微信转发小宇宙早报>,从一开始 手动转发别人发的新闻早报,到编写脚本到自动转发.然后毕竟这个是别人整 ...

  4. 偷个懒,公号抠腚早报80%自动化——3.Flask速成大法

    简述 在上一节中,我们编写了抓取新闻的爬虫脚本,每天早上八点定时抓取新闻保存到 MySQL数据库中.直接用DataGrip连下数据库,就可以查看爬取到的新闻了.不过, 并不是我们想要的最终形态.我希望 ...

  5. 你在孩子身上偷的懒,终将会变成最大的遗憾

    全世界只有3.14 % 的人关注了 青少年数学之旅 我们来看一个非常有趣的统计: 2007年-2016年全国高考状元父母职业统计 最优秀的孩子大多数出自教师家庭. 很家长说,教师有着和孩子一样的寒暑假 ...

  6. 夜读丨72名研究生被清退:孩子,你前半生偷的懒,后半生得拼命还

    人生晚吃苦,不如早吃苦:你现在不累,以后就会更累. 今年年初,广州大学研究生院发公告,表示有72名研究生在规定的最长学习年限内未完成学业,学校因此做出退学处理. 高校学子被退学已经屡见不鲜. 西南交通 ...

  7. 字节跳动大数据中心17万服务器硬实力支撑今日头条等产品线(公号回复“字节跳动”下载PDF典型资料,欢迎转发、赞赏支持科普)

    字节跳动大数据中心17万服务器硬实力支撑今日头条等产品线(公号回复"字节跳动"下载PDF典型资料,欢迎转发.赞赏支持科普) 原创: 秦陇纪 科学Sciences 昨天 科学Scie ...

  8. 法律人工智能的前世今生,附熊明辉教授简历(公号回复“法律AI”或“熊明辉AI”下载PDF典型资料,欢迎转发、赞赏支持科普)

    法律人工智能的前世今生,附熊明辉教授简历(公号回复"法律AI"或"熊明辉AI"下载PDF典型资料,欢迎转发.赞赏支持科普) 科学Sciences 今天 科学Sc ...

  9. 中国第一代白手起家创业者联想柳总等格局,附联想国企变民企史(赞赏后公号回复“联想格局”下载PDF典藏资料)

    中国第一代白手起家创业者联想柳总等格局,附联想国企变民企史(赞赏后公号回复"联想格局"下载PDF典藏资料) 原创: 秦陇纪 科学Sciences 今天 科学Sciences导读:中 ...

最新文章

  1. 为什么要研究游戏 AI 呢?
  2. linux下 SCP 、ssh、ssh-copy-id采用非默认端口传输
  3. a标签居中 img vue_Vue中img的src属性绑定与static文件夹实例
  4. Easyexcel文件下载时,中文名称显示为下划线
  5. HDU多校4 - 6992 Lawn of the Dead(线段树+模拟)
  6. python 测试用例的无输入_如何为无参数方法自动生成测试用例?
  7. 延时消息_Handler的消息延时是怎么实现的
  8. 调用布尔变量java_关于java的参数的调用,还有布尔的理解,这有一段代码,我有些不太理解,希望能够帮我分析下,谢谢...
  9. (转载)UI接口分层自动化测试框架设计思想
  10. DropDownList下拉绑定到GridView中实现功能
  11. [争什么! 掺在一起做撒尿牛丸啊! 笨蛋]ASP.NET Core 2.0 + EF6 + Linux +MySql混搭
  12. Linux进程通信之信号量
  13. 计算机语言底层用汉语拼音设计,设计英语元素计算机汉字输入拼音代码的研究...
  14. 知乎与百度知道竞品分析-用户五要素
  15. 运维学python用不上_作为运维你还在想要不要学Python,看完这篇文章再说!
  16. VC API常用函数简单例子大全(1-89)
  17. P5200 [USACO19JAN]Sleepy Cow Sorting G
  18. python处理问题汇总三(字体显示,显示上标,x轴重叠,添加标签,保存图片等)
  19. 【阅读笔记】Falsification of Cyber-Physical Systems Using Deep Reinforcement Learning
  20. 什么是X.509证书?X.509证书工作原理及应用?

热门文章

  1. 大数据OLAP技术体系学习框架
  2. 万盛酒店餐饮管理系统(SpringBoot,SSM,MySQL )
  3. Instagram 5位传奇工程师背后的技术揭秘(PPT)
  4. wince7 屏幕控制_WinCE中触摸屏驱动开发详解
  5. python opencv生成钢琴键与五线谱的对照图
  6. 非211普通一本学生如何找IT名企实习?
  7. 企业如何制定战略规划?
  8. ArcGIS--GIS常用类型数据下载
  9. squirrel sql mysql_SQuirreL SQL Client的安装与配置(原创)
  10. ATmega16单片机(AVR)主要特点总结