一个互联网app的开发设计(技术选型和架构)

               在做一个互联网应用时, 要考虑技术选型和架构搭建。 先说说技术选型,   以丁丁租房为例在开发时会面对如下问题:

1、图片处理, image loader、picasso、Glide、Fresco, 推荐使用fresco,因为它使用三级缓存、占内存更小;

2、http通信,  开源框架有很多例如volley,retrofit,okhttp等等,    用法都很简单也类似,  推荐使用OkHttp,它支持SPDY;

3、崩溃日志采集, 免费库也有很多,  腾讯bugly、友盟、Fabric。我们使用的是Fabric,  挺好用的。

4、即时聊天,   三方库也有很多, 就不多说了。 我们用的是leancloud, 因为它免费:), 而且效果还可以。

5、控件,   根据UI需要可以自己写或用别人写好的三方库, 例如Materialdialog,MPAndroidChart, WheelView等等。

6、进程内部消息传递,   例如跟Service交互的binder、或者LocalbroadcastReceiver(基于主线程handler实现的,所以不会被其他进程收到,比较安全!)、三方库EventBus和观察者模式等等。

7、支付, 可以用支付宝、微信的接口。 

8、埋点, 这个就很重要了,  产品经理每天都在盯着这个统计数据, 用他们的话叫做数据引导决策,  有时前端也会弄ABTest, 目的是做出让用户更喜欢的东西。 我们用了Countly, 因为它开源,  后台可以自己做,  埋点数据可以传到自己的服务器。 毕竟一些敏感的数据是不想让三方如友盟知道的。

9、bug热修复,  目前有很多技术方案, 我们参考了HotFix的方式在项目中落地,  网上有人说适配有问题, 但我们还没碰到。。。

10、插件化,  应用典型案例就是支付宝、微信。当app功能非常多、代码量很大(超过65535个方法)时要考虑,  我们是计划实践一下, 但因为公司突然倒闭, 没来得及做。

11、程序框架,   对比mvc、mvp和mvvm模式, 我觉得mvp模式比较合适并落地到丁丁租房app里, 因为mvp很好的实现了代码解耦、逻辑分层,  下层对上层透明, 每层只关心自己那点事情就够了。

12、UI标注,推荐使用标你妹啊网站、app.zeplin.io网站。


                                   下面丁丁租房架构图, 最上面一层是各个功能。
一个互联网app的开发设计(技术选型和架构)_第1张图片

   代码结构如下, 按照业务划分一级目录, 二级目录是按照android各组件区分。

一个互联网app的开发设计(技术选型和架构)_第2张图片一个互联网app的开发设计(技术选型和架构)_第3张图片

      从二级目录看到activity实例化presenter并保存该引用, presenter通过Activity传进来的interface回调到Activity, presenter实现网络接口。 

       调试网络接口要说一下, 在Activity/Fragment退出时要取消未完成的网络请求, 避免耗费流量和回调刷ui时的异常; 可以用RestClient在浏览器测试接口,目的是理解和调试接口的有效性;    

下面附干货源码, 供参考:

Model代码:

