[Java] 抽象类为什么不能实例化

大家知道,抽象类和接口都是不能实例化的,但是为什么呢?看看以下解释:

1.哲学上:

面向对象思想是对现实社会的模拟(抽象),从哲学上讲进化不完全的物种是不能生存的! 所以对于一个抽象性的东西,它是不完整的,也就没法实例,就好比买水果,显然是买不到一种叫“水果”的东西的。

2.内存中:

抽象类只在内存中分配了在栈中的引用,没有分配堆中的内存。程序都有一个代码段, 在内存中需要占据一定的内存,而抽象类有抽象方法,没有具体的实现方法,无法具体的给它分配内存空间,所以为了安全, JAVA不允许抽象类,接口直接实例化。

[Andorid]有赞Android客户端网络架构演进

转自:有赞技术团队博客 http://tech.youzan.com/android_http/

Android客户端网络请求是每一个应用都不可或缺的模块,其设计的好坏直接影响应用的性能和代码稳定性、扩展性。Android网络请求最开始官方只提供了最基础的方法,开发者必须在此基础上进行二次封装,这样就要求开发者对Http请求协议、缓存、JSON转换、错误处理以及线程切换等都比较熟悉,稳定性、可扩展性和可维护性都是比较大的挑战。

目前Android主流的网络请求都是基于Square公司开发的OkHttp,该框架也得到了Google官方的认可,OkHttp对网络请求做了大量的封装和优化,极大降低了开发者的使用成本,同时兼备稳定性和可扩展性。目前有赞Android客户端也是采用OkHttp进行网络请求,在OkHttp框架的基础上做了大量的封装和优化,减小业务逻辑与框架的耦合性的同时,也极大降低了业务方的使用成本。

1. 现在的网络请求

先以Get请求为例,代码如下:

private static final String TRADE_CATEGORIES = "kdt/tradecategories/get";  
/**
 * 获取订单类型
 *
 * @param context context
 * @param callback TradeCategoryEntity callback
 */
public void requestTradeCategories(Context context,Map<String, String> params, BaseTaskCallback<List<TradeCategoryEntity>> callback) {  
    RequestApi api = createRequestTokenApi(TRADE_CATEGORIES);
    if (null != params) {
        api.addMultipleRequestParams(params);
    }
    api.setMethod(RequestApi.Method.GET);
    api.setResponseKeys(GoodsTradeApi.ResponseKey.RESPONSE, "categories");
    doRequest(context, api, true, callback, TaskErrorNoticeMode.NONE);
}

针对上述Get请求需要几点说明:

  1. 每个模块都需要一个单独的网络请求类,罗列该模块所有的业务请求方法
  2. 请求url需要在每个请求类中定义,如上文中的TRADE_CATEGORIES
  3. 每次请求都需要调用createRequestTokenApi方法,完成token参数的封装
  4. 请求参数可以直接通过addRequestParams添加,也可以以Map的形式统一添加addMultipleRequestParams
  5. 请求方法通过setMethod方法设置
  6. 返回值可以通过setResponseKeys指定需要的具体JSON节点
  7. 请求结果通过BaseTaskCallback回调,返回值类型也可通过泛型方式指定
  8. 回调方法包括请求成功、请求失败以及请求时机等方法

以下是请求结果回调方法代码:

// 请求开始的回调方法
public void onBefore(RequestApi api) {

}
// 请求结束的回调方法
public void onAfter() {

}
// 请求成功的回调方法,data表示返回值,statusCode表示请求状态值
public void onResponse(T data, int statusCode){

}
// 网络请求失败的回调方法
public void onError(ErrorResponse errorResponse) {

}
// 请求的业务错误回调方法
public void onRequestError(ErrorResponse errorResponse) {

}

业务方只需要在不同的回调方法中处理不同的业务即可,比如说在onBefore中显示进度条,在onAfter中隐藏进度条,在onResponse中获取返回值以及状态值,onRequestError中获取错误类型,以及显示错误提示等等。

针对上述请求过程,可以发现还是有一些不是很合理的地方。

  1. 每次新建一个网络接口方法都需要设置请求url、封装token、设置请求方法等,造成了大量的重复代码
  2. 请求url和具体的请求方法分开定义,不够直观
  3. 如果请求参数比较多的时候,使用Map方式容易写错,而且在编译过程和自测阶段不会有错误提示
  4. 请求成功回调方法的线程切换还是需要借助Handler等方式实现

2. 改进后的网络请求框架

针对以上问题,我们引入Square公司开发的Retrofit和ReactiveX出品的RxJava,以下是结合有赞业务后网络请求用法。

