「daza.io」个人全端开源项目的 Android & iOS 客户端都上线了
发布于 23 天前 作者 lijy91 277 次浏览 最后一次编辑是 21 天前 来自 分享

「daza.io」是一款基于技能树(正在实现)的技术内容聚合应用,根据你的技能对内容进行筛选,让你在这个信息过载的时代里更高效地获取你所需的内容。

自上次发文章之后已经过了2个月了,我也在11月19号结束了一个人的旅行(历时59天)回到了深圳,专心于完成这个全端项目的客户端开发,终于在12月2号 iOS 版上线 AppStore ,12月7号 Android 上线到 GooglePlay。

最初我将 iOS 版定价为1元,但是后来和一朋友聊天时聊到这个项目能为用户提供什么价值的问题,后来想想目前这个项目能给用户提供的价值是有限的,所以就调为免费的了。

访问网站

获取源码

Star ! Star ! Star !

应用截图

Android 的界面布局与 iOS 版基本保持一致,但均采用了原生的控件实现,这里就不放截图了。

获取

快速获取(自动识别系统): http://a.app.qq.com/o/simple.jsp?pkgname=io.daza.app

扫一扫,下载应用

iOS 版

Android 版

已上架多个国内主流应用市场

一些小技巧

以下是我觉得比较值得分享的小技巧。

设计

对于我来说 UI 才是最头痛的,在没有设计师帮忙的情况下一切都得自己来了,下面是我在做 UI 时的一些经验。

  1. 选用成熟的配色方案
  2. 使用相同风格的图标(尽量使用比较全的图标库)
  3. 尽量保持简洁的界面设计(实用至上)
  4. 与系统风格保持一致
  5. 合适的字体尺寸以及边距等
  6. 设计要符合使用场景(很多使用侧边栏导航的应用就是反面教材)

我在项目里使用了 Material Design 提供的配色(Blue Grey)和图标,在两个系统上看起来都非常的和谐。

参考资源

第三方服务

使用第三方服务就是为了减少研发成本,但一定要慎重选用。下面介绍这个项目使用的一些第三方服务。

  • DaoCloud

    使用了 Docker 镜像构建,自有主机功能。 当前项目已经完全实现自动部署。

  • 阿里云

    使用了 ECS 云主机

  • 七牛云

    使用了 云存储,免费 SSL 证书

  • 云巴

    使用了推送服务

  • GrowingIO

    用于统计

  • BugHD

    用于 Crash 收集

  • AdMob

    广告

API

使用了 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>>();

客户端

文章详情(WebView交互)

文章详情页面因为排版相关原因,并没有采用原生的开发方式,而是直接加载一个外部链接

外部链接:

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/daza-android/blob/master/app/src/main/java/io/daza/app/ui/InAppBrowserActivity.java

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/daza-ios/blob/master/Daza/Controllers/InAppBrowserController.swift

DeepLink支持

支持 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或者客户端的需求欢迎加我微信

如果你有什么好想法想告诉我,或者想加入讨论组(注明加入讨论组),请加我微信。

捐赠

如果你觉得我的工作对你有帮助,那你可以为项目捐赠运营费用。

1 回复

膜拜了。。

来自酷炫的 CNodeMD

回到顶部