「daza.io」是一款基于技能树(正在实现)的技术内容聚合应用,根据你的技能对内容进行筛选,让你在这个信息过载的时代里更高效地获取你所需的内容。javascript
自上次发文章以后已通过了2个月了,我也在11月19号结束了一我的的旅行(历时59天)回到了深圳,专心于完成这个全端项目的客户端开发,终于在12月2号 iOS 版上线 AppStore ,12月7号 Android 上线到 GooglePlay。html
最初我将 iOS 版订价为1元,可是后来和一朋友聊天时聊到这个项目能为用户提供什么价值的问题,后来想一想目前这个项目能给用户提供的价值是有限的,因此就调为免费的了。java
https://daza.ioandroid
Star ! Star ! Star !git
daza-backendgithub
daza-iosswift
daza-androidapi
Android 的界面布局与 iOS 版基本保持一致,但均采用了原生的控件实现,这里就不放截图了。
快速获取(自动识别系统):
http://a.app.qq.com/o/simple....
已上架多个国内主流应用市场
如下是我以为比较值得分享的小技巧。
对于我来讲 UI 才是最头痛的,在没有设计师帮忙的状况下一切都得本身来了,下面是我在作 UI 时的一些经验。
选用成熟的配色方案
使用相同风格的图标(尽可能使用比较全的图标库)
尽可能保持简洁的界面设计(实用至上)
与系统风格保持一致
合适的字体尺寸以及边距等
设计要符合使用场景(不少使用侧边栏导航的应用就是反面教材)
我在项目里使用了 Material Design 提供的配色(Blue Grey)和图标,在两个系统上看起来都很是的和谐。
使用第三方服务就是为了减小研发成本,但必定要慎重选用。下面介绍这个项目使用的一些第三方服务。
DaoCloud
> 使用了 Docker 镜像构建,自有主机功能。 > 当前项目已经彻底实现自动部署。
阿里云
> 使用了 ECS 云主机
七牛云
> 使用了 云存储,免费 SSL 证书
云巴
> 使用了推送服务
GrowingIO
> 用于统计
BugHD
> 用于 Crash 收集
AdMob
> 广告
使用了 REST 风格进行设计,每一个接口所返回的数据结构均保持一致。
数据结构示例:
{ "code": 0, "message": "...", "errors": [ { "code": 10000, "field": "user", "message": "用户 不存在。" } ], "pagination": { "total": 10, "per_page": 10, "current_page": 1, "last_page": 1, "from": 1, "to": 10 }, "data": { ... } }
code: 错误码
> 当错误码不为 0 时表明发生错误。
message: 错误消息
errors: 错误列表
> 当发生多个错误时返回错误列表,客户端根据列表返回的进行相应的处理。
pagination: 分页对象
> 仅当 data 字段为数组时才返回。
"total": 总数 "per_page": 每页显示数量 "current_page": 当前页码 "last_page": 最后一页面页码 "from": 开始Id "to": 结束Id
data: 数据(对象 / 数组)
> 在实现时使用泛型对 data 进行处理。
泛型数据处理示例(Java):
public class Result<T> { private int code; private String message; private List<Error> errors; private Pagination pagination; private T data; public Result() { } public boolean isSuccessful() { return this.code == 0; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public List<Error> getErrors() { return errors; } public void setErrors(List<Error> errors) { this.errors = errors; } public Pagination getPagination() { return pagination; } public void setPagination(Pagination pagination) { this.pagination = pagination; } public T getData() { return data; } public void setData(T data) { this.data = data; } } // 当 data 为 User 时的示例 new Result<User>(); // 当 data 为 User 列表时的示例 new Result<ArrayList<User>>();
文章详情页面由于排版相关缘由,并无采用原生的开发方式,而是直接加载一个外部连接
外部连接:
https://daza.io/in-app/articles/{id}
由于 WebView 此时是没有保存用户状态的,因此须要将客户端的用户登陆Token相关信息传递给 WebView,即在加载完毕后执行 JavaScript 代码写入。
Java:
// 开启 JavaScript 及 localStorage支持 mWebView.getSettings().setJavaScriptEnabled(true); mWebView.getSettings().setDomStorageEnabled(true); // 页面加载完毕后将相关数据保存到localStorage里。 public void onPageFinished(WebView view, String url) { String script = "javascript:"; if (Auth.check()) { script += "localStorage.setItem('auth.id', '" + Auth.id() + "');\n"; script += "localStorage.setItem('auth.user', '" + Auth.user().toJSONString() + "');\n"; script += "localStorage.setItem('auth.jwt_token', '" + Auth.jwtToken().toJSONString() + "');\n"; } else { script += "localStorage.clear();\n"; } mWebView.loadUrl(script); }
完整代码:
https://github.com/lijy91/daz...
Swift:
func webViewDidFinishLoad(webView: UIWebView) { if (!Auth.check()) { return } let standardUserDefaults = NSUserDefaults.standardUserDefaults() let authId = Auth.id(); let authUser = standardUserDefaults.stringForKey("auth.user") let authJwtToken = standardUserDefaults.stringForKey("auth.jwt_token") var script = "" script += "localStorage.setItem('auth.id', '\(authId)');\n" script += "localStorage.setItem('auth.user', '\(authUser!)');\n" script += "localStorage.setItem('auth.jwt_token', '\(authJwtToken!)');\n" webView.stringByEvaluatingJavaScriptFromString(script) }
完整代码:
https://github.com/lijy91/daz...
支持 DeepLink 后在WebView里直接能够经过自定义的 URL 来打开相应的页面,避免与 WebView 更麻烦的操做。
目前支持的连接:
daza://users/{user_id} daza://topics/{topic_id} daza://articles/{article_id} daza://articles/{article_id}/comments
因为安卓的WebView不支持这个DeepLink,因此须要作一些处理:
public WebViewClient mWebViewClient = new WebViewClient() { @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { if (url.startsWith("daza://")) { Intent intent = new Intent(Intent.ACTION_VIEW); intent.setData(Uri.parse(url)); startActivity(intent); return true; } return super.shouldOverrideUrlLoading(view, url); } }
目前正处于自由职业的状态,若是有API或者客户端的需求欢迎加我微信
若是你有什么好想法想告诉我,或者想加入讨论组(注明加入讨论组),请加我微信。
若是你以为个人工做对你有帮助,那你能够为项目捐赠运营费用。