Rxjava基础

  现在很多Android App的开发开始使用Rxjava,但是Rxjava以学习曲线陡峭著称,入门有些困难。经过一段时间的学习和使用,这里来介绍一下我对Rxjava的理解。

  说到Rxjava首先需要了解的两个东西,一个是Observable(被观察者,事件源)和 Subscriber(观察者,是 Observer的子类)。Observable发出一系列事件,Subscriber处理这些事件。首先来看一个基本的例子,我们如何创建并使用Observable。

Observable.create(new Observable.OnSubscribe<String>() { 
	@Override public void call(Subscriber<? super String> subscriber) { 
		subscriber.onNext("hello"); 
		} 
	}).subscribe(new Subscriber<String>()
	{ 
		@Override public void onCompleted() {
		} 
		@Override public void onError(Throwable e) { 
		} 
		@Override public void onNext(String s) { 
			Log.d("rx-info", s); 
		} 
	});

  创建Observable的最基本的方法是通过Observable.create() 来进行,当有Subscriber通过Observable.subscribe() 方法进行订阅之后Observable就会发射事件,注意必须要有订阅者订阅才会发射事件。发射的方式是通过调用 Observable中的 OnSubsribe 类型的成员来实现(每个Observable有一个final OnSubscribe<T> onSubscribe 成员,该成员是一个接口,后面详细说),在 Onsubsribe类型成员中调用 call() 方法,注意,这个call方法的参数就是 Observable.subscribe() 方法传入的 Subsriber实例。总的一句话就是在Obsubscribe 的 call方法中执行订阅者(Subscriber)的三个方法 onNext(), onCompleted() 和 onError()。

  一开始就是一堆 Observable , Subscriber,subscribe() , OnSubscribe 估计看得头晕,因此我们需要先来对这些东西有一个了解。这里只列出一个帮助理解的大概。

public class Observable<T> {

	  final OnSubscribe<T> onSubscribe;
	  protected Observable(OnSubscribe<T> f) {
              this.onSubscribe = f;
          }

       public final static <T> Observable<T> create(OnSubscribe<T> f) {
            return new Observable<T>(hook.onCreate(f));
        }

       public interface OnSubscribe<T> extends Action1<Subscriber<? super T>> {
            // cover for generics insanity
        }
	
	  public final Subscription subscribe(Subscriber<? super T> subscriber) {
              return Observable.subscribe(subscriber, this);
          }

       public interface Operator<R, T> extends Func1<Subscriber<? super R>, Subscriber<? super T>> {
        // cover for generics insanity
       }
}
public interface Action1<T> extends Action {
    void call(T t);
}
public interface Subscription {
    void unsubscribe();
    boolean isUnsubscribed();
}
public interface Observer<T> {
    void onCompleted();
    void onError(Throwable e);
    void onNext(T t);
}

public abstract class Subscriber<T> implements Observer<T>, Subscription {
	//...
}

  通过上面的代码帮助理清楚 Observable, Observer, Subscriber, OnSubsriber, subscribe() 之间的关系。这里额外提一下 Observable.subscribe() 方法有多个重载:

Subscription    subscribe()
Subscription    subscribe(Action1<? super  T> onNext)
Subscription    subscribe(Action1<? super  T> onNext, Action1< java.lang .Throwable> onError)
Subscription    subscribe(Action1<? super  T> onNext, Action1< java.lang .Throwable> onError, Action0 onComplete)
Subscription    subscribe(Observer<? super  T> observer)
Subscription    subscribe(Subscriber<? super  T> subscriber)

  其它的ActionX 和 FuncX 请大家自行去查阅定义。

  介绍了基本的创建Observable和 Observable是怎么发射事件的之后,来介绍一下Rxjava的Operator和Operator的原理。

  Rxjava的Operator常见的有map, flatMap, concat, merge之类的。这里就不介绍Operator的使用了,介绍一下其原理。介绍原理还是来看源码,以map为例。

  首先看一下使用map的例子:

Observable.create(new Observable.OnSubscribe<String>() {
    @Override
    public void call(Subscriber<? super String> subscriber) {
        subscriber.onNext("hello");
    }
})
.map(new Func1<String, String>() {
    @Override
    public String call(String s) {
        return s + "word";
    }
})
.subscribe(new Subscriber<String>() {
    @Override
    public void onCompleted() {

    }

    @Override
    public void onError(Throwable e) {

    }

    @Override
    public void onNext(String s) {
        Log.d("info-rx", s);
    }
});

  继续来看 map的定义:

    public final <R> Observable<R> map(Func1<? super T, ? extends R> func) {
        return lift(new OperatorMap<T, R>(func));
    }

  简单说一下Func1,其中的T表示传入的参数类型,R表示方法返回的参数类型。Operator的操作原理最核心的就是lift的实现。

    public final <R> Observable<R> lift(final Operator<? extends R, ? super T> operator) {
        return new Observable<R>(new OnSubscribe<R>() {
            @Override
            public void call(Subscriber<? super R> o) {
                try {
                    Subscriber<? super T> st = hook.onLift(operator).call(o);
                    try {
                        // new Subscriber created and being subscribed with so 'onStart' it
                        st.onStart();
                        onSubscribe.call(st);
                    } catch (Throwable e) {
                        // localized capture of errors rather than it skipping all operators 
                        // and ending up in the try/catch of the subscribe method which then
                        // prevents onErrorResumeNext and other similar approaches to error handling
                        Exceptions.throwIfFatal(e);
                        st.onError(e);
                    }
                } catch (Throwable e) {
                    Exceptions.throwIfFatal(e);
                    // if the lift function failed all we can do is pass the error to the final Subscriber
                    // as we don't have the operator available to us
                    o.onError(e);
                }
            }
        });
    }

  lift方法看起来太过复杂,稍作简化:

public final <R> Observable<R> lift(final Operator<? extends R, ? super T> operator) {
    return new Observable<R>(...);
}

  lift方法实际是产生一个新的 Observable。在map()调用之后,我们操作的就是新的Observable对象,我们可以把它取名为Observable$2,我们这里调用subscribe就是Observable$2.subscribe,继续看到subscribe里,重要的几个调用:

hook.onSubscribeStart(observable, observable.onSubscribe).call(subscriber);
return hook.onSubscribeReturn(subscriber);

  注意,这里的observable是Observable$2!!也就是说,这里的onSubscribe是,lift中定义的!!

  回过头来看lift方法中创建新Observable的过程:

return new Observable<R>(new OnSubscribe<R>() {
    @Override
    public void call(Subscriber<? super R> o) {
        try {
            Subscriber<? super T> st = hook.onLift(operator).call(o);
            try {
                // new Subscriber created and being subscribed with so 'onStart' it
                st.onStart();
                onSubscribe.call(st); //请注意我!! 这个onSubscribe是原始的OnSubScribe对象!!
            } catch (Throwable e) {
                // localized capture of errors rather than it skipping all operators 
                // and ending up in the try/catch of the subscribe method which then
                // prevents onErrorResumeNext and other similar approaches to error handling
                if (e instanceof OnErrorNotImplementedException) {
                    throw (OnErrorNotImplementedException) e;
                }
                st.onError(e);
            }
        } catch (Throwable e) {
            if (e instanceof OnErrorNotImplementedException) {
                throw (OnErrorNotImplementedException) e;
            }
            // if the lift function failed all we can do is pass the error to the final Subscriber
            // as we don't have the operator available to us
            o.onError(e);
        }
    }
});

  一定一定要注意这段函数执行的上下文!,这段函数中的onSubscribe对象指向的是外部类,也就是第一个Observable的onSubScribe!而不是Observable$2中的onSubscribe,接下来看:

Subscriber<? super T> st = hook.onLift(operator).call(o);

  这行代码,就是定义operator,生成一个经过operator操作过的Subscriber,看下OperatorMap这个类中的call方法: 

@Override
public Subscriber<? super T> call(final Subscriber<? super R> o) {
    return new Subscriber<T>(o) {

        @Override
        public void onCompleted() {
            o.onCompleted();
        }

        @Override
        public void onError(Throwable e) {
            o.onError(e);
        }

        @Override
        public void onNext(T t) {
            try {
                o.onNext(transformer.call(t));
            } catch (Throwable e) {
                Exceptions.throwIfFatal(e);
                onError(OnErrorThrowable.addValueAsLastCause(e, t));
            }
        }

    };
}

  没错,对传入的Subscriber做了一个代理,把转换后的值传入。这样就生成了一个代理的Subscriber,最后我们最外层的OnSubscribe对象对我们代理的Subscriber进行了调用: 

 @Override
public void call(Subscriber<? super String> subscriber) {
    //此处的subscriber就是被map包裹(wrapper)后的对象。
    subscriber.onNext("hello");
}

  然后这个subscriber传入到内部,链式的通知,最后通知到我们在subscribe函数中定义的对象。

  分析lift的原理,其实还是回到了一开始介绍的Observable必须要有订阅者进行订阅才能发射事件。lift方法会产生一个新的Observable,并且这个Observable位于原始Observable和后面的Subsriber之间,因此lift方法也需要提供一个新的Subscriber来使得新产生的Observable发射事件,这个新的Subsriber就是对事件链后方的Subscriber就行包装做一个代理。

  详细使用Rxjava可参见:

  1. 给 Android 开发者的 RxJava 详解

  2.Rxjava使用基础合集

 

Android 网络通信API的选择和实现实例

  Android开发网络通信一开始的时候使用的是AsyncTask封装HttpClient,没有使用原生的HttpURLConnection就跳到了Volley,随着OkHttp的流行又开始迁移到OkHttp上面,随着Rxjava的流行又了解了Retrofit,随着Retrofit的发展又从1.x到了2.x……。好吧,暂时到这里。

  那么的多的使用工具有时候有点眼花缭乱,今天来总结一下现在比较流行的基于OkHttp 和 Retrofit 的网络通信API设计方法。有些同学可能要想,既然都有那么好用的Volley和Okhttp了,在需要用到的地方创建一个Request然后交给RequestQueue(Volley的方式)或者 Call(Okhttp的方式)就行了吗,为什么还那么麻烦? 但是我认为这种野生的网络库的用法还是是有很多弊端(弊端就不说了,毕竟是总结新东西),在好的Android架构中都不会出现这样的代码。

  网络通信都是异步完成,设计网络API我觉得首先需要考虑异步结果的返回机制。基于Okhttp或Retrofit,我们考虑如何返回异步的返回结果,有几种方式:

  1. 直接返回:

  OkHttp 的返回方式:

OkHttpClient : OkHttpClient client = new OkHttpClient();

Request :  Request request = new Request.Builder()
                                        .url("https://api.github.com/repos/square/okhttp/issues")
                                        .header("User-Agent", "OkHttp Headers.java")
                                        .addHeader("Accept", "application/json; q=0.5")
                                        .addHeader("Accept", "application/vnd.github.v3+json")
                                        .build();
                                                 
