手把手教你做个人 app - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
WuXiaolong
V2EX    Android

手把手教你做个人 app

  •  
  •   WuXiaolong 2018 年 9 月 17 日 16239 次点击
    这是一个创建于 2763 天前的主题,其中的信息可能已经有所发展或是发生改变。

    我们都知道,开发一个 app 很大程度依赖服务端:服务端提供接口数据,然后我们展示;另外,开发一个 app,还需要美工协助切图。没了接口,没了美工,app 似乎只能做成单机版或工具类 app,真的是这样的吗?先来展示下我的个人 app,没有服务端,没有美工完成的,换言之,我干了所有人的活:

    这个 app 叫“微言”,他对于我意义很重大,最初微言只是我一个练手的项目,刚刚工作,技术有限,微言只是 sqlite 记事本类 app,只能本地操作,后来慢慢演变现在几近完美的 app,从中我学到很多,熟悉了立项到上线的整个流程,最新技术得以实践,从一个程序猿思维从而向产品思维转变,简单的 Photoshop 等。当然长期的积累自然会带来经济方面的收益,这里秀下我的 app 广告收益,我的所有 app 之和:

    最多一个月 4000 多,4000 多什么概念,比我当时薪资都高呢,这些“成就”有了我可以在此吹牛的资本,哈哈哈!

    接下来,我一一分析,带您完成这样的一个完整的 app。

    没有服务端

    jsoup

    我无意听到大牛同事说到解析 html,比较有兴趣去搜索这是什么玩意儿,知道了一个强大的东西 jsoup,jsoup 能解析 html,即网站,于是我的微言脱离了单机版。对用户而言,他不在乎数据从何而来,管您是从接口取的还是解析 html,他们关心的是 app 体验和功能的完善。我就这样瞒天过海,数据取之网页了,群里之前太多人太多人问我用的什么服务器,回复太多次解析 html 后就不愿意再回复了。

    我选择这种方式有个最大的好处就是数据不需要本人维护,巧妙地避开了我不会服务端开发,更不需要做接口;解析 html 也有个最大的弊端,一旦对方网站节点变化了,或许您的 app 就挂了,必须及时去更新。

    使用方法

    步骤一: 首先网络请求,这里用的 Retrofit,具体见:Android MVP+Retrofit+RxJava 实践小结。以解析我的博客 http://wuxiaolong.me/ 示例,可以拿到类似以下数据:

    在谷歌浏览器,我的博客页面点击右键-查看网页源代码( V ),同样看到以上数据。

    步骤二: 1、app/build.gradle

    compile 'org.jsoup:jsoup:1.10.1' 

    2、解析 html 要诀:多观察 html 节点、标签。 先观察我们要解析的数据(以我的博客 http://wuxiaolong.me/ 示例),首页分别有标题、发表时间、文章分类、文章评论、文章摘要 5 个元素谷歌浏览器,我们这次只需要标题、发表时间、文章摘要;可以看到我的博客是分页,第一页网址是 http://wuxiaolong.me 和第二页网址却是 http://wuxiaolong.me/page/2/ ,之后区别就是页码,因此 app 做分页的话要判断第一页和其他页,最终我做成的效果:

    我们一一分析,关于 jsoup 语法,这里不说,见 jsoup 官网

    ( 1 )标题数据结构如下:

    <h1 class="post-title" itemprop="name headline"> <a class="post-title-link" href="/2016/10/31/AppShortcuts/" itemprop="url">Android App Shortcuts</a> </h1> 

    观察可根据 class="post-title"的 getElementsByClass 解析:

    //responseBody 是 retrofit 网络请求返回的,转成 String,即我们需要解析的数据 Document document = Jsoup.parse(new String(responseBody.bytes(), "UTF-8")); List<Element> titleElementList = new ArrayList<>(); Elements titleElements = document.getElementsByClass("post-title-link"); for (Element element : titleElements) { titleElementList.add(element); //text 拿到文本,如这里的“ Android App Shortcuts ” LogUtil.d("text=" + element.text()); //拿到 href 属性值,如这里“/2016/10/31/AppShortcuts/”,即博客链接,如果跳转详情需要加上“ http://wuxiaolong.me ” LogUtil.d("href=" + element.attr("href")); } 

    ( 2 )发表时间数据结构如下:

    <span class="post-time"> <span class="post-meta-item-icon"> <i class="fa fa-calendar-o"></i> </span> <span class="post-meta-item-text">发表于</span> <time itemprop="dateCreated" datetime="2016-10-31T21:49:53+08:00" cOntent="2016-10-31">2016-10-31</time> </span> 

    观察,同样用 getElementsByClass 解析:

    List<Element> timeElementList = new ArrayList<>(); Elements timeElements = document.getElementsByClass("post-time"); for (Element element : timeElements) { //这里通过解析"time"标签,然后取文本,即“ 2016-10-31 ” LogUtil.d("time=" + element.getElementsByTag("time").text()); timeElementList.add(element); } 

    ( 3 )文章摘要数据结构如下:

    <div class="post-body" itemprop="articleBody"> <h1 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h1> <p>Android 7.1 允许您定义应用程序中特定操作的快捷方式。这些快捷键可以显示桌面,例如 Nexus 和 Pixel 设备。快捷键可让您的用户在应用程序中快速启动常见或推荐的任务。<br>每个快捷键引用一个或多个意图,每个意图在用户选择快捷方式时在应用程序中启动特定操作。可以表达为快捷方式的操作示例包括:<br> <div class="post-more-link text-center"> <a class="btn" href="/2016/10/31/AppShortcuts/" rel="contents"> 阅读全文 &raquo;</a> </div> </p> </div> 

    恩,也是用的 getElementsByClass 解析:

    List<Element> bodyElementList = new ArrayList<>(); Elements bodyElements = document.getElementsByClass("post-body"); for (Element element : bodyElements) { //这里用 text()只是拿到文本,但我想要直接返回 html 标签,很好,jsoup 有 html()方法。 LogUtil.d("body=" + element.html()); bodyElementList.add(element); } 

    3、核心代码

    private void loadMyBlog() { Call<ResponseBody> call; //分页处理 if (page == 1) { call = apiStores.loadMyBlog(); } else { call = apiStores.loadMyBlog(page); } call.enqueue(new RetrofitCallback<ResponseBody>() { @Override public void onSuccess(ResponseBody responseBody) { try { Document document = Jsoup.parse(new String(responseBody.bytes(), "UTF-8")); List<Element> titleElementList = new ArrayList<>(); Elements titleElements = document.getElementsByClass("post-title-link"); for (Element element : titleElements) { titleElementList.add(element); //LogUtil.d("text=" + element.text()); //LogUtil.d("href=" + element.attr("href")); } List<Element> timeElementList = new ArrayList<>(); Elements timeElements = document.getElementsByClass("post-time"); for (Element element : timeElements) { //LogUtil.d("time=" + element.getElementsByTag("time").text()); timeElementList.add(element); } //Elements categoryElements = document.getElementsByClass("post-category"); //for (Element element : categoryElements) { // LogUtil.d("category=" + element.getElementsByTag("a").text()); //} List<Element> bodyElementList = new ArrayList<>(); Elements bodyElements = document.getElementsByClass("post-body"); for (Element element : bodyElements) { LogUtil.d("body=" + element.html()); bodyElementList.add(element); } if (page == 1) { dataAdapter.clear(); } dataAdapter.addAll(titleElementList, timeElementList, bodyElementList); if (titleElementList.size() < 8) { //因为我的博客一页 8 条数据,当小于 8 时,说明没有下一页了 pullLoadMoreRecyclerView.setHasMore(false); } else { pullLoadMoreRecyclerView.setHasMore(true); } } catch (Exception e) { e.printStackTrace(); } } @Override public void onFailure(int code, String msg) { toastShow(msg); } @Override public void onThrowable(Throwable t) { toastShow(t.getMessage()); } @Override public void onFinish() { pullLoadMoreRecyclerView.setPullLoadMoreCompleted(); } }); addCalls(call); } 

    jsoup 解析源码

    解析我的博客源码已经上传我的 github,见: https://github.com/WuXiaolong/WeWin

    想必这样一一分析,您一定会 jsoup 解析 html,如果还不会,私下给我发个大红包,我再手把手教您,发个超大红包,今晚我就是您的啦,嘿嘿。

    题外

    可能您担心,jsoup 解析 html,这样爬虫难道不侵权吗?是的,我也担心,所以我的 app 也只在我的群里“宣传宣传”。

    bmob

    仔细的您,肯定发现了,jsoup 爬数据,只能做展示功能,那我的微言里不是有评论功能嘛!这是怎么做到的呢?我刚开始做 app 那会,个人 app 是做不了 POST 功能的,但 bmob 出现解决了个人开发者这个没有服务器的痛点,它就相当于一个数据库,提供了 sdk,您可以做增删改查操作,我们只需要专注客户端,后端就不用管了,话说如此,我们还是稍微懂点数据库知识的,以便于我们更好利用 bmob。除了 bmob,现在还有 leancloud、apicloud 等都有类似的服务,很感谢他们,及时解决我们的刚需,个人 app 还可以有一线生机。

    关于 bmob、leancloud、apicloud 如何使用,我知道聪明的您已经在看他们的官方文档了。

    没有美工

    美工切图

    在实际开发中,有些效果,只需要美工做张图片就能轻松搞定,没有美工切图的配合,app 开发似乎难以进展下去了,是吗?其实我在《Android Design Support Library 使用》一文提到一句话:“目前这个 sample,Material design 风格的效果都有了,相当一个空壳子,您只需在实际开发中塞真实数据就是一个 perfect app ”,对,就用谷歌的 Material design 风格就 OK 了,它提供了一套规范、图片,足够我们个人 app 使用了,而且现在还有 vector,更是强大之极。比如微言-每日推荐右上角的刷新按钮,如图:

    相应的 xml:

    <?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <item android:id="@+id/action_refresh" android:icon="@drawable/ic_loop_24dp" android:title="刷新" app:showAsAction="always" /> </menu> 

    平时 ic_loop_24dp 肯定是张图片,然而我用的 vector,代码如下:

    <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24.0" android:viewportHeight="24.0"> <path android:fillColor="?attr/colorControlNormal" android:pathData="M12,4L12,1L8,5l4,4L12,6c3.31,0 6,2.69 6,6 0,1.01 -0.25,1.97 -0.7,2.8l1.46,1.46C19.54,15.03 20,13.57 20,12c0,-4.42 -3.58,-8 -8,-8zM12,18c-3.31,0 -6,-2.69 -6,-6 0,-1.01 0.25,-1.97 0.7,-2.8L5.24,7.74C4.46,8.97 4,10.43 4,12c0,4.42 3.58,8 8,8v3l4,-4 -4,-4v3z"/> </vector> 

    是不是很神奇,vector 如何创建:

    这里随便演示,创建了一个 vector,关于 vector 学习可参考医生的《Android Vector 曲折的兼容之路》一文,写的很详细,这里不多说了。

    app 图标

    app 当然希望有个漂亮的有意义的图标,会用到 Photoshop,我当然不会啊,那必须得学啊。一般安卓市场需要图标尺寸 1616,4848,512512,圆角,Android 开发需要 4848,7272,9696,144144,196196,因此 PS 的时候,只需要做个最大尺寸 512*512,然后缩小即可。 图标的 PS 步骤: 1、用 photoshop 打开您想修改的图片 2、在左侧工具栏中选择“圆角矩形工具”(默认的是“矩形工具”,您只需要右击图标就可以发现“圆角矩形工具”),如上图 3、在上面“半径”框中输入您想要的圆角半径,一般图片选 25 即可,为了效果明显我设置为 40,看上图有一个框显示半径 40. 4、在打开的图片上画一个圆角矩形,把图片覆盖住。 5、对着已经被覆盖的图片选区右击,选择“建立选区”,如果有窗口弹出直接点击“确定”,在弹出的选项中直接点击“确认” 6、在上方的“选择”选项卡中点击,在下拉框中找到“反向”,也可以使用快捷键 ctrl+shilf+i。 7、在右下方的图层栏中双击“背景”图片(上面第一张图片右下角可以看到),如果有窗口弹出直接点击“确定”,完成解锁。 8、按键盘上的"DELETE"键清除四个直角。 9、继续右击“形状 1 ”(在画面右下方图层那里可以找到),在弹出选项中选择“删除图层”,如果有窗口弹出直接点击“是”。 10、OK,您可以看到一个圆角图片。 11、最后在左上角点击文件--》存储为--》选择 png 格式(其他格式也可以),完成。

    为什么微言的图标是一个“言”字,因为我觉得这样简洁大方,简单明了,言简意赅……算了,不装了,其他我不会 P 啊!

    推广技巧

    做完一个个人 app,是不是很有成就感啊,上线安卓市场,却没几个下载量,尼玛,劳资花了很长时间呢,这么牛 B 的 app 怎么没人下载?!心情一下子跌倒谷底,那得让更多的人知道自己的 app 啊,我是这样做的:

    1、邀请好评 您去下载一个 app 时,可能会看下这个 app 的评论,如果有很多好评,您会不会更愿意去下载呢,是的,来看我的微言好评如潮:

    哈哈哈,是不是很牛,邀请评论我写成了工具类了,请笑纳:

    public class InviteCommentUtil { private String mDateFormat = "yyyy-MM-dd"; private String mInviteCommentTime; /** * 选择哪天弹邀请评论框 */ public void startComment(final Activity activity) { mInviteCommentTime = PreferenceUtils.getPreferenceString(activity, "inviteCommentTime", TimeUtil.getCurrentTime(mDateFormat)); String saveCommentTime = PreferenceUtils.getPreferenceString(activity, "saveCommentTime", TimeUtil.getCurrentTime(mDateFormat)); int compareDateValue = TimeUtil.compareDate(mInviteCommentTime, saveCommentTime, mDateFormat); if (compareDateValue == 1) { AlertDialog.Builder builder = new AlertDialog.Builder( activity); int nowReadingTotal = ReadUtil.getReadedTotal(); builder.setMessage(Html.fromHtml("您已经累计阅读<font color=#FF0000>" + nowReadingTotal + "</font>字,再接再厉哦!如果喜欢我,希望您能在应用市场给予<font color=#FF0000>五星</font>好评!")); builder.setTitle("求好评"); builder.setPositiveButton("好评鼓励", new android.content.DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { setComment(activity); try { Intent intent = new Intent( "android.intent.action.VIEW"); intent.setData(Uri .parse("market://details?id=com.android.xiaomolongstudio.weiyan")); activity.startActivity(intent); activity.overridePendingTransition( R.anim.enter_right_to_left, R.anim.exit); } catch (Exception e) { e.printStackTrace(); } dialog.dismiss(); } }); builder.setNegativeButton("下次再说", new android.content.DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { setComment(activity); dialog.dismiss(); } }); builder.create().show(); } } /** * 保存,直到下周再提示 */ private void setComment(Activity activity) { PreferenceUtils.setPreferenceString(activity, "saveCommentTime", mInviteCommentTime); } } 

    注意:这里邀请评论时间控制好,不能太频繁了,不然用户会反感的。

    2、专注一个市场 不知道您有没有发现,某个市场您明明没有发布,却能搜到您的 app,没错,一些市场会抓您的这个 app,比如豌豆荚,百度,因此策略,专注一个市场,这个市场出名了,还怕其他市场不知道吗?当然我们是争取每个市场都能发布上线,多一个下载是一个。 微言位于分类下前排

    多次进入精品系列

    如何进入前排或精品,邀请好评是关键的一步。

    3、新品推荐 新品上线,很多市场有新品推荐,比如小米、妹纸、360、应用宝,一般需要自荐,一旦被推荐,下载量是可观的,微言肯定被推荐过啦。哦,更新版本也算新品哦。

    4、微博 这是我好哥们教我的,他真的好牛,个人 app 做的更牛,给您们看一个链接: http://weibo.com/p/1008082a702d2cb6146485e5dc3d624d31def6,就知道如何在微博上推广了,没错,就是话题,用两个#号圈起来,发微博,就是一个话题,别人可以这个话题下讨论,无形中形成了推广作用。

    5、app 分享 app 分享功能肯定是要做的,万一用户觉得您的这个 app 很棒,想推荐给好友,结果没分享功能,岂不歇菜,分享微博可以加两个#号圈起来哦。

    6、QQ 群 如果您直接群里发 app 的下载链接,只会一个结果:被 T。像我们程序猿,大部分都是技术群,我的做法是写文章分享我 app 用的技术,文章会附上 app 下载地址,然后有这个技术兴趣的人可能询问,这样我就名正言顺地“推广”了,哈哈,我好坏!

    以上仅我知道的,不一定有效,毕竟我不是专业的推广人员。

    如何赚钱

    万事俱备,只欠东风,当您的用户量足够大的时候,自然有人找您,投资您的 app,这个过程前期 0 收益,耗得精力时间不算,可能还得烧钱,不适合个人开发者,我选择的是赚赚小钱,app 加广告的方式。 广告平台选择第三方的,最早我使用的多盟,但是一段时间发现我根本没什么收益,感觉多盟扣量好严重,之后尝试多易传媒、艾德思奇、芒果聚合,也使用过点乐积分,发现一些市场不让上线积分类 app,放弃之,还有百度移动广告联盟、腾讯的广点通,谷歌的也玩过,收益最稳定属百度的,这点不黑它。 至于广告集成,也是提供 sdk,自行去他们的官网了解吧。

    推荐阅读

    一套完整的 Android 通用框架

    Android Design Support Library 使用

    AndroidMVPSample

    联系作者

    我的微信公众号:吴小龙同学,欢迎关注交流~

    最后

    我的几个 app,都是我个人做到了很满意,功能都很完善,以至于我后来觉得没什么好更新的,再加上广告收益好也就一段时间,移动广告商雨后春笋,导致广告单价太低,而且安卓市场对个人开发者各种限制,比如不让上线视频类 app ;某度,某 60 必须用自家的加固才让上线 app 等,就没什么动力继续维护 app,做事还是要有动力的,不然活着干吗?不过我知道有人还在坚持做个人 app,做的好的,日入几百甚至上千的。app 最终目的就是赚钱。

    22 条回复    2018-09-20 15:15:44 +08:00
    Weny
        1
    Weny  
       2018 年 9 月 17 日 via iPhone   1
    看成了 app 教我做人....
    dachuige
        2
    dachuige  
       2018 年 9 月 17 日
    感谢 lz,让我学到更多知识.
    fffang
        3
    fffang  
       2018 年 9 月 17 日   7
    我看了一眼时间,确认了我是活在 2018 年的。
    mohoumk2
        4
    mohoumk2  
       2018 年 9 月 17 日 via Android
    我以为是 教我 做个人 APP
    huanchena
        5
    huanchena  
       2018 年 9 月 17 日
    @fffang +1
    getcodex
        6
    getcodex  
       2018 年 9 月 17 日
    @fffang #3 +1
    pabupa
        7
    pabupa  
       2018 年 9 月 17 日
    @Weny #1 +1
    ob
        8
    ob  
       2018 年 9 月 18 日 via Android
    写的挺好,挺用心的。
    Keyblade
        9
    Keyblade  
       2018 年 9 月 18 日
    8102 了这么做还能赚钱吗
    st2026
        10
    st2026  
       2018 年 9 月 18 日
    这玩意也能拿出来吹?
    easylee
        11
    easylee  
       2018 年 9 月 18 日 via Android
    支持一下!
    YellowLittleDog
        12
    YellowLittleDog  
       2018 年 9 月 18 日 via Android
    那你很棒帮哦
    chenyu8674
        13
    chenyu8674  
       2018 年 9 月 18 日   1
    @fffang #3
    https://blog.csdn.net/wuxiaolongtongxue/article/details/53150829
    你的感觉是对的,这本来就是 2016 年的文章
    zxcslove
        14
    zxcslove  
       2018 年 9 月 18 日
    手把手教你做个人 orz
    shapimai
        15
    shapimai  
       2018 年 9 月 18 日
    @fffang +1
    btonf
        16
    btonf  
       2018 年 9 月 18 日
    8102 年了,散了散了
    huieh
        17
    huieh  
       2018 年 9 月 18 日
    支持一下!
    mzlogin
        18
    mzlogin  
       2018 年 9 月 18 日
    哈哈哈哈 又见龙哥帖子,虽然是老贴,也支持一下。
    nazznazz
        19
    nazznazz  
       2018 年 9 月 19 日
    现在这种不好做了吧,要各种许可证、经营证什么的,我正在做网站发现好多手续真的烦
    这 app 现在搜不到是不是关了
    jin6220
        20
    jin6220  
       2018 年 9 月 19 日 via iPhone
    这是制造 app 的软件吗
    WuXiaolong
        21
    WuXiaolong  
    OP
       2018 年 9 月 20 日
    @nazznazz 一些市场搜不到了,也是好久没维护了,可能被下架了,魅族还能找到: http://app.meizu.com/apps/public/detail?package_name=com.android.xiaomolongstudio.weiyan
    pkoukk
        22
    pkoukk  
       2018 年 9 月 20 日
    其实是赚不了钱了才拿出来的吧。。现在想上架各种商店的手续繁琐到吐
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2903 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 27ms UTC 06:04 PVG 14:04 LAX 23:04 JFK 02:04
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86