首先定义网络请求方法,Retrofit是通过接口的方式完成请求方法定义的,代码如下:

public interface TradesService {  
    @GET("api.tradecategories/1.0.0/get")
    Observable<Response<CategoryResponse>> tradeCategories();

    @FormUrlEncoded
    @GET("api.trade/1.0.0/get")
    Observable<Response<TradeItemResponse>> tradeDetail(@Field("tid") String tid);
}

通过注解的方式指定请求类型、请求url、请求参数,返回值是RxJava的Observable对象,就可以进行一系列的链式操作,具体用法等一下会详细讲述。通过对比两种请求方法的写法,很显然,通过Retrofit定义请求方法更简洁清晰,下面我们来看一下如何通过RxJava实现错误信息的统一处理。

2.1 请求错误处理

首先定义一个BaseResponse,所有的Response都要继承自它。

public class BaseResponse {  
    public static final int CODE_SUCCESS = 0;

    public String msg;
    public int code;
    @SerializedName("error_response")
    public ErrorResponse errorResponse;

    public static final class ErrorResponse {
        public String msg;
        public int code;
    }
}

BaseResponse定义了错误信息,后续所有的Response都会继承BaseResponse,而服务端返回的错误信息都会进行集中处理,处理逻辑代码如下:

 // 下文的T:T extends Response<R>, R: R extends BaseResponse
 public Observable<R> call(Observable<T> observable) {
    return observable.map(new Func1<T, R>() {
        @Override
        public R call(T t) {
            String msg = null;
            if (!t.isSuccessful() || t.body() == null) {
                msg = mDefaultErrorMsg;
            } else if (t.body().code != BaseResponse.CODE_SUCCESS) {
                msg = t.body().msg;
                if (msg == null) {
                    msg = mDefaultErrorMsg;
                }
                tryLogin(t.body().code);
            } else if (t.body().errorResponse != null) {
                msg = t.body().errorResponse.msg;
                if (msg == null) {
                    msg = mDefaultErrorMsg;
                }
                tryLogin(t.body().errorResponse.code);
            }
            if (msg != null) {
                try {
                    throw new ErrorResponseException(msg);
                } catch (ErrorResponseException e) {
                    throw Exceptions.propagate(e);
                }
            }
            return t.body();
        }
    });     

关于上面的代码有几点需要说明:

  1. T继承了Retrofit提供的Response类,该类包含了Http返回值、协议版本号等等通用信息,R是具体的业务数据结构,如同上文所说,R继承了BaseResponse,方便统一处理错误信息。
  2. 业务的错误返回值都是事先定义好的,只需要根据返回的错误码和错误类型进行处理即可
  3. 这里的错误类型本质上是利用RxJava的map转换方法,即将一种Observable转换成另一种Observable,转换的过程中对不同的错误类型进行处理,同时将处理后的结果通过Observable传递出去

2.2 线程切换

正如上文所言,之前的请求方法在请求回调方法中无法方便的切换线程,必须要借助Android原生的Handler方式,代码就会比较分散,可读性也比较差,而RxJava针对线程切换提供了很友好的处理方法,只需要显式的设置即可完成不同线程的切换。

public class SchedulerTransformer<T> implements Observable.Transformer<T, T> {  
    @Override
    public Observable<T> call(Observable<T> observable) {
        return observable
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread());
    }
}

上面的代码主要完成了两部分的工作,subscribeOn声明生产者Observable所在的线程,由于网络请求比较耗时,一般会放到IO线程或者单独的子线程;而observeOn声明了处理请求返回值所在的线程为Android系统的主线程,也即UI线程,这样就可以在处理返回值时更新UI。

2.3 请求日志

网络请求经常会出现各种各样的异常情况,这个时候就需要通过查看请求日志来跟踪定位问题,OkHttp提供了打印完整日志的方法,方便开发者调试网络请求。OkHttp官方提供了一个很方便查看日志的Interceptor,你可以控制你需要的打印信息类型,使用方法也很简单。

首先需要在build.gradle文件中引入logging-interceptor

compile 'com.squareup.okhttp3:logging-interceptor:3.4.1'  

在OkHttpClient创建处添加创建好的Interceptor即可,完整的示例代码如下:

private static OkHttpClient getNewClient(){  
    HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
    logging.setLevel(HttpLoggingInterceptor.Level.BODY);
    return new OkHttpClient.Builder()
           .addInterceptor(logging)
           .build();
}