//第一种
Response response = client.newCall(request).execute();
// 第二种
client.newCall(request).enqueue(new Callback() {
    @Override
    public void onFailure(Request request, Throwable throwable) {
                                      
    }
    @Override public void onResponse(Response response) throws IOException {

    }                                     
}

  Retrofit 的方式:

interface GitHubService {
  @GET("/repos/{owner}/{repo}/contributors")
  Call<List<Contributor>> repoContributors(
      @Path("owner") String owner,
      @Path("repo") String repo);
}
Call<List<Contributor>> call =
    gitHubService.repoContributors("square", "retrofit");

response = call.execute();

  上面的方式适用于野生的返回网络请求的内容。

  2. 使用事件总线(Otto,EventBus,RxBus(自己使用PublishSubject封装))

  代码来源:https://github.com/saulmm/Material-Movies

public interface MovieDatabaseAPI { /************Retrofit 1.x ,使用异步的方式返回 ****************/

    @GET("/movie/popular")
    void getPopularMovies(
        @Query("api_key") String apiKey,
        Callback<MoviesWrapper> callback);

    @GET("/movie/{id}")
    void getMovieDetail (
        @Query("api_key") String apiKey,
        @Path("id") String id,
        Callback<MovieDetail> callback
    );

    @GET("/movie/popular")
    void getPopularMoviesByPage(
        @Query("api_key") String apiKey,
        @Query("page") String page,
        Callback<MoviesWrapper> callback
    );

    @GET("/configuration")
    void getConfiguration (
        @Query("api_key") String apiKey,
        Callback<ConfigurationResponse> response
    );

    @GET("/movie/{id}/reviews")
    void getReviews (
        @Query("api_key") String apiKey,
        @Path("id") String id,
        Callback<ReviewsWrapper> response
    );

    @GET("/movie/{id}/images")
    void getImages (
        @Query("api_key") String apiKey,
        @Path("id") String movieId,
        Callback<ImagesWrapper> response
    );
}

  

public class RestMovieSource implements RestDataSource {

    private final MovieDatabaseAPI moviesDBApi;
    private final Bus bus; /***********使用了Otto**************/

    public RestMovieSource(Bus bus) {

        RestAdapter movieAPIRest = new RestAdapter.Builder() /*** Retrofit 1.x ***/
            .setEndpoint(Constants.MOVIE_DB_HOST)
            .setLogLevel(RestAdapter.LogLevel.HEADERS_AND_ARGS)
            .build();

        moviesDBApi = movieAPIRest.create(MovieDatabaseAPI.class);
        this.bus = bus;
    }

    @Override
    public void getMovies() {

        moviesDBApi.getPopularMovies(Constants.API_KEY, retrofitCallback);
    }

    @Override
    public void getDetailMovie(String id) {

        moviesDBApi.getMovieDetail(Constants.API_KEY, id,
            retrofitCallback);
    }

    @Override
    public void getReviews(String id) {

        moviesDBApi.getReviews(Constants.API_KEY, id,
            retrofitCallback);
    }

    @Override
    public void getConfiguration() {

        moviesDBApi.getConfiguration(Constants.API_KEY, retrofitCallback);
    }

    @Override
    public void getImages(String movieId) {

        moviesDBApi.getImages(Constants.API_KEY, movieId,
            retrofitCallback);
    }

    public Callback retrofitCallback = new Callback() { /******************这里统一的Callback,根据不同的返回值使用事件总线进行返回**************************/
        @Override
        public void success(Object o, Response response) {

            if (o instanceof MovieDetail) {

                MovieDetail detailResponse = (MovieDetail) o;
                bus.post(detailResponse);

            } else if (o instanceof MoviesWrapper) {

                MoviesWrapper moviesApiResponse = (MoviesWrapper) o;
                bus.post(moviesApiResponse);

            } else if (o instanceof ConfigurationResponse) {

                ConfigurationResponse configurationResponse = (ConfigurationResponse) o;
                bus.post(configurationResponse);

            } else if (o instanceof ReviewsWrapper) {

                ReviewsWrapper reviewsWrapper = (ReviewsWrapper) o;
                bus.post(reviewsWrapper);

            } else if (o instanceof ImagesWrapper) {

                ImagesWrapper imagesWrapper = (ImagesWrapper) o;
                bus.post(imagesWrapper);
            }
        }

        @Override
        public void failure(RetrofitError error) {

            System.out.printf("[DEBUG] RestMovieSource failure - " + error.getMessage());
        }
    };

    @Override
    public void getMoviesByPage(int page) {

        moviesDBApi.getPopularMoviesByPage(
            Constants.API_KEY,
            page + "",
            retrofitCallback
        );
    }
}

  

  3. 返回Observable(这里也可以考虑直接返回Observable 和间接返回Observable)

  直接的返回 Observable,在创建 apiService 的时候使用 Retrofit.create(MovieDatabaseAPI)就行了(见下面代码)

public interface MovieDatabaseAPI {

    @GET("/movie/popular")
    Observable<MovieWrapper> getPopularMovies(
        @Query("api_key") String apiKey,
        );

    @GET("/movie/{id}")
    Observable<MovideDetail> getMovieDetail (
        @Query("api_key") String apiKey,
        @Path("id") String id,
    );
}  

  间接返回Observable,这里参考了AndroidCleanArchitecture:

public interface RestApi {   /************定义API接口*****************/
  String API_BASE_URL = "http://www.android10.org/myapi/";

  /** Api url for getting all users */
  String API_URL_GET_USER_LIST = API_BASE_URL + "users.json";
  /** Api url for getting a user profile: Remember to concatenate id + 'json' */
  String API_URL_GET_USER_DETAILS = API_BASE_URL + "user_";

  /**
   * Retrieves an {@link rx.Observable} which will emit a List of {@link UserEntity}.
   */
  Observable<List<UserEntity>> userEntityList();

  /**
   * Retrieves an {@link rx.Observable} which will emit a {@link UserEntity}.
   *
   * @param userId The user id used to get user data.
   */
  Observable<UserEntity> userEntityById(final int userId);
}

  

/**** 使用Rx Observable 实现 RestApi 接口,实际调用的是 ApiConnection 里面的方法  ****/
public class RestApiImpl implements RestApi { /***注意这里没有使用Retrofit,而是对上面接口的实现***/

    private final Context context;
    private final UserEntityJsonMapper userEntityJsonMapper;

    /**
     * Constructor of the class
     *
     * @param context {@link android.content.Context}.
     * @param userEntityJsonMapper {@link UserEntityJsonMapper}.
     */
    public RestApiImpl(Context context, UserEntityJsonMapper userEntityJsonMapper) {
        if (context == null || userEntityJsonMapper == null) {
            throw new IllegalArgumentException("The constructor parameters cannot be null!!!");
        }
        this.context = context.getApplicationContext();
        this.userEntityJsonMapper = userEntityJsonMapper;
    }

    @RxLogObservable(SCHEDULERS)
    @Override
    public Observable<List<UserEntity>> userEntityList() {
        return Observable.create(subscriber -> {
            if (isThereInternetConnection()) {
                try {
                    String responseUserEntities = getUserEntitiesFromApi();
                    if (responseUserEntities != null) {
                        subscriber.onNext(userEntityJsonMapper.transformUserEntityCollection(
                                responseUserEntities));
                        subscriber.onCompleted();
                    } else {
                        subscriber.onError(new NetworkConnectionException());
                    }
                } catch (Exception e) {
                    subscriber.onError(new NetworkConnectionException(e.getCause()));
                }
            } else {
                subscriber.onError(new NetworkConnectionException());
            }
        });
    }

    @RxLogObservable(SCHEDULERS)
    @Override
    public Observable<UserEntity> userEntityById(final int userId) {
        return Observable.create(subscriber -> {
            if (isThereInternetConnection()) {
                try {
                    String responseUserDetails = getUserDetailsFromApi(userId);
                    if (responseUserDetails != null) {
                        subscriber.onNext(userEntityJsonMapper.transformUserEntity(responseUserDetails));
                        subscriber.onCompleted();
                    } else {
                        subscriber.onError(new NetworkConnectionException());
                    }
                } catch (Exception e) {
                    subscriber.onError(new NetworkConnectionException(e.getCause()));
                }
            } else {
                subscriber.onError(new NetworkConnectionException());
            }
        });
    }

    private String getUserEntitiesFromApi() throws MalformedURLException {
        return ApiConnection.createGET(RestApi.API_URL_GET_USER_LIST).requestSyncCall();
    }

    private String getUserDetailsFromApi(int userId) throws MalformedURLException {
        String apiUrl = RestApi.API_URL_GET_USER_DETAILS + userId + ".json";
        return ApiConnection.createGET(apiUrl).requestSyncCall();
    }

    /**
     * Checks if the device has any active internet connection.
     *
     * @return true device with internet connection, otherwise false.
     */
    private boolean isThereInternetConnection() {
        boolean isConnected;

        ConnectivityManager connectivityManager =
                (ConnectivityManager) this.context.getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
        isConnected = (networkInfo != null && networkInfo.isConnectedOrConnecting());

        return isConnected;
    }
}

  

public class ApiConnection implements Callable<String> {  /***********************网络接口的实际实现********************************/

    private static final String CONTENT_TYPE_LABEL = "Content-Type";
    private static final String CONTENT_TYPE_VALUE_JSON = "application/json; charset=utf-8";

    private URL url;
    private String response;

    private ApiConnection(String url) throws MalformedURLException {
        this.url = new URL(url);
    }

    public static ApiConnection createGET(String url) throws MalformedURLException {
        return new ApiConnection(url);
    }

    /**
     * Do a request to an api synchronously.
     * It should not be executed in the main thread of the application.
     *
     * @return A string response
     */
    @Nullable
    public String requestSyncCall() {
        connectToApi();
        return response;
    }

    private void connectToApi() {
        OkHttpClient okHttpClient = this.createClient(); /*******************使用OKhttp的实现*******************/
        final Request request = new Request.Builder()
                .url(this.url)
                .addHeader(CONTENT_TYPE_LABEL, CONTENT_TYPE_VALUE_JSON)
                .get()
                .build();

        try {
            this.response = okHttpClient.newCall(request).execute().body().string();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private OkHttpClient createClient() {
        final OkHttpClient okHttpClient = new OkHttpClient();
        okHttpClient.setReadTimeout(10000, TimeUnit.MILLISECONDS);
        okHttpClient.setConnectTimeout(15000, TimeUnit.MILLISECONDS);

        return okHttpClient;
    }

    @Override
    public String call() throws Exception {
        return requestSyncCall();
    }
}

  这里简单总结了一下OkHttp和Retrofit该如何封装,这样的封装放在整个大的代码框架中具有很好的模块化效果。对于使用MVP架构或者类似架构的APP,良好的网络接口模块封装是非常重要的。

 

OkHttp,Retrofit 1.x – 2.x 基本使用

  Square 为广大开发者奉献了OkHttp,Retrofit1.x,Retrofit2.x,运用比较广泛,这三个工具有很多相似之处,初学者可能会有一些使用迷惑。这里来总结一下它们的一些基本使用和一些细微差别。

/**************
Retrofit 基本使用方法

Retrofit 到底是返回什么? void, Observable, Call?

*************/
/********************************************Retrofit****************************************************************/
/*** 同步调用的方式  ****/
interface GitHubService {
  @GET("/repos/{owner}/{repo}/contributors")
  List<Contributor> repoContributors(
      @Path("owner") String owner,
      @Path("repo") String repo);
} 

List<Contributor> contributors =
    gitHubService.repoContributors("square", "retrofit");
/***** 异步调用的方式 仅限于 Retrofit 1.x !!!!!!! *****/
interface GitHubService {
  @GET("/repos/{owner}/{repo}/contributors")
  void repoContributors(
      @Path("owner") String owner,
      @Path("repo") String repo,
      Callback<List<Contributor>> cb); // 异步调用添加 CallBack
} 

service.repoContributors("square", "retrofit", new Callback<List<Contributor>>() {
  @Override void success(List<Contributor> contributors, Response response) {
    // ...
  }


  @Override void failure(RetrofitError error) {
    // ...
  }
});

/**** Rxjava 方式 ****/
interface GitHubService {
  @GET("/repos/{owner}/{repo}/contributors")
  Observable<List<Contributor>> repoContributors(
      @Path("owner") String owner,
      @Path("repo") String repo);
} 
// 调用
gitHubService.repoContributors("square", "retrofit")
    .subscribe(new Action1<List<Contributor>>() {
      @Override public void call(List<Contributor> contributors) {
        // ...
      }
    });
    
/*******************************注意以下三个Callback的不同***************************************/
	
// Retrofit Callback Version 1.9 
public interface Callback<T> {

  /** Successful HTTP response. */
  void success(T t, Response response);

  /**
   * Unsuccessful HTTP response due to network failure, non-2XX status code, or unexpected
   * exception.
   */
  void failure(RetrofitError error);
}
// Retrofit Callback Version 2.0	!!!!!!!!!
public interface Callback<T> {
  /** Successful HTTP response. */
  void onResponse(Response<T> response, Retrofit retrofit);

  /** Invoked when a network or unexpected exception occurred during the HTTP request. */
  void onFailure(Throwable t);
}
// OkHttp	
public interface Callback {
  void onFailure(Request request, IOException e);

  void onResponse(Response response) throws IOException; // 注意参数不同
}



/*********************************回顾一下Okhttp的调用方式********************************************/

//1. 创建 
OkHttpClient : OkHttpClient client = new OkHttpClient();
//2. 创建 
Request :  Request request = new Request.Builder()
									    .url("https://api.github.com/repos/square/okhttp/issues")
										.header("User-Agent", "OkHttp Headers.java")
										.addHeader("Accept", "application/json; q=0.5")
										.addHeader("Accept", "application/vnd.github.v3+json")
										.build();
												
//3. 使用 client 执行请求(两种方式):  
//第一种,同步执行
Response response = client.newCall(request).execute();
// 第二种,异步执行方式
client.newCall(request).enqueue(new Callback() { 
    @Override 
    public void onFailure(Request request, Throwable throwable) {
    // 复写该方法
											 
    }
    @Override public void onResponse(Response response) throws IOException {
	// 复写该方法
    }									   
}
	
	
/***********************************Retrofit 1.0 不能获得 Header 或者整个 Body*****************************************/
/**********引入 Call , 每个Call只能调用一次,可以使用Clone方法来生成一次调用多次,使用Call既可以同步也可以异步*********/

interface GitHubService {
  @GET("/repos/{owner}/{repo}/contributors")
  Call<List<Contributor>> repoContributors(
      @Path("owner") String owner,
      @Path("repo") String repo);
}

Call<List<Contributor>> call =
    gitHubService.repoContributors("square", "retrofit");

response = call.execute(); /*************** 同步的方式调用,注意这里返回了 Response 后面会提到 ********************/

// This will throw IllegalStateException: 每个Call只能执行一次
response = call.execute();

Call<List<Contributor>> call2 = call.clone(); // 调用Clone之后又可以执行
// This will not throw:
response = call2.execute();

/************************ 异步的方式调用 *********************************/

Call<List<Contributor>> call =
    gitHubService.repoContributors("square", "retrofit");

call.enqueue(new Callback<List<Contributor>>() {
  @Override void onResponse(/* ... */) {
    // ...
  }

  @Override void onFailure(Throwable t) {
    // ...
  }
});

/****************************引入 Response,获取返回的RawData,包括:response code, response message, headers**********************************/

class Response<T> {
  int code();
  String message();
  Headers headers();

  boolean isSuccess(); 
  T body();
  ResponseBody errorBody(); 
  com.squareup.okhttp.Response raw();
}

interface GitHubService {
  @GET("/repos/{owner}/{repo}/contributors")
  Call<List<Contributor>> repoContributors(
      @Path("owner") String owner,
      @Path("repo") String repo);
} 

Call<List<Contributor>> call = 
    gitHubService.repoContributors("square", "retrofit");
Response<List<Contributor>> response = call.execute(); 

/*********************************** Dynamic URL *****************************************/

interface GitHubService {
  @GET("/repos/{owner}/{repo}/contributors")
  Call<List<Contributor>> repoContributors(
      @Path("owner") String owner,
      @Path("repo") String repo);

  @GET
  Call<List<Contributor>> repoContributorsPaginate(
      @Url String url);// 直接填入 URL 而不是在GET中替换字段的方式
}

/*************************************根据返回值实现重载*****************************************************/
interface SomeService {
  @GET("/some/proto/endpoint")
  Call<SomeProtoResponse> someProtoEndpoint(); // SomeProtoResponse

  @GET("/some/json/endpoint")
  Call<SomeJsonResponse> someJsonEndpoint(); // SomeJsonResponse
}

interface GitHubService {
  @GET("/repos/{owner}/{repo}/contributors")
  Call<List<Contributor>> repoContributors(..);

  @GET("/repos/{owner}/{repo}/contributors")
  Observable<List<Contributor>> repoContributors2(..);

  @GET("/repos/{owner}/{repo}/contributors")
  Future<List<Contributor>> repoContributors3(..); // 可以返回 Future
}

/******************************************Retrofit 1.x Interceptor,添加头部信息的时候经常用到Interceptor*************************************************************/
    RestAdapter.Builder builder = new RestAdapter.Builder().setRequestInterceptor(new RequestInterceptor() {
        @Override
        public void intercept(RequestFacade request) {
            request.addHeader("Accept", "application/json;versions=1");
        }
    });


/******************************************Retrofit 2.x Interceptor**************************************************/            
    
OkHttpClient client = new OkHttpClient();
client.interceptors().add(new Interceptor() {    
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request original = chain.request();
        
        Request request = original.newBuilder()
                                  .header("Accept", "application/json")
                                  .header("Authorization", "auth-token")
                                  .method(original.method(), original.body())
                                  .build();
       
       Response response = chain.proceed(request);
       return response;      
        
    }   
}

Retrofit retrofit = Retrofit.Builder()
            .baseUrl("https://your.api.url/v2/")
            .client(client).build();


/***************************************异步实例*********************************************/
public interface APIService {

    @GET("/users/{user}/repos")
    Call<List<Repo>> listRepos(@Path("user") String user);

    @GET("/users/{user}/repos")
    Call<String> listReposStr(@Path("user") String user);
//错误,不能这样使用异步
//    @GET("/users/{user}/repos")
//    void listRepos(@Path("user") String user, Callback<List<Repo>> callback);
}

private void prepareServiceAPI() {
    //For logging
    HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
    logging.setLevel(HttpLoggingInterceptor.Level.BODY);

    OkHttpClient client = new OkHttpClient();
    client.interceptors().add(new MyInterceptor());
    client.interceptors().add(logging);
	// setUp Retrofit 
    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl("https://api.github.com")
            //.addConverterFactory(new ToStringConverterFactory())
            .addConverterFactory(GsonConverterFactory.create())
            .client(client)
            .build();

    service = retrofit.create(APIService.class);
}
// 异步调用
public void execute() {
    Call<List<Repo>> call = service.listRepos("pasha656");
    call.enqueue(new Callback<List<Repo>>() {
        @Override
        public void onResponse(Response<List<Repo>> response, Retrofit retrofit) {

            if (response.isSuccess()) {
                if (!response.body().isEmpty()) {
                    StringBuilder sb = new StringBuilder();
                    for (Repo r : response.body()) {
                        sb.append(r.getId()).append(" ").append(r.getName()).append(" /n");
                    }
                    activity.setText(sb.toString());
                }
            } else {
                APIError error = ErrorUtils.parseError(response, retrofit);
                Log.d("Pasha", "No succsess message "+error.getMessage());
            }


            if (response.errorBody() != null) {
                try {
                    Log.d("Pasha", "Error "+response.errorBody().string());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        @Override
        public void onFailure(Throwable t) {
            Log.d("Pasha", "onFailure "+t.getMessage());
        }
    });
}

  

Dagger2 生成代码学习

  接上一篇文章介绍了Dagger2的初步使用,相信刚接触的人会觉得很奇怪,怎么会有很多自己没有定义的代码出现,为什么Component的创建方式是那样的。为了搞清楚这些东西,我们需要查看一下Dagger2 生成的源代码。Dagger2 是一个DI框架,通过学习生成的代码也可以更好的理解Dagger2是如何做依赖注入的。

  将上一篇文章中的工程在SublimeText中打开,结构如下图:

Dagger2 生成代码学习

  可以看到AppComponent 生成了 DaggerAppComponent,Dagger2的生成规则中,我们自定义的Component生成之后会加上前缀“Dagger”。此外Module中的每个@Provides 方法都会生成一个Factory 类,命名规则也是很规律的。这里Dagger一共为我们生成了6个类:DaggerAppComponent / AppModule_ProvideApplicationFactory /ReposListActivity_MembersInjector / GithubApiModule_ProvideRetrofitFactory / GithubApiModule_ProvideOkHttpClientFactory / GithubApiModule_ProvideGitHubServiceFactory。接下来我们看看这些类具体是什么样的。

  首先来看一下DaggerAppComponent:

@Generated("dagger.internal.codegen.ComponentProcessor")
public final class DaggerAppComponent implements AppComponent {
  private Provider<Application> provideApplicationProvider;
  private Provider<OkHttpClient> provideOkHttpClientProvider;
  private Provider<Retrofit> provideRetrofitProvider;
  private Provider<GithubApiService> provideGitHubServiceProvider;
  private MembersInjector<ReposListActivity> reposListActivityMembersInjector;

  private DaggerAppComponent(Builder builder) {  
    assert builder != null;
    initialize(builder);
  }

  public static Builder builder() {  
    return new Builder();
  }

  private void initialize(final Builder builder) {  
    this.provideApplicationProvider = AppModule_ProvideApplicationFactory.create(builder.appModule);
    this.provideOkHttpClientProvider = GithubApiModule_ProvideOkHttpClientFactory.create(builder.githubApiModule);
    this.provideRetrofitProvider = GithubApiModule_ProvideRetrofitFactory.create(builder.githubApiModule, provideApplicationProvider, provideOkHttpClientProvider);
    this.provideGitHubServiceProvider = GithubApiModule_ProvideGitHubServiceFactory.create(builder.githubApiModule, provideRetrofitProvider);
    this.reposListActivityMembersInjector = ReposListActivity_MembersInjector.create((MembersInjector) MembersInjectors.noOp(), provideGitHubServiceProvider);
  }

  @Override
  public void inject(ReposListActivity activity) {  
    reposListActivityMembersInjector.injectMembers(activity);
  }

  public static final class Builder {
    private AppModule appModule;
    private GithubApiModule githubApiModule;
  
    private Builder() {  
    }
  
    public AppComponent build() {  
      if (appModule == null) {
        throw new IllegalStateException("appModule must be set");
      }
      if (githubApiModule == null) {
        this.githubApiModule = new GithubApiModule();
      }
      return new DaggerAppComponent(this);
    }
  
    public Builder appModule(AppModule appModule) {  
      if (appModule == null) {
        throw new NullPointerException("appModule");
      }
      this.appModule = appModule;
      return this;
    }
  
    public Builder githubApiModule(GithubApiModule githubApiModule) {  
      if (githubApiModule == null) {
        throw new NullPointerException("githubApiModule");
      }
      this.githubApiModule = githubApiModule;
      return this;
    }
  }
}

  我们在Application中实例化AppComponent的时候是这样的:

appComponent = DaggerAppComponent.builder()
        .githubApiModule(new GithubApiModule())
        .appModule(new AppModule(this))
        .build();

  第一次见的时候肯定觉得一团雾水,为什么要这样写呢? 通过上面DaggerAppcomponent代码可以看出,使用了建造者模式。我们回顾一下AppComponent是怎么样的:

@Component(modules = { AppModule.class, GithubApiModule.class})
public interface AppComponent {
    // inject what
    void inject(ReposListActivity activity);
}

  @component 后面modules包含了两个类,在生成的DaggerAppComponent 中的内部类 Builder中,AppModule.class 和 GithubApiModule.class 成为了其成员,并且提供了set方法。在Application中实例化DaggerAppComponent的过程中其实就是调用了其set方法来设置依赖。此外在DaggerAppComponent各个@Provides 注解的方法返回类型都是其一个成员变量,并在Initialize()方法中创建。此外还有一个MembersInjector成员,DaggerAppComponent也要负责创建它。

@Generated("dagger.internal.codegen.ComponentProcessor")
public final class ReposListActivity_MembersInjector implements MembersInjector<ReposListActivity> {
  private final MembersInjector<BaseActivity> supertypeInjector;
  private final Provider<GithubApiService> githubApiServiceProvider;

  public ReposListActivity_MembersInjector(MembersInjector<BaseActivity> supertypeInjector, Provider<GithubApiService> githubApiServiceProvider) {  
    assert supertypeInjector != null;
    this.supertypeInjector = supertypeInjector;
    assert githubApiServiceProvider != null;
    this.githubApiServiceProvider = githubApiServiceProvider;
  }

  @Override
  public void injectMembers(ReposListActivity instance) {  
    if (instance == null) {
      throw new NullPointerException("Cannot inject members into a null reference");
    }
    supertypeInjector.injectMembers(instance);
    instance.githubApiService = githubApiServiceProvider.get();
  }

  public static MembersInjector<ReposListActivity> create(MembersInjector<BaseActivity> supertypeInjector, Provider<GithubApiService> githubApiServiceProvider) {  
      return new ReposListActivity_MembersInjector(supertypeInjector, githubApiServiceProvider);
  }
}

  ReposListActivity_MembersInjector 中通过 injectMembers 方法获取到 ReposListActivity 的实例(对应的就是 ReposListActivityComponent中的inject),然后进行赋值。从这种赋值的方式来看被@Inject注解的注入对象不能是private的。在看赋值是通过 githubAPiServiceProvider.get() 方法获取的,githubApiServiceProvider是一个工厂类,我们来看看这个工厂类是怎么样的:

@Generated("dagger.internal.codegen.ComponentProcessor")
public final class GithubApiModule_ProvideGitHubServiceFactory implements Factory<GithubApiService> {
  private final GithubApiModule module;
  private final Provider<Retrofit> retrofitProvider;

  public GithubApiModule_ProvideGitHubServiceFactory(GithubApiModule module, Provider<Retrofit> retrofitProvider) {  
    assert module != null;
    this.module = module;
    assert retrofitProvider != null;
    this.retrofitProvider = retrofitProvider;
  }。

  @Override
  public GithubApiService get() {  
    GithubApiService provided = module.provideGitHubService(retrofitProvider.get());
    if (provided == null) {
      throw new NullPointerException("Cannot return null from a non-@Nullable @Provides method");
    }
    return provided;
  }

  public static Factory<GithubApiService> create(GithubApiModule module, Provider<Retrofit> retrofitProvider) {  
    return new GithubApiModule_ProvideGitHubServiceFactory(module, retrofitProvider);
  }
}

  GithubApiModule_ProvideGitHubServiceFactory 类中有两个成员,一个是提供(@Provides)GithubService 所在的 module 类,一个是创建GithubService方法所需参数的retrofitProvider(从这里可以看出Dagger2需要创建retrofitProvider的工厂)。然后通过 module.provdeGitHubService()方法来创建 GithubApiService实例,这样最终穿件了在ReposListActivity中注入的依赖实例。

  Dagger2 入门有点绕,也没找到什么系统性的资料,但是可以通过查看框架自动生成的类来加深理解,方便大家使用。使用到dependencies和SubComponent注解的时候生成的代码就比较多了,本来也想捋捋的,但是原理和上面的都是一样的,大家自己去看吧。

 

Dagger2 使用初步

  Dagger2 是一个Android依赖注入框架,由谷歌开发,最早的版本Dagger1 由Square公司开发。依赖注入框架主要用于模块间解耦,提高代码的健壮性和可维护性。Dagger 这个库的取名不仅仅来自它的本意“匕首”,同时也暗示了它的原理。Jake Wharton 在对 Dagger 的介绍中指出,Dagger 即 DAG-er,这里的 DAG 即数据结构中的 DAG——有向无环图(Directed Acyclic Graph)。也就是说,Dagger 是一个基于有向无环图结构的依赖注入库,因此Dagger的使用过程中不能出现循环依赖。

  Android开发从一开始的MVC框架,到MVP,到MVVM,不断变化。现在MVVM的data-binding还在实验阶段,传统的MVC框架Activity内部可能包含大量的代码,难以维护,现在主流的架构还是使用MVP(Model + View + Presenter)的方式。但是 MVP 框架也有可能在Presenter中集中大量的代码,引入DI框架Dagger2 可以实现 Presenter 与 Activity 之间的解耦,Presenter和其它业务逻辑之间的解耦,提高模块化和可维护性。

  说了那么多,那什么是依赖呢?如果在 Class A 中,有 Class B 的实例,则称 Class A 对 Class B 有一个依赖。例如下面类 Human 中用到一个 Father 对象,我们就说类 Human 对类 Father 有一个依赖(参考)。

public class Human {
    ...
    Father father;
    ...
    public Human() {
        father = new Father();
    }
}

  那什么又是依赖注入呢,依赖注入就是非自己主动初始化依赖,而通过外部来传入依赖的方式,简单来说就是不使用 new 来创建依赖对象。使用 Dagger2 创建依赖对象,我们就不用手动初始化了。个人认为 Dagger2 和 MVP 架构是比较不错的搭配,Activity 依赖的 Presenter 可以使用该DI框架直接生成,实现解耦,简单的使用方式如下:

public class MainActivity extends BaseActivity {
      @Inject
       MainActivityPresenter presenter;

     ...  
    
}

  上面这些主要是对DI框架有一个初步全局的了解,下面来看看Dagger2的基本内容。Dagger2 通过注解来生成代码,定义不同的角色,主要的注解有:@Inject、@Module 、@Component 、@Provides 、@Scope 、@SubComponent 等。

  @Inject: 通常在需要依赖的地方使用这个注解。换句话说,你用它告诉Dagger这个类或者字段需要依赖注入。这样,Dagger就会构造一个这个类的实例并满足他们的依赖。
  @Module: Modules类里面的方法专门提供依赖,所以我们定义一个类,用@Module注解,这样Dagger在构造类的实例的时候,就知道从哪里去找到需要的 依赖。modules的一个重要特征是它们设计为分区并组合在一起(比如说,在我们的app中可以有多个组成在一起的modules)。
  @Provides: 在modules中,我们定义的方法是用这个注解,以此来告诉Dagger我们想要构造对象并提供这些依赖。
  @Component: Components从根本上来说就是一个注入器,也可以说是@Inject和@Module的桥梁,它的主要作用就是连接这两个部分。 Components可以提供所有定义了的类型的实例,比如:我们必须用@Component注解一个接口然后列出所有的   @Modules组成该组件,如 果缺失了任何一块都会在编译的时候报错。所有的组件都可以通过它的modules知道依赖的范围。
  @Scope: Scopes可是非常的有用,Dagger2可以通过自定义注解限定注解作用域。后面会演示一个例子,这是一个非常强大的特点,因为就如前面说的一样,没必要让每个对象都去了解如何管理他们的实例。

  介绍的差不多了,来看一个简单的实例,该实例参考了该项目和其相关的文章。该实例只是讲解怎么使用dagger2,并不涉及MVP,同时结合了当前流行的 Retrofit 2.0 、RxAndroid 等库(回想刚开始的时候做Android自己封装AsyncTask和使用BroadCast简直和原始人刀耕火种无异啊)。

  首先来看看整个工程的结构:

Dagger2 使用初步

  在 gradle 配置文件中首先引入需要的库:

apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt' // 注释处理


android {
    compileSdkVersion 23
    buildToolsVersion "23.0.2"

    defaultConfig {
        applicationId "com.zyp.archi.githubclient_mdr_0"
        minSdkVersion 16
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
    }

//    packagingOptions {
//        exclude 'META-INF/services/javax.annotation.processing.Processor'
//    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:23.1.1'

    compile 'com.android.support:recyclerview-v7:23.1.1' // RecyclerView

    compile 'com.jakewharton:butterknife:7.0.1' // 标注

    compile 'com.google.dagger:dagger:2.0.2' // dagger2
    compile 'com.google.dagger:dagger-compiler:2.0.2' // dagger2
    provided 'org.glassfish:javax.annotation:10.0-b28'

    compile 'io.reactivex:rxandroid:1.1.0' // RxAndroid
    compile 'io.reactivex:rxjava:1.1.0' // 推荐同时加载RxJava

    compile 'com.squareup.retrofit:retrofit:2.0.0-beta2' // Retrofit网络处理
    compile 'com.squareup.retrofit:adapter-rxjava:2.0.0-beta2' // Retrofit的rx解析库
    compile 'com.squareup.retrofit:converter-gson:2.0.0-beta2' // Retrofit的gson库

}

  由于 Dagger 使用 apt 生成代码,在Project gradle中还需要加入:

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:1.5.0'
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

  网络相关的API在Application中生成,注意别忘了在Manifest文件中添加 <uses-permission android:name=”android.permission.INTERNET”/> 和 绑定 android:name= .AppApplication 。

public class AppApplication extends Application{

    private static AppApplication sInstance;
    private AppComponent appComponent;

    @Override
    public void onCreate(){
        super.onCreate();
        this.sInstance = this;
        setupCompoent();
    }

    private void setupCompoent(){
        appComponent = DaggerAppComponent.builder()
                .githubApiModule(new GithubApiModule())
                .appModule(new AppModule(this))
                .build();
    }

    public static AppApplication getsInstance(){
        return sInstance;
    }

    public AppComponent getAppComponent(){
        return appComponent;
    }
}

  在 Application 中创建了 AppComponent 实例,并可以获取到,注意Appcomponent的实例化方式,Dagger + AppComponent.builder().somModule(new somModule()).build() ,注意写法,大小写也要注意,以后介绍Apt生成的原码的时候就清楚了为什么要这样写,我相信这里也是一个一开始不好理解的地方。接下来看看AppComponent是什么鬼。

@Component(modules = { AppModule.class, GithubApiModule.class})
public interface AppComponent {
    // inject what
    void inject(ReposListActivity activity);
}

  AppCompoent 是一个 Interface,通过 @Component 添加了两个 Module : AppModule、GithubApiModule。此外还有一个inject方法,其中的参数表示要注入的位置(先打个预防针,Component中的方法还可以起到暴露资源,实现Component中的“继承”的作用)。接下来看看AppModule 和 GithubApiModule。

@Module
public class AppModule {
    private Application application;

    public AppModule(Application application){
        this.application=application;
    }

    @Provides
    public Application provideApplication(){
        return application;
    }
}
@Module
public class GithubApiModule {

    @Provides
    public OkHttpClient provideOkHttpClient() {
        OkHttpClient okHttpClient = new OkHttpClient();
        okHttpClient.setConnectTimeout(60 * 1000, TimeUnit.MILLISECONDS);
        okHttpClient.setReadTimeout(60 * 1000, TimeUnit.MILLISECONDS);
        return okHttpClient;
    }

    @Provides
    public Retrofit provideRetrofit(Application application, OkHttpClient okHttpClient){
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(application.getString(R.string.api_github))
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) // 添加Rx适配器
                .addConverterFactory(GsonConverterFactory.create()) // 添加Gson转换器
                .client(okHttpClient)
                .build();
        return retrofit;
    }

    @Provides
    protected GithubApiService provideGitHubService(Retrofit retrofit) {

        return retrofit.create(GithubApiService.class);
    }
}

  这两个Module很简单,但是也包含很多东西。首先作为Module,需要使用@Module注解,在被@Module注解修饰的类内部,使用@Provides注解来表明可以提供的依赖对象。需要注意的是,有些由@Provides 提供的方法需要输入参数,这些参数是怎么来的呢?这对于刚刚接触的新手来说有点棘手。这里就先说了,这些需要传入的参数需要其它用@Provides注解修饰的方法生成,比如在GithubModule.class 中的 provideGitHubService(Retrofit retrofit) 方法中的参数就是由另外一个 @Provides 注解修饰的方法生成的,这里就是public Retrofit provideRetrofit(Application application, OkHttpClient okHttpClient),那么这个provideRetrofit()方法中的参数又是怎么来的呢?请读者自己去找。

  此外为什么GithubModule会这样设计,有没有更加单方法?试想当有多种 ApiService 需要用到的时候,OkhttpClient中的超时设置需要不同的时候,Retrofit 实例的 Converter需要不同的时候我们该如何应对?大家可以思考一下,我也在思考。

  这里使用到了Retrofit,Retrofit的基本使用方法见这里,虽然Retrofit2.0 还处于 beta 阶段,但是这里还是任性的使用了,Retrofit2.0 新特性和基本使用方式见这里

public interface GithubApiService {
    @GET("/users/{user}/repos")
    Observable<ArrayList<Repo>> getRepoData(@Path("user") String user);
}

  GithubAPiService 中定义了一个访问需要访问的接口,注意这里返回了一个Observable对象,这里使用到了 RxJava 的相关知识,RxJava的好处也很多,这里就不解释了,有兴趣入门参考见这里,此外建议大家参阅这里,其实都还不够。

  好了准备工作基本上做好了,现在来看看Activity怎么写。首先定义 BaseActivity,这里提一下基本的Android应用开发架构,稍微有经验的开发者肯定都是会对Activity 进行分层的,将一些公共的代码放在BaseActivity中,当然BaseActivity也许不止一种,或者不止一层,这就要具体问题具体分析了,此外一般还会引入utils 包来定义一些公共的静态方法来实现对这个应用的AOP,具体可以参考这里。这里BaseActivity 提供了ButterKnife依赖注入,提供了Component建立的方法和布局文件获取方法。

public abstract class BaseActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(getLayoutId());
        ButterKnife.bind(this);
        setupActivityComponent(AppApplication.getsInstance().getAppComponent());
    }

    protected abstract void setupActivityComponent(AppComponent appComponent);
    protected abstract int getLayoutId();

}

  这里设计了两个Activity,一个是MainActivity 一个是 ReposListActivity。MainActivity 提供一个按钮,点击则跳转到ReposListActivity,显示某一个人的GitHub账户上的信息。

public class MainActivity extends BaseActivity {

    @OnClick(R.id.showButton)
    public void onShowRepositoriesClick() {
        startActivity(new Intent(this, ReposListActivity.class));
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

    }

    @Override
    public int getLayoutId(){
        return R.layout.activity_main;
    }

    @Override
    public void setupActivityComponent(AppComponent appComponent){

    }
}
/**
 * Created by zhuyp on 2016/1/10.
 */
public class ReposListActivity extends BaseActivity {

    @Bind(R.id.repos_rv_list)
    RecyclerView mRvList;

    @Bind(R.id.pbLoading)
    ProgressBar pbLoading;


    @Inject
    GithubApiService githubApiService;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        initView();
    }

    @Override
    public int getLayoutId(){
        return R.layout.activity_repos_list;
    }

    @Override
    public void setupActivityComponent(AppComponent appComponent){
        appComponent.inject(this);
    }


    private void initView(){
        LinearLayoutManager manager = new LinearLayoutManager(this);
        manager.setOrientation(LinearLayoutManager.VERTICAL);
        mRvList.setLayoutManager(manager);

        ListAdapter adapter = new ListAdapter();
        mRvList.setAdapter(adapter);
        loadData(adapter);
    }

    private void loadData(final ListAdapter adapter){
        showLoading(true);
        githubApiService.getRepoData(getUser())
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new SimpleObserver<ArrayList<Repo>>() {
                    @Override
                    public void onNext(ArrayList<Repo> repos) {
                        showLoading(false);
                        adapter.setRepos(repos);
                    }
                    @Override
                    public void onError(Throwable e){
                        showLoading(false);
                    }
                });
    }

    private String getUser(){
        return "bird1015";
    }

    public void showLoading(boolean loading) {
        Log.i("info",loading + " Repos");
        pbLoading.setVisibility(loading ? View.VISIBLE : View.GONE);
    }
}

  简单说一下 ReposListActivity 中的依赖注入,@Inject 注入了GithubApiService,在loadData() 方法中读取对应用户GitHub上的信息并返回。这里我们只提取了很少一部分信息,并显示在RecyclerView中。由于本篇文章不是介绍Rxjava在Android中的应用,RxJava 相关就不做具体解释了,有兴趣可以从前面提到过的资料中去了解。从上面代码可以看到程序的处理逻辑异常清晰简单,这就是Rxjava的威力所在,但是这也是一个不好上手的东西,建议还是根据参考资料学习一下吧,不管能否实际运用,至少能看得懂啊。

  这只是Dagger2的一个入门实例代码,其实要搞懂Dagger需要看生成的源码(后面会写文章介绍),希望我能尽快再写一至两篇总结一下其它特性,比如 SubComponent ,Dependencies,Scope等。上面代码中还用到的资源我就直接贴在下面了。

public class ListAdapter extends RecyclerView.Adapter<ListAdapter.RepoViewHolder> {

    private ArrayList<Repo> mRepos;

    public ListAdapter() {
        mRepos = new ArrayList<>();
    }

    public void setRepos(ArrayList<Repo> repos) {
        mRepos = repos;
        notifyItemInserted(mRepos.size() - 1);
    }

    @Override
    public RepoViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.item_repo, parent, false);
        return new RepoViewHolder(view);
    }

    @Override
    public void onBindViewHolder(RepoViewHolder holder, int position) {
        holder.bindTo(mRepos.get(position));
    }

    @Override
    public int getItemCount() {
        return mRepos.size();
    }

    public static class RepoViewHolder extends RecyclerView.ViewHolder {

        @Bind(R.id.item_iv_repo_name)
        TextView mIvRepoName;
        @Bind(R.id.item_iv_repo_detail)
        TextView mIvRepoDetail;

        public RepoViewHolder(View itemView) {
            super(itemView);
            ButterKnife.bind(this, itemView);
        }

        public void bindTo(Repo repo) {
            mIvRepoName.setText(repo.name );
            mIvRepoDetail.setText(String.valueOf(repo.description + "(" + repo.language + ")"));
        }
    }
}
/**
 * Created by zhuyp on 2016/1/10.
 */