/**  * http网络相关工具类, 目前使用三方库OkHttp库  */ public class NetworkUtils {

    //网络接口执行周期打点
    private static class DotNet {
        public String tag;   //网络接口的标签
        public long   timestamp; //时间戳
        public String uuid;
    }

    private static OkHttpClient sInstance;

    //初始化OkHttp实例
    static {
        sInstance = new OkHttpClient();
        sInstance.setConnectTimeout(10, TimeUnit.SECONDS);  //连接超时时间
        sInstance.setWriteTimeout(10, TimeUnit.SECONDS);    //上传文件超时时间
        sInstance.setReadTimeout(20, TimeUnit.SECONDS);     //下载文件超时时间
    }

    /**  * 格式化请求body  *  * @param object, 查询用的参数,Map or entity  */  private static RequestBody formatRequestBody(
            Context context,
            Object object) {

        //JSON.toJSONString(object)方法默认执行去除value为null的字段
        /*Iterator iterator = map.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry entry = (Map.Entry) iterator.next();
            //编辑房源时可以传空字符串
            if (entry.getValue() == null) {
                iterator.remove();
            }
        }*/

        LinkedHashMap baseMap = GatewayUtils.getInstance().getBase(context);  //获取base参数
        Iterator it = baseMap.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry entry = (Map.Entry) it.next();
            if (entry.getValue() == null || StringUtils.isNull(entry.getValue().toString())) {
                it.remove();
            }
        }

        String param = JSON.toJSONString(object);           //生成param参数
        String sign = ParamBuild.getSign(object, baseMap);  //计算sign参数

        baseMap.put("sign", sign);
        String base = JSON.toJSONString(baseMap);

        RequestBody formBody = new FormEncodingBuilder()
                .add("base", base)
                .add("param", param)
                .build();

        return formBody;
    }

    /**  * 格式化请求body  *  * @param object, 查询用的参数,Map or entity  * @param dotNet, 打点要用的参数  */  private static RequestBody formatRequestBodyExt(
            Context context,
            Object object,
            DotNet dotNet) {

        //JSON.toJSONString(object)方法默认执行去除value为null的字段
        /*Iterator iterator = map.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry entry = (Map.Entry) iterator.next();
            //编辑房源时可以传空字符串
            if (entry.getValue() == null) {
                iterator.remove();
            }
        }*/

        LinkedHashMap baseMap = GatewayUtils.getInstance().getBase(context);  //获取base参数
        Iterator it = baseMap.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry entry = (Map.Entry) it.next();
            if (entry.getValue() == null || StringUtils.isNull(entry.getValue().toString())) {
                it.remove();
            }
        }

        try {
            //网络接口执行周期打点时的参数
            String uuid = (String) baseMap.get("uuid");
            dotNet.timestamp = System.currentTimeMillis();
            dotNet.uuid = uuid;
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        String param = JSON.toJSONString(object);           //生成param参数
        String sign = ParamBuild.getSign(object, baseMap);  //计算sign参数

        baseMap.put("sign", sign);
        String base = JSON.toJSONString(baseMap);

        RequestBody formBody = new FormEncodingBuilder()
                .add("base", base)
                .add("param", param)
                .build();

        return formBody;
    }



    /**  * 与服务器异步交互, 并返回结果字符串  *  * @param ctx 应用上下文  * @param url 服务器url地址  * @param map 查询参数  * @param tag 标签,用于取消请求  * @param listener 回调  */  public static void asyncWithServer(final Context ctx,
                                       String url,
                                       Map map,
                                       String tag,
                                       final OnNetworkListener listener) {
        if (ctx == null
                || url == null
                || listener == null
                || tag == null
                || map == null
                ) {
            return;
        }

        DotNet dotNet = new DotNet();
        dotNet.tag = tag;
        final RequestBody requestBody = formatRequestBodyExt(ctx, map, dotNet);   //组织请求包体

        Request request = new Request.Builder()
                .tag(dotNet)
                .url(url)
                .addHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8")
                .post(requestBody)
                .build();

        sInstance.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Request request, IOException e) {
                DotNet dotTag = (DotNet)request.tag();
                listener.onFailure(dotTag.tag);
            }

            @Override
            public void onResponse(Response response) throws IOException {
                DotNet dotTag = (DotNet)response.request().tag();
                if (!response.isSuccessful()) {
                    //失败
                    listener.onFailure(dotTag.tag);
                } else {
                    //Countly打点, 统计接口执行周期
                    try {
                        dotInterfaceTag(ctx, dotTag.timestamp, dotTag.uuid,
                                response.request().urlString());
                    } catch (Exception ex) {
                        ex.printStackTrace();
                    }

                    String jsonBody = response.body().string();
                    //添加json解析异常逻辑,https://fabric.io/cb5ab5/android/apps/com.dingding.client/issues/561cf641f5d3a7f76bc3e0df
                    Map, Object> map = null;
                    try {
                        map = JSON.parseObject(jsonBody);
                    } catch (JSONException ex) {
                        ex.printStackTrace();
                    } finally {
                        if (map == null
                                || map.size() == 0) {
                            listener.onFailure(dotTag.tag); //解析失败时按照接口失败处理
                            return;
                        }
                    }
                    //更新token, 可能引起性能问题, 相当于做了2次json解析
                    if (map.containsKey("token")) {
                        String token = (String) map.get("token");
                        GatewayUtils.getInstance().setToken(ctx, token);
                    }
                    Integer code = (Integer) map.get("code");
                    if (code == 100016) {//code为100016时为token过期 重新登录
                        GatewayUtils.getInstance().setDefult(ctx);
                        Intent intent = new Intent();
                        intent.setAction("com.dingding.client.NewLoginActivity");
                        intent.putExtra("code", code);
                        intent.putExtra("from", "2");
                        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                        ctx.startActivity(intent);
                    }
                    listener.onSuccess(jsonBody,
                            dotTag.tag);
                }

            }
        });
    }

    /**  * 与服务器异步交互, 并返回类对象  *  * @param ctx 应用上下文  * @param url 服务器url地址  * @param map 查询参数  * @param tag 标签,用于取消请求  * @param listener 回调  * @param clz, 解析结果类 可以传入null null是为了考虑有些返回值没有data节点  * @param isList ,传入true则返回解析为列表,传入false则返回为对象  */  public static void asyncWithServerExt(Context ctx,
                                          String url,
                                          Map map,
                                          String tag,
                                          final OnNetworkListener listener,
                                          final Class clz,
                                          final boolean isList) {
        asyncWithServerExt(ctx, url, map, tag, listener, clz, isList, null);
    }


    /**  * 与服务器异步交互, 并返回类对象  *  * @param ctx 应用上下文  * @param url 服务器url地址  * @param object 查询参数,Map or entity  * @param tag 标签,用于取消请求  * @param listener 回调  * @param clz, 解析结果类 可以传入null null是为了考虑有些返回值没有data节点  * @param isList ,传入true则返回解析为列表,传入false则返回为对象  */  public static void asyncWithServerExt(final Context ctx,
                                          String url,
                                          Object object,
                                          String tag,
                                          final OnNetworkListener listener,
                                          final Class clz,
                                          final boolean isList, final String key) {
        if (ctx == null
                || url == null
                || listener == null
                || tag == null
                || object == null
                ) {
            return;
        }

        DotNet dotNet = new DotNet();
        dotNet.tag = tag;

        RequestBody requestBody = formatRequestBodyExt(ctx, object, dotNet);   //组织请求包体, 会修改时间戳和uuid

        Request request = new Request.Builder()
                .tag(dotNet)
                .url(url)
                .addHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8")
                .post(requestBody)
                .build();

        sInstance.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Request request, IOException e) {
                DotNet dotNet = (DotNet) request.tag();
                listener.onFailure(dotNet.tag);
            }

            @Override
            public void onResponse(Response response) throws IOException {
                DotNet dotTag = (DotNet) response.request().tag();
                if (!response.isSuccessful()) {
                    //失败
                    listener.onFailure(dotTag.tag);
                } else {
                    //Countly打点, 统计接口执行周期
                    try {
                        dotInterfaceTag(ctx, dotTag.timestamp, dotTag.uuid,
                                response.request().urlString());
                    } catch (Exception ex) {
                        ex.printStackTrace();
                    }

                    //json解析并回调
                    DataFactory factory = new ResultObjectFactory();
                    ResultObject result;
                    if (clz == null) {
                        result = factory.createResultObjectNoCls(ctx, response.body().string());
                    } else {
                        result = factory.createResultObject(ctx, isList,
                                response.body().string(),
                                clz, key);
                    }
                    if (result == null) {
                        listener.onFailure(dotTag.tag);
                    } else {
                        listener.onSuccessExt(result, dotTag.tag);
                    }
                }

            }
        });
    }

    /**  * Countly打点计算每个接口的执行周期  * @param url, 请求的url  */  private static void dotInterfaceTag(Context ctx, long timestamp, String uuid, String url) {
        long diff = Math.abs(System.currentTimeMillis() - timestamp);

        HashMap, Object> paramMap = new HashMap<>();
        paramMap.put("uuid", uuid);
        paramMap.put("time", diff + "");
        paramMap.put("url", url);

        Log.d("NetPerformance", "url:" + url + ", diff:" + diff);
        Statistics.dotNetPerformance(ctx, paramMap);
    }


    /**  * 与服务器异步交互, 并返回类对象  *  * @param ctx 应用上下文  * @param url 服务器url地址  * @param map 查询参数  * @param tag 标签,用于取消请求  * @param listener 回调  * @param clz, 解析结果类 可以传null  */  public static void asyncWithServerExt(Context ctx,
                                          String url,
                                          Map map,
                                          String tag,
                                          final OnNetworkListener listener,
                                          final Class clz) {
        asyncWithServerExt(ctx, url, map, tag, listener, clz, false);
    }

    /**  * 与服务器同步交互, 必须在非UI线程执行!  */  public static String syncWithServer(
            Context ctx,
            String url,
            Map map) {
        if (ctx == null
                || url == null
                || map == null) {
            return "";
        }

        String retStr = null;    //服务器返回的包体

        RequestBody requestBody = formatRequestBody(ctx, map);   //组织请求包体

        Request request = new Request.Builder()
                .url(url)
                .addHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8")
                .post(requestBody)
                .build();

        Response response;
        try {
            response = sInstance.newCall(request).execute();
            if (response != null
                    && response.isSuccessful()) {
                retStr = response.body().string();  //包体
            }
        } catch (IOException ex) {
            ex.printStackTrace();
        } finally {

        }

        return retStr;
    }

    //返回ResultObject
    public static ResultObject syncWithServer(
            Context ctx,
            String url,
            Map map,
            final Class clz) {

        RequestBody requestBody = formatRequestBody(ctx, map);   //组织请求包体

        Request request = new Request.Builder()
                .url(url)
                .addHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8")
                .post(requestBody)
                .build();


        ResultObject result;
        Response response;
        DataFactory factory;
        try {
            response = sInstance.newCall(request).execute();
            factory = new ResultObjectFactory();
            result = factory.createResultObject(ctx, false, response.body().string(), clz, null);
        } catch (IOException ex) {
            result = new ResultObject();
            result.setCode(-1);
            result.setSuccess(false);
            result.setMessage("网络错误");
        }

        return result;
    }