HttpLoggingInterceptor提供了4中控制打印信息类型的等级,分别是NONE,BASIC,HEADERS,BODY,接下来分别来说一下相应的打印信息类型。

  • NONE

    没有任何日志信息

  • Basic

    打印请求类型,URL,请求体大小,返回值状态以及返回值的大小

  D/HttpLoggingInterceptor$Logger: --> POST /upload HTTP/1.1 (277-byte body)  
  D/HttpLoggingInterceptor$Logger: <-- HTTP/1.1 200 OK (543ms, -1-byte body)  
  • Headers

    打印返回请求和返回值的头部信息,请求类型,URL以及返回值状态码

  <-- 200 OK https://your_url (3787ms)
  D/OkHttp: Date: Sat, 06 Aug 2016 14:26:03 GMT
  D/OkHttp: Content-Type: application/json; charset=utf-8
  D/OkHttp: Transfer-Encoding: chunked
  D/OkHttp: Connection: keep-alive
  D/OkHttp: Keep-Alive: timeout=30
  D/OkHttp: Vary: Accept-Encoding
  D/OkHttp: Expires: Sun, 1 Jan 2006 01:00:00 GMT
  D/OkHttp: Pragma: no-cache
  D/OkHttp: Cache-Control: must-revalidate, no-cache, private
  D/OkHttp: Set-Cookie: bid=D6UtQR5N9I4; Expires=Sun, 06-Aug-17 14:26:03 GMT; Domain=.douban.com; Path=/
  D/OkHttp: X-DOUBAN-NEWBID: D6UtQR5N9I4
  D/OkHttp: X-DAE-Node: dis17
  D/OkHttp: X-DAE-App: book
  D/OkHttp: Server: dae
  D/OkHttp: <-- END HTTP
  • Body

    打印请求和返回值的头部和body信息

  <-- 200 OK https://your_url (3583ms)
  D/OkHttp: Connection: keep-alive
  D/OkHttp: Date: Sat, 06 Aug 2016 14:29:11 GMT
  D/OkHttp: Keep-Alive: timeout=30
  D/OkHttp: Content-Type: application/json; charset=utf-8
  D/OkHttp: Vary: Accept-Encoding
  D/OkHttp: Expires: Sun, 1 Jan 2006 01:00:00 GMT
  D/OkHttp: Transfer-Encoding: chunked
  D/OkHttp: Pragma: no-cache
  D/OkHttp: Connection: keep-alive
  D/OkHttp: Cache-Control: must-revalidate, no-cache, private
  D/OkHttp: Keep-Alive: timeout=30
  D/OkHttp: Set-Cookie: bid=ESnahto1_Os; Expires=Sun, 06-Aug-17 14:29:11 GMT; Domain=.douban.com; Path=/
  D/OkHttp: Vary: Accept-Encoding
  D/OkHttp: X-DOUBAN-NEWBID: ESnahto1_Os
  D/OkHttp: Expires: Sun, 1 Jan 2006 01:00:00 GMT
  D/OkHttp: X-DAE-Node: dis5
  D/OkHttp: Pragma: no-cache
  D/OkHttp: X-DAE-App: book
  D/OkHttp: Cache-Control: must-revalidate, no-cache, private
  D/OkHttp: Server: dae
  D/OkHttp: Set-Cookie: bid=5qefVyUZ3KU; Expires=Sun, 06-Aug-17 14:29:11 GMT; Domain=.douban.com; Path=/
  D/OkHttp: X-DOUBAN-NEWBID: 5qefVyUZ3KU
  D/OkHttp: X-DAE-Node: dis17
  D/OkHttp: X-DAE-App: book
  D/OkHttp: Server: dae
  D/OkHttp: {"count":3,"start":0,"total":778,"books":[{"rating":{"max":10,"numRaters":202900,"average":"9.0","min":0},"subtitle":"","author":["[法] 圣埃克苏佩里"],"pubdate":"2003-8","tags":[{"count":49322,"name":"小王子","title":"小王子"},{"count":41381,"name":"童话","title":"童话"},{"count":19773,"name":"圣埃克苏佩里","title":"圣埃克苏佩里"}
  D/OkHttp: <-- END HTTP (13758-byte body)

2.3 统一添加token和User-Agent

现在基本上所有的应用都会通过token来鉴定用户权限,User-Agent参数方便服务端获取更多手机本地的信息,类似这样的每一个请求都需要的参数,也可以通过Interceptor方式实现。

public class LoginInterceptor implements Interceptor {  
    public static final String ACCESS_TOKEN = "access_token";
    public static final String USER_AGENT = "User-Agent";

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request original = chain.request();
        Request request = original.newBuilder()
                .header(USER_AGENT, AppUtils.getUserAgent() + " app_name/" + AppUtils.getVersionName())
                .header(ACCESS_TOKEN, AppUtils.getToken())
                .method(original.method(), original.body())
                .build();
        return chain.proceed(request);
    }
}

2.4 通过对象封装请求体

有的时候post请求的参数会有很多个,如果一个个写,那么方法的参数就显得很多,杂乱且不好维护,当然可以用Map的方式集中管理,但是Map有一个问题就是一旦key值写错了,前期是无法及时发现的,必须等到真正的网络请求的时候才能发现。Retrofit提供了一个很好的解决方法,通过对象封装这些请求参数。

@FormUrlEncoded
@POST("book/reviews")
Observable<Response<String>> addReviews(@Body Reviews reviews);

public class Reviews {  
    public String book;
    public String title;
    public String content;
    public String rating;
}

需要注意的是类中的属性名必须要和请求参数的key保持一致,否则服务端无法正常识别请求参数

关于Retrofit详细的用法可以参考文章Retrofit详解

3. 如何使用

我们以获取Category为例来说明如何利用RetrofitRxAndroid来改写现有模块。

3.1 定义CategoryResponse

CategoryResponse必须继承自BaseResponse,里面包含了错误信息的数据结构。

@Keep
public class CategoryResponse extends BaseResponse {  
    public Response response;

    @Keep
    public static final class Response {
        public List<Category> categories;
    }
}

其中Category是具体的实体类型。

3.2 定义Service Method

public interface TradesService {  
    @GET("api/tradecategories/get")
    Observable<Response<CategoryResponse>> tradeCategories();
}

注意点

  • TradesService必须是一个interface,而且不能继承其他interface
  • tradeCategories的返回值必须是Observable类型。

3.3 利用ServiceFactory创建一个TradeService实例

在适当的时机(Activity#onCreateFragment#onViewCreated等)根据网关类型通过ServiceFactory创建一个TradeService实例。

mTradesService = ServiceFactory.createNewService(TradesService.class)  

3.4 TradeService获取数据

mTradesService.tradeCategories()  
        .compose(new DefaultTransformer<CategoryResponse>(getActivity()))
        .map(new Func1<CategoryResponse, List<Category>>() {
            @Override
            public List<Category> call(CategoryResponse response) {
                return response.response.categories;
            }
        })
        .flatMap(new Func1<List<Category>, Observable<Category>>() {
            @Override
            public Observable<Category> call(List<Category> categories) {
                return Observable.from(categories);
            }
        })
        .subscribe(new ToastSubscriber<Category>(getActivity) {
            @Override
            public void onCompleted() {
                hideProgressBar();
                // business related code
            }

            @Override
            public void onError(Throwable e) {
                super.onError(e);
                hideProgressBar();
                // business related code
            }

            @Override
            public void onNext(Category category) {
                // business related code
            }
        });

注意:DefaultTransformer包含了线程分配错误处理两部分功能,所以调用方只需要关心正确的数据就可以了。

DefaultTransformer包含了上文提到的线程切换转换器SchedulerTransformer和错误处理转换器ErrorCheckerTransformer,具体代码如下:

public class DefaultTransformer<R extends BaseResponse>  
        implements Observable.Transformer<Response<R>, R> {

    private Context context;

    public DefaultTransformer(final Context context) {
        this.context = context;
    }

    @Override
    public Observable<R> call(Observable<Response<R>> observable) {
        return observable
                .compose(new SchedulerTransformer<Response<R>>())
                .compose(new ErrorCheckerTransformer<Response<R>, R>(context));
    }
}

4.写在最后

网络请求对于提升Android应用的体验和性能有很大的影响,结合Retrofit使用提高了代码可读性和编码的灵活性,RxJava提供了链式调用方式,融合了线程切换、数据过滤、数据转换等优点。有赞在此基础上进行了少量的封装,便可适应大部分复杂的业务场景。

[Android] apk打包问题

多次在生产签名打包后的apk,出现功能不可用的情况,比方说有个社会化分享功能,写代码时都可以正常实现,但签名生成apk后该功能无法再使用了,点击分享面板的平台,没有任何响应。请问是怎么回事,这种问题解决应该从哪几个方面入手,希望有一些思路可供参考

  • 应该是混淆引起的
  • 混淆是将易读性较好的变量,方法和类名替换成可读性较差的名称
  • 混淆的目的是为了加大逆向的成本,但不能避免
  • 通常混淆的处理是将某些库不加入混淆
  • 第三方的库不建议混淆

一些需要排除混淆的

  • 被native方法调用的java方法
  • 供javascript调用的java方法
  • 反射调用的方法
  • AndroidManifest中声明的组件
  • 总结:即所有硬编码的元素(变量,方法,类)

关于混淆,请参考文章读懂 Android 中的代码混淆

[Android] Bitmap优化

Bitmap优化

  • options.inJustDecodeBounds = true;可以获取width,height和mimetype等信息,但不会申请内存占用
  • 合理进行缩放,一个高分辨率的图片不仅展示在一个小的imageView中,不仅不会有任何视觉优势,反而还占用了很大的内存
  • 将Bitmap处理移除主线程
  • 使用LruCache或者DiskLruCache缓存Bitmap
  • before 2.3 手动调用recycle()方法

关于Bitmap的文章

[Android] 把Activity作为参数传给一个静态方法,会影响这个Activity的正常销毁吗

把Activity作为参数传给一个静态方法,会影响这个Activity的正常销毁吗

  • 内存泄露与方法是否是静态与否无关,与内部的方法体实现有关系。
  • 内存泄露可以简单理解成:生命周期长的对象不正确持有了持有了生命周期短的对象,导致生命周期短的对象无法回收。
  • 比如Activity实例被Application对象持有,Activity实例被静态变量持有。

关于Android中内存泄漏的文章

[Android] static 单例是怎么保证单例的?

static 单例是怎么保证单例的?没太看明白

  • static变量为类所有
  • staitc只初始化一次,即在调用的时候。
  • 如下代码,STATIC_OBJECT只在第一次调用时初始化,后续调用则不会再执行初始化
1
2
3
public class Example {
    public static Object STATIC_OBJECT = new Object();
}
  • 使用static机制创建单例
1
2
3
4
5
6
7
8
9
10
11
12
public class SingleInstance {
    private SingleInstance() {
    }

    public static SingleInstance getInstance() {
        return SingleInstanceHolder.sInstance;
    }

    private static class SingleInstanceHolder {
        private static SingleInstance sInstance = new SingleInstance();
    }
}

关于单例的文章

[Android] 业务场景:需要定时后台扫描数据库,上传本地照片至云端,定时任务采用何种模式

业务场景:需要定时后台扫描数据库,上传本地照片至云端,定时任务采用何种模式

  • Handler或者Timer定时一般为秒级别的任务,Timer会启动额外线程,而Handler可以不用。
  • 无论是Handler还是Timer都需要依赖于进程存活
  • 利用Handler实现定时任务的类:HandlerTimer
  • 如果时间较长,则需要使用AlarmManager
  • 另外,我们对于这种业务应该优先考虑是否可以基于事件通知。
  • 如果是加入媒体库的文件,我们可以使用registerContentObserver监听媒体库文件变化。

[Android] 使用Handler到底需不需要使用弱引用,什么时候情况下用

使用Handler到底需不需要使用弱引用,什么时候情况下用

  • 正常境况下的引用都为强引用,其特点是及时内存溢出也不可以被回收
1
ArrayList list = new ArrayList();
  • 弱引用则会在垃圾回收时被回收掉,因而弱引用解决内存泄露的一种方法。
1
2
3
ArrayList list = new ArrayList();
WeakReference<ArrayList> listWeakRef = new WeakReference<ArrayList>(list);
ArrayList myList = listWeakRef.get();
  • Handler是否需要设置弱引用,取决于它是否可能发生内存泄露

Handler内存泄露的场景

  • Message的target变量实际是Handler对象
  • Message存放在MessageQueue中
  • MessageQueue通常为Looper持有
  • Looper和可以认为和线程生命周期相同
  • 通常情况下,我们使用匿名内部类的形式创建Handler,而匿名内部类(非静态内部类)会隐式持有外部类的引用。即如下的mHandler会隐式持有Activity的实例引用。
1
2
3
4
5
6
private Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
    }
};
  • 如果有一个延迟很久的消息,可能会导致Activity内存泄露
  • 可以使用弱引用解决内存泄露问题
  • 也可以在Activity onDestory方法中调用handler.removeCallbacksAndMessages(null);

相关文章

[Android]如何优化字符串拼接

如何优化字符串拼接

  • 字符串拼接无法避免的创建StringBuilder对象
  • 如果是循环情况下拼接,需要显式在循环外声明一个StringBuilder对象

不好的代码

1
2
3
4
5
6
7
public void  implicitUseStringBuilder(String[] values) {
  String result = "";
  for (int i = 0 ; i < values.length; i ++) {
      result += values[i];//create new StringBuilder object every time
  }
  System.out.println(result);
}

改进后的代码

1
2
3
4
5
6
public void explicitUseStringBuider(String[] values) {
  StringBuilder result = new StringBuilder();
  for (int i = 0; i < values.length; i ++) {
      result.append(values[i]);
  }
}

关于拼接字符串的文章