public class Repo {
    public String name; // 库的名字
    public String description; // 描述
    public String language; // 语言
//  public String testNullField; // 试错
}
/**
 * Created by zhuyp on 2016/1/10.
 */
public class SimpleObserver<T> implements Observer<T> {
    @Override
    public void onCompleted() {

    }

    @Override
    public void onError(Throwable e) {

    }

    @Override
    public void onNext(T t) {

    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin">

    <Button
        android:id="@+id/showButton"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/main_goto_activity"/>

</LinearLayout>

activity_repo_list.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="vertical">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/repos_rv_list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

    <ProgressBar
        android:id="@+id/pbLoading"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>

item_repo.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              xmlns:tools="http://schemas.android.com/tools"
              android:orientation="vertical">

    <TextView
        android:id="@+id/item_iv_repo_name"
        tools:text="Repos name"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="8dp"
        android:textColor="@android:color/holo_purple"
        android:textSize="22sp"/>

    <TextView
        android:id="@+id/item_iv_repo_detail"
        tools:text="Details"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="8dp"
        android:textSize="14sp"/>

</LinearLayout>

strings.xml

<resources>
    <string name="app_name">GithubClient_mdr_0</string>

    <string name="main_mock_data">自定义数据(测试)</string>
    <string name="main_goto_activity">跳转列表</string>
    <string name="api_github">https://api.github.com</string>

</resources>

 

Android Studio1.4.x JNI开发基础 – 简单实例

  接上一篇,搭建好基于Android Studio的环境之后,编写native代码相对来说也比较简单了。在Android上编写Native代码和在Linux编写C/C++代码还是有区别,Native代码一般需要与JVM交互数据,需要遵循一定的规范,本文来介绍一下基本的JNI代码写法。

  我们还是从实例出发,配置好Android Studio工程之后,我们需要创建jni目录和在jni目下创建c/c++文件和相应的头文件,创建方式见下图。

Android Studio1.4.x JNI开发基础 - 简单实例

Android Studio1.4.x JNI开发基础 - 简单实例

  在实例工程中我们创建了NdkSample.cpp 和 NdkSample.h,源码见下面:

#include "NdkSample.h"

JNIEXPORT jstring JNICALL Java_com_zyp_ndktest_MainActivity_sayHello
        (JNIEnv *env, jclass cls, jstring j_str)
{
    const char *c_str = nullptr;
    char buff[128] = {0};
    jboolean isCopy;    
    c_str = env->GetStringUTFChars(j_str, &isCopy);
    printf("isCopy:%d/n",isCopy);
    if(c_str == NULL)
    {
        return NULL;
    }
    printf("C_str: %s /n", c_str);
    sprintf(buff, "hello %s", c_str);
    env->ReleaseStringUTFChars(j_str, c_str);
    return env->NewStringUTF(buff);
}  
#ifndef NDKTEST_NDKSAMPLE_H
#define NDKTEST_NDKSAMPLE_H

#include "jni.h"
#include <stdio.h>
#include <string.h>

extern "C" {
JNIEXPORT jstring JNICALL
        Java_com_zyp_ndktest_MainActivity_sayHello(JNIEnv *env, jclass type,
                                                       jstring filename);
}

#endif //NDKTEST_NDKSAMPLE_H

  现在来简单介绍一下,首先是NdkSample.h文件,刚刚创建的时候只有相应的预处理命令,我们在头文件预处理命令之间加上 jni.h ,stdio.h ,string.h 后两个非必要。将我们要在java层调用的接口声明出来,放在extern “c”{} 中(告诉编译器按照C标准进行编译)。第一次接触jni的同学看到那么复杂的函数命名和奇怪的JNIEXPORT ,JNICALL,JNIEnv之类的估计有点不习惯,本文就不详细介绍它们的意思,其实你跟踪源码它们就是几个宏(JNIEnv是一个结构体保存当前环境的上下文),其它的jstring,jclass之类的很好理解就是在Native环境中对JVM中java对应结构的一种表示方式。

  函数命令方式是包名加activity名加函数名,表如我们在java层中的包名是java.com.zyp.ndktest,在MainActivity中调用sayHello函数,则jni层函数命名就要写成上面的方式。

  接下来看NdkSample.cpp文件中函数的定义。ni层的函数还需要多两个参数,一个是JNIEnv * ,一个是jclass。我们在java层调用的时候就只用传递前两个参数之外的参数。例子中我们想从java层传递一个String类型的参数到jni层,jni层从JVM中取数据的时候取到的却是jstring类型,在jni层我们不能直接使用需要转换。这里我们通过env->GetStringUTFChars(j_str, &isCopy)函数来完成,将j_str所在的地址转换并赋值给const char *类型的指针,之后我们就可以通过该指针访问那块内存了。注意这里是const char * 表示该指针指向的内存区域的内容是不可以改变的,java中的String 也是自带final属性的。GetStringUTFChars()实际上是将JVM内部的Unicode转化成为了C/C++认识的UTF-8的格式的字符串,注意这个函数内部发生了内存分配,相当于是拷贝了一份Unicode然后进行转化,所以后面需要ReleaseStringUTFChars()来释放内存。

  最后该函数返回一个新的构建好的jstring类型给java层,为了将C/C++层的UTF-8字符串转换为JVM中的Unicode字符串,需要调用另外一个函数NewStringUTF()来完成转换。

  我们注意到JVM中的内容jni中不能直接操作需要进行转换,jni中的内同也要进行转换,因此也有大量的相关jni接口存在,后面文章中会挑选一些来讲解。

  此外,函数中JNIEnv *env这个参数需要说一下,在C和C++中使用方式是不一样的,不要搞混了。在C中,看到JNIEnv 我们实质是取得了JNINativeInterface* (JNIEnv指针的指针),我们得使用**env获取结构体,从而才能使用结构体里面的方法。在C++中,看到JNIEnv我们实质是取得了JNIEnv*(JNIEnv结构体的指针),我们可以直接使用env->使用结构体里面的方法。注意我们调用GetStringUTFChars()的方式,但是注意和Java_com_zyp_ndktest_MainActivity_sayHello()进行区别。

  现在来看看我们在java层中如何调用jni层的接口,看下面代码:

package com.zyp.ndktest;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        String ret = sayHello("zhuzhu");
        Log.i("JNI_INFO", ret);
    }