Presenter代码:

/**  * Presenter基类  */ public abstract class BasePresenter {
    public HashMap, Object> mKeyMap
            = new HashMap, Object>();  //请求的参数

    public HashMap, Object> mFilterMap
            = new HashMap, Object>();   //请求的参数

    public Handler mHandler = new Handler();
    /**  * UI主线程handler, 用于更新view  */   protected HashSet mTagList = new HashSet();

    public IBaseView mIView;

    /**  * 与后台交互时的参数  *  * @return  */  public abstract HashMap, Object> getParams();

    /**  * 获取应用上下文  */  public abstract Context getContext();

    /**  * 设置应用上下文, 使用ApplicationContext  */  public abstract void setContext(Context ctx);

    /**  * 设置tag, 用于删除队列中的网络请求, 也可以区分不同的接口  *  * @param tag  */  public abstract void setTag(String tag);

    /**  * 获取tag, 用于删除队列中的网络请求,也可以区分不同的接口  */  public abstract String getTag();

    /**  * 获取回调函数的引用, 不同业务的逻辑不同  */  public abstract OnNetworkListener getListener();

    /**  * 设置View引用,派生类可以扩展IBaseView接口类, 在activity里实例化,并通过强制类转换赋值。  */  public void setView(IBaseView view) {
        mIView = view;
    }

    /**  * 获取View引用, 派生类可以扩展IBaseView接口类,在activity里实例化,并通过强制类转换赋值。  */  public IBaseView getView() {
        return mIView;
    }

    /**  * 在ui主线程执行更新界面的操作  */  public void updateUI(Runnable runnable) {
        mHandler.post(runnable);
    }

    /**  * 清空参数  */  public void resetParams() {
        mKeyMap.clear();
        mFilterMap.clear();
    }

    /**  * 与后台交互, 派生类可以实例化回调函数OnNetworkListener。  * 回调函数会执行onSuccess(String body)  *  * @param url, 网址  * @param listener, model层的回调。 为空时使用getListener()  * @return boolean, 是否成功传递到model层  */  protected boolean asyncWithServer(String url, OnNetworkListener listener) {
        String tag = getTag();
        Context ctx = getContext();
        OnNetworkListener callback;
        if (listener == null) {
            callback = getListener();
        } else {
            callback = listener;
        }

        if (tag == null
                || callback == null
                || ctx == null) {
            return false;
        }

        mTagList.add(tag);
        NetworkUtils.asyncWithServer(
                ctx,
                url,
                getParams(),
                tag,
                callback);
        return true;
    }

    /**  * 与后台交互, 派生类可以实例化回调函数OnNetworkListener。  * 回调函数会执行onSuccess(ResultObject result)  *  * @param url, 网址  * @param clz, 解析的类名  * @param listener, model层的回调。 为空时使用getListener()  * @return boolean, 是否成功传递到model层  */  protected boolean asyncWithServerExt(String url,
                                         Class clz,
                                         OnNetworkListener listener) {
        return asyncWithServerExt(url, clz, listener, false);
    }

    /**  * 与后台交互, 派生类可以实例化回调函数OnNetworkListener。  * 回调函数会执行onSuccess(ResultObject result)  *  * @param url, 网址  * @param clz, 解析的类名  * @param listener, model层的回调。 为空时使用getListener()  * @param isList ,传入true则返回解析为列表,传入false则返回为对象  * @return boolean, 是否成功传递到model层  */  protected boolean asyncWithServerExt(String url,
                                         Class clz,
                                         OnNetworkListener listener,
                                         final boolean isList) {
        return asyncWithServerExt(url, clz, listener, isList, null);
    }

    /**  * 与后台交互, 派生类可以实例化回调函数OnNetworkListener。  * 回调函数会执行onSuccess(ResultObject result)  *  * @param url, 网址  * @param clz, 解析的类名  * @param listener, model层的回调。 为空时使用getListener()  * @param isList ,传入true则返回解析为列表,传入false则返回为对象  * @return boolean, 是否成功传递到model层  */  protected boolean asyncWithServerExt(String url,
                                         Class clz,
                                         OnNetworkListener listener,
                                         final boolean isList, final String key) {
        String tag = getTag();
        Context ctx = getContext();
        OnNetworkListener callback;
        if (listener == null) {
            callback = getListener();
        } else {
            callback = listener;
        }

        if (tag == null
                || callback == null
                || ctx == null) {
            return false;
        }

        mTagList.add(tag);
        NetworkUtils.asyncWithServerExt(
                ctx,
                url,
                getParams(),
                tag,
                callback,
                clz,
                isList, key);
        return true;
    }

    /**  * 释放当前presenter的请求  * 在activity或fragment的onDestory函数中调用  */  public void cancelRequests() {
        for (Iterator iterator = mTagList.iterator(); iterator.hasNext(); ) {
            String tag = iterator.next();
            NetworkUtils.cancelRequestByTag(tag);
        }
    }


你可能感兴趣的:(Android)