    static {
        System.loadLibrary("NdkSample");
    }

    public native static String sayHello(String str);
}

  我们首先要通过System.loadLibrary()加载jni代码编译后生成的.so,但是这个库的名字怎么来的呢,注意回过头去看上一篇中gradle ndk{}中的内容,我们是在那里进行的命名的;然后还要声明native static 类型的该函数。然后直接调用就好了。运行结果见下图。

Android Studio1.4.x JNI开发基础 - 简单实例

  希望通过这篇文章能够让大家入门JNI开发^_^。

 

Android Studio1.4.x JNI开发基础-基本环境配置

  从Eclipse时代到Android Studio普及,开发工具越来越好用。早些时候还需要安装Cygwin工具,从Android Studio1.3以后,在Android 环境开发JNI程序搭建开发环境变得相对简单。这里就来介绍一下急于Android Studio如何进行jni开发。

  首先准备基本工具,Android Studio (>=1.3.x), NDK(ndk-r10-e)。打开Android Studio 建立一个空工程,关联上NDK,操作步骤方式如下图:

 

Android Studio1.4.x JNI开发基础-基本环境配置

 

Android Studio1.4.x JNI开发基础-基本环境配置

  设置好NDK之后,开始设置gradle,设置gradle主要需要设置三个地方,设置好之后就可以直接编写和编译JNI代码了,不需要像以前一样编写Makefile,相当方便。但是设置gradle也是需要比较小心的,由于当前NDK还处于Experimental 阶段,更新不断,经常会爆出各种奇怪的错误,因此也要特别留心。好了废话不多说,下面来介绍设置gradle的三个主要步骤。

  首先设置TopLevel gradle,也就是Project gradle,这里比较简单,在dependencies中设置:classpath ‘com.android.tools.build:gradle-experimental:0.2.0’ ,注意这里要把之前的类似classpath ‘com.android.tools.build:gradle:1.3.0’ 注释掉。还要多提一句的是,这里设置的是gradle-experimental:0.2.0,后面对应设置gradle wrapper的时候要对应gradle2.5-all 版本,这里先说到这里。

  接着设置 Module gradle,这一步是比较麻烦的。由于我们在创建工程的时候自动生成的这个gradle文件内容比较多,而且如果要使用NDK的话这个gradle变化比较大,这里直接贴出需要使用NDK的gradle,然后来进行说明。

  
apply plugin: 'com.android.model.application' model { android { compileSdkVersion = 23 buildToolsVersion = "23.0.2" defaultConfig.with { applicationId = "com.zyp.ndktest" minSdkVersion.apiLevel = 19 // Unable to load class com.android.build.gradle.managed.ProductFlavor_Impl targetSdkVersion.apiLevel = 23 versionCode = 1 versionName = "1.0" } } android.buildTypes { release { minifyEnabled = false proguardFiles += file('proguard-rules.pro') } } compileOptions.with { sourceCompatibility = JavaVersion.VERSION_1_7 targetCompatibility = JavaVersion.VERSION_1_7 } android.ndk { moduleName = "NdkSample" cppFlags += "-std=c++11" cppFlags += "-fexceptions" cppFlags += "-I${file("src/main/jni//include")}".toString() ldLibs += ["android", "log"] stl = "gnustl_shared" } android.productFlavors { create("arm7") { ndk.abiFilters.add("armeabi-v7a") } create("arm8") { ndk.abiFilters.add("arm64-v8a") } } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.support:appcompat-v7:23.1.0' }

  和自动生成的gradle相比,首先是 apply plugin: ‘com.android.application’ 变成了 apply plugin: ‘com.android.model.application’。下面的配置也需要包装在model{}中。

  这个gradle的配置有几点需要注意的:

  1. 所有值的设置都要写成 xxx = yyyy的形式,比如: applicationId = “com.zyp.ndktest” (自动生成的gradle 则可能是: applicationId = “com.zyp.ndktest”  ),否则会爆这种错误:Error:Cause: org.gradle.api.internal.ExtensibleDynamicObject, 当出现此类错误,检查是否都用了 “=”的方式。

  2. buildTypes 需要从android{} 中取出来,写成android.buildTypes{}的形式,否则会出现这种错误:Error:Unable to load class ‘org.gradle.nativeplatform.internal.DefaultBuildType_Decorated’.    

   此外,自动生成的buildTypes的形式和上面的也不一样为以下的形式:

          
release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' }

  需要改成上面文件中的格式,否则会报这种错误:Error:No signature of method: org.gradle.model.ModelMap.minifyEnabled() is applicable for argument types: (java.lang.Boolean) values: [false]     

  3. defaultConfig{} 需要写成defaultConfig.with{} 的形式,否则会报这种错误:Error:Cause: com.android.build.gradle.managed.AndroidConfig_Impl

  4. 在defaultConfig.with{} 中 需要写成 

    minSdkVersion.apiLevel  = 19  
    targetSdkVersion.apiLevel = 23

    也就是比自动生成的多 .apiLevel ,否则会报这种错误:Unable to load class com.android.build.gradle.managed.ProductFlavor_Impl

  5. 增加compileOptions.with{} 需要选择JavaVersion.VERSION_1_7,否则会报这种错误:Bad class file magic or version

  6. 最后一点,在gradleWrapper中使用的是2.5,则android.ndk {} 中类似cppFlags 的添加使用 += 的方式,否则需要使用 .add的方式

  以上可能遇到的问题我这里帮大家罗列出来,具体的请参考Google的文档,只不过这个文档需要FQ。

  最后设置gradle wrapper就好了,将左边的工程视图调整到Project,在gradle->wrapper->grale-wrapper.properties文件的最后设置:distributionUrl=https/://services.gradle.org/distributions/gradle-2.5-all.zip,注意这里如果在Project gradle中设置的是gradle-experimental:0.2.0,则这里选择gradle-2.5-all,如果是gradle-experimental:0.4.0,需要设置gradle-2.8-all。

  gradle设置完成之后就可以创建jni文件夹,然后编写Native代码了,创建好jni后一个工程的基本结构见下图:

Android Studio1.4.x JNI开发基础-基本环境配置

  在JNI中创建.cpp/.c文件即可。本文先写到这里,接下来会介绍基本的jni实例和jni编程的及一些基本知识,希望对大家有帮助。

 

Android 触摸事件处理机制

  Android 触摸事件的处理主要涉及到几个方法:onInterceptTouchEvent(), dipatchTouchEvent(), onTouchEvent(), onTouch()。

  onInterceptTouchEvent() 用于拦截事件并改变事件传递方向。解释一下事件传递。比如一个Activity中展示给用户可能是ViewGroup和View的多层嵌套,默认情况下触摸事件产生之后从最外层一次传递到最里面一层,然后在从最里面一层开始响应。从最里面一层开始依次调用各层次的dispatchTouchEvent()进行分发,dispatchTouchEvent()中在调用onTouch / onTouchEvent进行响应触摸事件。

  onInterceptTouchEvent() 方法可以将触摸事件的传递截断,让触摸事件在某一层就不往下面传递,就开始调用这一层的dispatchTouchEvent(),开始向上层返回。如果需要在某一层拦截,需要复写该层的onInterceptTouchEvent()方法,并让该方法返回 true。通过一张图来解释一下。

  Android 触摸事件处理机制

  假设一个Activity展示的界面有A->B->C->D四层,当事件发生之后,首先经过A的onInterceptTouchEvent(), 如果A的onInterceptTouchEvent()返回false,则会传递到B的onInterceptTouchEvent(),如果返回false则一次向下(内层)传递。如果某一层的onInterceptTouchEvent()返回true,然后就会调用该层的disatchTouchEvent()分发事件,事件不再向下传递。如果各层都没有拦截事件则从最内层开始调用dispatchTouchEvent(),如果某一各层的dispatchTouchEvent()返回true,则表明该层消费了该事件,则上面层的dispatchTouEvent()不会被调用。举一个例子:

  Android 触摸事件处理机制

  上图中B层的onInterceptTouchEvent()返回true,则事件被拦截,开始调用B层的dispatchTouchEvent()向上返回一次响应触摸事件。

  知道这个机制有什么卵用吗?

  一个简单例子,我们在scrollView中放置了图片,图片允许缩放拖动,但是你对图片进行拖动的时候会发现scrollView也跟着动,这样体验就会很不好,怎么办呢?

  结合上面的分析,我们可以知道,如果让触摸事件传递到内层的图片,然后在在图片的disPatchTouchEvent()中把这个触摸事件消费了就不就可以了吗?

  具体做法在图片的onTouch() 方法中,ACTION_DOWN中设置scrollView不拦截事件,通过scrollView.requestDisallowInterceptTouchEvent(true)来完成,完成想要的处理之后在图片的onTouch()方法最后返回true就可以实现了。

  问题又来了 onTouch(View v, MotionEvent event) 和 onTouchEvent(MotionEvent event) 有什么区别呢? 看起来那么像,不会是完成相同的功能吧?这样做不是多此一举吗,为什么不用一个就好了。

  为了搞清楚这个问题,首先需要来看View中disPatchTouchEvent()方法:

public boolean dispatchTouchEvent(MotionEvent event) {
    // If the event should be handled by accessibility focus first.
    if (event.isTargetAccessibilityFocus()) {
        // We don't have focus or no virtual descendant has it, do not handle the event.
        if (!isAccessibilityFocusedViewOrHost()) {
            return false;
        }
        // We have focus and got the event, then use normal event dispatch.
        event.setTargetAccessibilityFocus(false);
    }

    boolean result = false;

    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onTouchEvent(event, 0);
    }

    final int actionMasked = event.getActionMasked();
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        // Defensive cleanup for new gesture
        stopNestedScroll();
    }

    if (onFilterTouchEventForSecurity(event)) {
        //noinspection SimplifiableIfStatement
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }

        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }

    if (!result && mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
    }

    // Clean up after nested scrolls if this is the end of a gesture;
    // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
    // of the gesture.
    if (actionMasked == MotionEvent.ACTION_UP ||
            actionMasked == MotionEvent.ACTION_CANCEL ||
            (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
        stopNestedScroll();
    }

    return result;
}

  从上面可以看出onTouch() 先于 onTouchEvent()执行。通过查看View的源码还发现,或者简单推理一下我们设置setOnTouchListener()设置的是谁就知道,我们什么时候需要调用onTouch()方法让其发挥作用而不是让onTouchEvent()来响应了。View源码中可以看出onTouch是一个Interface的方式实现,将处理逻辑的实现交给开发者自定义,因此可以得出Android系统自带的控件我们使用onTouch处理事件,如果我们需要扩展View,则需要复写onTouchEvent()来实现事件的处理。其实onTouch(View v, MotionEvent event) 和 onTouchEvent(MotionEvent event)可以从这两个方法接受的参数就可以看出不同来,onTouch包含一个View类型的参数,因此是可以设置给某个View的,onTouchEvent()则是给某个View自己用的。

  既然提到了View的事件响应,那onClick事件又是怎么响应的呢? 通过产看源码可以发现onClick是在onTouchEvent中执行的,而且是在onTouchEvent的ACTION_UP事件中执行的。因此如果View 的onTouch()返回true则会导致onClick得不到执行,因为onTouchEvent()得不到执行。

  此外Activity中也有onTouchEvent()成员方法,如果Activity中的View都不处理Event则Activity的onTouchEvent()会调用。

 

Android OpenGL 编写简单滤镜

  Android 上使用Opengl进行滤镜渲染效率较高,比起单纯的使用CPU给用户带来的体验会好很多。滤镜的对象是图片,图片是以Bitmap的形式表示,Opengl不能直接处理Bitmap,在Android上一般是通过GLSurfaceView来进行渲染的,也可以说成Android需要借助GLSurfaceView来完成对图片的渲染。

  GlSurfaceView 的图片来源依然是Bitmap,但是Bitmap需要以纹理(Texture)的形式载入到Opengl中。因此我首先来看一下载入纹理的步骤:

  1. GLES20.glGenTextures() : 生成纹理资源的句柄

  2. GLES20.glBindTexture(): 绑定句柄

  3. GLUtils.texImage2D() :将bitmap传递到已经绑定的纹理中

  4. GLES20.glTexParameteri() :设置纹理属性,过滤方式,拉伸方式等

  这里做滤镜使用Android4.x以后提供的 Effect 类来完成,Effect类实现也是通过Shader的方式来完成的,这些Shader程序内置在Android中,我们只需要按照一定的方式来调用就行了。在Android上使用GLSurfaceView来显示并完成图片的渲染,实现渲染需要实现GLSurfaceView.Render接口,该接口有三个方法:onDrawFrame(GL10 gl) ,该方法按照一定的刷新频率反复执行;onSurfaceChanged(GL10 gl, int width, int height),该方法在窗口重绘的时候执行;onSurfaceCreated(GL10 gl, EGLConfig config) 在创建SurfaceView的时候执行。

  使用Effect类会用到EffectFactory 和 EffectContex,在下面的例子中看看具体的使用方式。

  首先定义一个Activity:EffectivefilterActivity

package com.example.effectsfilterdemo;

import java.nio.IntBuffer;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;

public class EffectsFilterActivity extends Activity {

    private GLSurfaceView mEffectView;

    private TextureRenderer renderer;


    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        renderer = new TextureRenderer();
        renderer.setImageBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.puppy));
        renderer.setCurrentEffect(R.id.none);
        
        mEffectView = (GLSurfaceView) findViewById(R.id.effectsview);
        //mEffectView = new GLSurfaceView(this);
        mEffectView.setEGLContextClientVersion(2);
        //mEffectView.setRenderer(this);
        mEffectView.setRenderer(renderer);
        mEffectView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
        
        //setContentView(mEffectView);
    }
    
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        Log.i("info", "menu create");
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        renderer.setCurrentEffect(item.getItemId());
        mEffectView.requestRender();
        return true;
    }
}

  EffectivefilterActivity 中使用了两个布局文件,一个用于Activity的布局,另一个用于菜单的布局。

  R.layout.main:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <android.opengl.GLSurfaceView
        android:id="@+id/effectsview"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
         />

</LinearLayout>

  R.menu.main:

<menu xmlns:android="http://schemas.android.com/apk/res/android" >

    <item
        android:id="@+id/none"
        android:showAsAction="never"
        android:title="none"/>
    <item
        android:id="@+id/autofix"
        android:showAsAction="never"
        android:title="autofix"/>
    <item
        android:id="@+id/bw"
        android:showAsAction="never"
        android:title="bw"/>
    <item
        android:id="@+id/brightness"
        android:showAsAction="never"
        android:title="brightness"/>
    <item
        android:id="@+id/contrast"
        android:showAsAction="never"
        android:title="contrast"/>
    <item
        android:id="@+id/crossprocess"
        android:showAsAction="never"
        android:title="crossprocess"/>
    <item
        android:id="@+id/documentary"
        android:showAsAction="never"
        android:title="documentary"/>
    <item
        android:id="@+id/duotone"
        android:showAsAction="never"
        android:title="duotone"/>
    <item
        android:id="@+id/filllight"
        android:showAsAction="never"
        android:title="filllight"/>
    <item
        android:id="@+id/fisheye"
        android:showAsAction="never"
        android:title="fisheye"/>
    <item
        android:id="@+id/flipvert"
        android:showAsAction="never"
        android:title="flipvert"/>
    <item
        android:id="@+id/fliphor"
        android:showAsAction="never"
        android:title="fliphor"/>
    <item
        android:id="@+id/grain"
        android:showAsAction="never"
        android:title="grain"/>
    <item
        android:id="@+id/grayscale"
        android:showAsAction="never"
        android:title="grayscale"/>
    <item
        android:id="@+id/lomoish"
        android:showAsAction="never"
        android:title="lomoish"/>
    <item
        android:id="@+id/negative"
        android:showAsAction="never"
        android:title="negative"/>
    <item
        android:id="@+id/posterize"
        android:showAsAction="never"
        android:title="posterize"/>
    <item
        android:id="@+id/rotate"
        android:showAsAction="never"
        android:title="rotate"/>
    <item
        android:id="@+id/saturate"
        android:showAsAction="never"
        android:title="saturate"/>
    <item
        android:id="@+id/sepia"
        android:showAsAction="never"
        android:title="sepia"/>
    <item
        android:id="@+id/sharpen"
        android:showAsAction="never"
        android:title="sharpen"/>
    <item
        android:id="@+id/temperature"
        android:showAsAction="never"
        android:title="temperature"/>
    <item
        android:id="@+id/tint"
        android:showAsAction="never"
        android:title="tint"/>
    <item
        android:id="@+id/vignette"
        android:showAsAction="never"
        android:title="vignette"/>

</menu>

  在R.layout.main中只定义了一个GLSurfaceView用于显示图片,R.menu.main用于显示多个菜单项,通过点击菜单来完成调用不同滤镜实现对图片的处理。

  接下来看比较关键的Renderer接口的实现。

package com.example.effectsfilterdemo;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.media.effect.Effect;
import android.media.effect.EffectContext;
import android.media.effect.EffectFactory;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.opengl.GLUtils;
import android.util.Log;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.LinkedList;
import java.util.Queue;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

public class TextureRenderer implements GLSurfaceView.Renderer{

    private int mProgram;
    private int mTexSamplerHandle;
    private int mTexCoordHandle;
    private int mPosCoordHandle;

    private FloatBuffer mTexVertices;
    private FloatBuffer mPosVertices;

    private int mViewWidth;
    private int mViewHeight;

    private int mTexWidth;
    private int mTexHeight;
    
    private Context mContext;
    private final Queue<Runnable> mRunOnDraw;
    private int[] mTextures = new int[2];
    int mCurrentEffect;
    private EffectContext mEffectContext;
    private Effect mEffect;
    private int mImageWidth;
    private int mImageHeight;
    private boolean initialized = false;

    private static final String VERTEX_SHADER =
        "attribute vec4 a_position;/n" +
        "attribute vec2 a_texcoord;/n" +
        "varying vec2 v_texcoord;/n" +
        "void main() {/n" +
        "  gl_Position = a_position;/n" +
        "  v_texcoord = a_texcoord;/n" +
        "}/n";

    private static final String FRAGMENT_SHADER =
        "precision mediump float;/n" +
        "uniform sampler2D tex_sampler;/n" +
        "varying vec2 v_texcoord;/n" +
        "void main() {/n" +
        "  gl_FragColor = texture2D(tex_sampler, v_texcoord);/n" +
        "}/n";

    private static final float[] TEX_VERTICES = {
        0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f
    };

    private static final float[] POS_VERTICES = {
        -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f
    };

    private static final int FLOAT_SIZE_BYTES = 4;
    
    public TextureRenderer() {
        // TODO Auto-generated constructor stub
        mRunOnDraw = new LinkedList<>();

    }

    public void init() {
        // Create program
        mProgram = GLToolbox.createProgram(VERTEX_SHADER, FRAGMENT_SHADER);

        // Bind attributes and uniforms
        mTexSamplerHandle = GLES20.glGetUniformLocation(mProgram,
                "tex_sampler");
        mTexCoordHandle = GLES20.glGetAttribLocation(mProgram, "a_texcoord");
        mPosCoordHandle = GLES20.glGetAttribLocation(mProgram, "a_position");

        // Setup coordinate buffers
        mTexVertices = ByteBuffer.allocateDirect(
                TEX_VERTICES.length * FLOAT_SIZE_BYTES)
                .order(ByteOrder.nativeOrder()).asFloatBuffer();
        mTexVertices.put(TEX_VERTICES).position(0);
        mPosVertices = ByteBuffer.allocateDirect(
                POS_VERTICES.length * FLOAT_SIZE_BYTES)
                .order(ByteOrder.nativeOrder()).asFloatBuffer();
        mPosVertices.put(POS_VERTICES).position(0);
    }

    public void tearDown() {
        GLES20.glDeleteProgram(mProgram);
    }

    public void updateTextureSize(int texWidth, int texHeight) {
        mTexWidth = texWidth;
        mTexHeight = texHeight;
        computeOutputVertices();
    }

    public void updateViewSize(int viewWidth, int viewHeight) {
        mViewWidth = viewWidth;
        mViewHeight = viewHeight;
        computeOutputVertices();
    }

    public void renderTexture(int texId) {
        GLES20.glUseProgram(mProgram);
        GLToolbox.checkGlError("glUseProgram");

        GLES20.glViewport(0, 0, mViewWidth, mViewHeight);
        GLToolbox.checkGlError("glViewport");

        GLES20.glDisable(GLES20.GL_BLEND);

        GLES20.glVertexAttribPointer(mTexCoordHandle, 2, GLES20.GL_FLOAT, false,
                0, mTexVertices);
        GLES20.glEnableVertexAttribArray(mTexCoordHandle);
        GLES20.glVertexAttribPointer(mPosCoordHandle, 2, GLES20.GL_FLOAT, false,
                0, mPosVertices);
        GLES20.glEnableVertexAttribArray(mPosCoordHandle);
        GLToolbox.checkGlError("vertex attribute setup");

        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
        GLToolbox.checkGlError("glActiveTexture");
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texId);//把已经处理好的Texture传到GL上面
        GLToolbox.checkGlError("glBindTexture");
        GLES20.glUniform1i(mTexSamplerHandle, 0);

        GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
    }

    private void computeOutputVertices() { //调整AspectRatio 保证landscape和portrait的时候显示比例相同,图片不会被拉伸
        if (mPosVertices != null) {
            float imgAspectRatio = mTexWidth / (float)mTexHeight;
            float viewAspectRatio = mViewWidth / (float)mViewHeight;
            float relativeAspectRatio = viewAspectRatio / imgAspectRatio;
            float x0, y0, x1, y1;
            if (relativeAspectRatio > 1.0f) {
                x0 = -1.0f / relativeAspectRatio;
                y0 = -1.0f;
                x1 = 1.0f / relativeAspectRatio;
                y1 = 1.0f;
            } else {
                x0 = -1.0f;
                y0 = -relativeAspectRatio;
                x1 = 1.0f;
                y1 = relativeAspectRatio;
            }
            float[] coords = new float[] { x0, y0, x1, y0, x0, y1, x1, y1 };
            mPosVertices.put(coords).position(0);
        }
    }
    
    private void initEffect() {
        EffectFactory effectFactory = mEffectContext.getFactory();
        if (mEffect != null) {
            mEffect.release();
        }
        /**
         * Initialize the correct effect based on the selected menu/action item
         */
        switch (mCurrentEffect) {

        case R.id.none:
            break;

        case R.id.autofix:
            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_AUTOFIX);
            mEffect.setParameter("scale", 0.5f);
            break;

        case R.id.bw:
            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_BLACKWHITE);
            mEffect.setParameter("black", .1f);
            mEffect.setParameter("white", .7f);
            break;

        case R.id.brightness:
            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_BRIGHTNESS);
            mEffect.setParameter("brightness", 2.0f);
            break;

        case R.id.contrast:
            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_CONTRAST);
            mEffect.setParameter("contrast", 1.4f);
            break;

        case R.id.crossprocess:
            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_CROSSPROCESS);
            break;

        case R.id.documentary:
            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_DOCUMENTARY);
            break;

        case R.id.duotone:
            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_DUOTONE);
            mEffect.setParameter("first_color", Color.YELLOW);
            mEffect.setParameter("second_color", Color.DKGRAY);
            break;

        case R.id.filllight:
            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_FILLLIGHT);
            mEffect.setParameter("strength", .8f);
            break;

        case R.id.fisheye:
            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_FISHEYE);
            mEffect.setParameter("scale", .5f);
            break;

        case R.id.flipvert:
            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_FLIP);
            mEffect.setParameter("vertical", true);
            break;

        case R.id.fliphor:
            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_FLIP);
            mEffect.setParameter("horizontal", true);
            break;

        case R.id.grain:
            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_GRAIN);
            mEffect.setParameter("strength", 1.0f);
            break;

        case R.id.grayscale:
            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_GRAYSCALE);
            break;

        case R.id.lomoish:
            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_LOMOISH);
            break;

        case R.id.negative:
            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_NEGATIVE);
            break;

        case R.id.posterize:
            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_POSTERIZE);
            break;

        case R.id.rotate:
            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_ROTATE);
            mEffect.setParameter("angle", 180);
            break;

        case R.id.saturate:
            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_SATURATE);
            mEffect.setParameter("scale", .5f);
            break;

        case R.id.sepia:
            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_SEPIA);
            break;

        case R.id.sharpen:
            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_SHARPEN);
            break;

        case R.id.temperature:
            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_TEMPERATURE);
            mEffect.setParameter("scale", .9f);
            break;

        case R.id.tint:
            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_TINT);
            mEffect.setParameter("tint", Color.MAGENTA);
            break;

        case R.id.vignette:
            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_VIGNETTE);
            mEffect.setParameter("scale", .5f);
            break;

        default:
            break;

        }
    }
    
    public void setCurrentEffect(int effect) {
        mCurrentEffect = effect;
    }

    
    public void setImageBitmap(final Bitmap bmp){
        runOnDraw(new Runnable() {
            
            @Override
            public void run() {
                // TODO Auto-generated method stub
                loadTexture(bmp);
            }
        });
    }
    
    private void loadTexture(Bitmap bmp){
        GLES20.glGenTextures(2, mTextures , 0);

        updateTextureSize(bmp.getWidth(), bmp.getHeight());
        
        mImageWidth = bmp.getWidth();
        mImageHeight = bmp.getHeight();

        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextures[0]);
        GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bmp, 0);

        GLToolbox.initTexParams();
    }
    
    private void applyEffect() {
        if(mEffect == null){
            Log.i("info","apply Effect null mEffect");
        }
        
        mEffect.apply(mTextures[0], mImageWidth, mImageHeight, mTextures[1]);
    }

    private void renderResult() {
        if (mCurrentEffect != R.id.none) {
            renderTexture(mTextures[1]);
        } else {
            renderTexture(mTextures[0]);
        }
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        // TODO Auto-generated method stub
        if(!initialized){
            init();
            mEffectContext = EffectContext.createWithCurrentGlContext();
            initialized = true;
        }
        
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
        synchronized (mRunOnDraw) {
            while (!mRunOnDraw.isEmpty()) {
                mRunOnDraw.poll().run();
            }
        }
        
        if (mCurrentEffect != R.id.none) {
            initEffect();
            applyEffect();
        }
        renderResult();
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        // TODO Auto-generated method stub
        updateViewSize(width, height);
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        // TODO Auto-generated method stub
        
    }
    
    protected void runOnDraw(final Runnable runnable) {
        synchronized (mRunOnDraw) {
            mRunOnDraw.add(runnable);
        }
    }
}

  这里有一个地方需要注意,任何使用Opengl接口的方法调用需要在Opengl Context中进行,否则会出现:call to OpenGL ES API with no current context (logged once per thread) 报错信息。所谓的Opengl Context 其实就是需要在onDrawFrame(GL10 gl),onSurfaceChanged(GL10 gl, int width, int height),onSurfaceCreated(GL10 gl, EGLConfig config)中调用,注意到这三个方法都有一个参数GL10。这里还有一个地方就是在载入纹理之前需要载入位图,使用了runOnDraw()方法将loadTexure的步骤放在onDrawFrame() 中来完成,巧妙的为外界提供了一个接口并使得操作在具有Opengl Context的黄金中完成。

  最后来看看辅助的工具类(GLToolbox),该类完成Shader程序的创建,应用程序提供Shader 源码给该工具类编译:

package com.example.effectsfilterdemo;
import android.opengl.GLES20;

public class GLToolbox {

    public static int loadShader(int shaderType, String source) {
        int shader = GLES20.glCreateShader(shaderType);
        if (shader != 0) {
            GLES20.glShaderSource(shader, source);
            GLES20.glCompileShader(shader);
            int[] compiled = new int[1];
            GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
            if (compiled[0] == 0) {
                String info = GLES20.glGetShaderInfoLog(shader);
                GLES20.glDeleteShader(shader);
                shader = 0;
                throw new RuntimeException("Could not compile shader " +
                shaderType + ":" + info);
            }
        }
        return shader;
    }

    public static int createProgram(String vertexSource,
            String fragmentSource) {
        int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
        if (vertexShader == 0) {
            return 0;
        }
        int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
        if (pixelShader == 0) {
            return 0;
        }

        int program = GLES20.glCreateProgram();
        if (program != 0) {
            GLES20.glAttachShader(program, vertexShader);
            checkGlError("glAttachShader");
            GLES20.glAttachShader(program, pixelShader);
            checkGlError("glAttachShader");
            GLES20.glLinkProgram(program);
            int[] linkStatus = new int[1];
            GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus,
                    0);
            if (linkStatus[0] != GLES20.GL_TRUE) {
                String info = GLES20.glGetProgramInfoLog(program);
                GLES20.glDeleteProgram(program);
                program = 0;
                throw new RuntimeException("Could not link program: " + info);
            }
        }
        return program;
    }

    public static void checkGlError(String op) {
        int error;
        while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
            throw new RuntimeException(op + ": glError " + error);
        }
    }

    public static void initTexParams() {
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,
                GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,
                GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S,
                GLES20.GL_CLAMP_TO_EDGE);
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,
                GLES20.GL_CLAMP_TO_EDGE);
   }

}

  这里就不提供整个工程了,结合上面的代码,自己在资源文件中提供一个图片载入就可以看到效果了。