Статьи

Retrofit, простой HTTP-клиент для Android и Java

Эта статья была обновлена ​​11 января 2017 года. В частности: Обновление для Retrofit v2.1

Обмен данными между мобильным приложением и внутренним сервером является важной необходимостью для многих проектов разработки. Хотя задача стала проще с библиотекой Google Volley , у нее есть крутая кривая обучения, и Retrofit стремится сделать задачу еще проще.

В этом уроке я покажу вам, как обмениваться данными между приложением Android и бэкэнд-приложением PHP с помощью библиотеки модернизации. Этот пример приложения будет имитировать процесс входа в систему, отправив две строки (имя пользователя и пароль) на сервер, ожидая ответа, а затем покажет его пользователю.

Вы можете найти окончательный код для этого приложения на GitHub .

Сборка приложения для Android

Создайте новый проект в Android Studio, выбрав минимальный уровень API 18 и добавив пустое действие .

Добавьте разрешение на доступ к Интернету в AndroidManifest.xml внутри тега application :

 < uses-permission android:name = "android.permission.INTERNET" /> 

Добавьте библиотечные зависимости в раздел зависимостей файла build.gradle (Module: app) :

  compile 'com.squareup.okhttp3:logging-interceptor:3.4.0' compile 'com.squareup.retrofit2:retrofit:2.1.0' compile 'com.squareup.retrofit2:converter-gson:2.1.0' compile 'com.squareup:otto:1.3.8' compile 'com.google.code.gson:gson:2.6.2' 

Создание макета входа

Единственный макет, необходимый для этого приложения, — activity_main.xml , замените его следующим:

 <?xml version="1.0" encoding="utf-8"?> < RelativeLayout 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:paddingBottom = "@dimen/activity_vertical_margin" android:paddingLeft = "@dimen/activity_horizontal_margin" android:paddingRight = "@dimen/activity_horizontal_margin" android:paddingTop = "@dimen/activity_vertical_margin" tools:context = "com.example.theodhorpandeli.retrofit.MainActivity" > < LinearLayout android:layout_width = "match_parent" android:layout_height = "wrap_content" android:orientation = "vertical" android:id = "@+id/loginLayout" > < EditText android:layout_width = "match_parent" android:layout_height = "wrap_content" android:id = "@+id/usernameInput" android:hint = "Username:" /> < EditText android:layout_width = "match_parent" android:layout_height = "wrap_content" android:id = "@+id/passwordInput" android:hint = "Password:" /> < LinearLayout android:layout_width = "match_parent" android:layout_height = "wrap_content" android:orientation = "horizontal" > < Button android:layout_width = "wrap_content" android:layout_height = "wrap_content" android:id = "@+id/loginButtonPost" android:text = "Login - Post" android:layout_gravity = "right" android:layout_weight = "1" /> < Button android:layout_width = "wrap_content" android:layout_height = "wrap_content" android:id = "@+id/loginButtonGet" android:text = "Login - Get" android:layout_weight = "1" /> </ LinearLayout > </ LinearLayout > < LinearLayout android:orientation = "vertical" android:layout_width = "match_parent" android:layout_height = "fill_parent" android:layout_below = "@id/loginLayout" > < TextView android:layout_width = "wrap_content" android:layout_height = "wrap_content" android:id = "@+id/information" android:layout_gravity = "center_horizontal" android:layout_marginTop = "20dp" /> < TextView android:layout_width = "wrap_content" android:layout_height = "wrap_content" android:id = "@+id/extraInformation" android:layout_gravity = "center_horizontal" android:layout_marginTop = "20dp" /> </ LinearLayout > </ RelativeLayout > 

Макет включает два элемента EditText (имя пользователя и пароль), две Buttons и два TextView для отображения ответа сервера.

Создание классов

Для отправки данных на сервер Retrofit использует Communicator и класс Interface . Методы Communicator создают RestAdapters который использует Interfaces для выполнения запроса к серверу.

Чтобы создать интерфейсный класс, щелкните правой кнопкой мыши основной пакет и выберите New -> Java Class . Вызовите этот интерфейс класса и выберите Вид -> Интерфейс .

Этот класс содержит методы, которые будут взаимодействовать с API. Сначала добавьте методы для запросов POST .

 public interface Interface { //This method is used for "POST" @FormUrlEncoded @POST ( "/api.php" ) Call<ServerResponse> post( @Field ( "method" ) String method, @Field ( "username" ) String username, @Field ( "password" ) String password ); //This method is used for "GET" @GET ( "/api.php" ) Call<ServerResponse> get( @Query ( "method" ) String method, @Query ( "username" ) String username, @Query ( "password" ) String password ); } 

Три переменные отправляются в API, закодированные как данные формы. Вы ServerResponse класс ServerResponse позже, поэтому не беспокойтесь о возможных ошибках.

Класс Communicator выполняет вызов и содержит методы, которые создают адаптер отдыха.

Создайте новый класс с именем Communicator и добавьте этот код:

 public class Communicator { private static final String TAG = "Communicator" ; private static final String SERVER_URL = "http://127.0.0.1/retrofit" ; public void loginPost (String username, String password){ //Here a logging interceptor is created HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); logging.setLevel(HttpLoggingInterceptor.Level.BODY); //The logging interceptor will be added to the http client OkHttpClient.Builder httpClient = new OkHttpClient.Builder(); httpClient.addInterceptor(logging); //The Retrofit builder will have the client attached, in order to get connection logs Retrofit retrofit = new Retrofit.Builder() .client(httpClient.build()) .addConverterFactory(GsonConverterFactory.create()) .baseUrl(SERVER_URL) .build(); Interface service = retrofit.create(Interface.class); Call<ServerResponse> call = service.post( "login" ,username,password); call.enqueue( new Callback<ServerResponse>() { @Override public void onResponse (Call<ServerResponse> call, Response<ServerResponse> response) { BusProvider.getInstance().post( new ServerEvent(response.body())); Log.e(TAG, "Success" ); } @Override public void onFailure (Call<ServerResponse> call, Throwable t) { // handle execution failures like no internet connectivity BusProvider.getInstance().post( new ErrorEvent(- 2 ,t.getMessage())); } }); } public void loginGet (String username, String password){ //Here a logging interceptor is created HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); logging.setLevel(HttpLoggingInterceptor.Level.BODY); //The logging interceptor will be added to the http client OkHttpClient.Builder httpClient = new OkHttpClient.Builder(); httpClient.addInterceptor(logging); //The Retrofit builder will have the client attached, in order to get connection logs Retrofit retrofit = new Retrofit.Builder() .client(httpClient.build()) .addConverterFactory(GsonConverterFactory.create()) .baseUrl(SERVER_URL) .build(); Interface service = retrofit.create(Interface.class); Call<ServerResponse> call = service.get( "login" ,username,password); call.enqueue( new Callback<ServerResponse>() { @Override public void onResponse (Call<ServerResponse> call, Response<ServerResponse> response) { BusProvider.getInstance().post( new ServerEvent(response.body())); Log.e(TAG, "Success" ); } @Override public void onFailure (Call<ServerResponse> call, Throwable t) { // handle execution failures like no internet connectivity BusProvider.getInstance().post( new ErrorEvent(- 2 ,t.getMessage())); } }); } 

Измените SERVER_URL на URL вашего сервера PHP, я не буду SERVER_URL о настройке PHP в этом руководстве, но вы можете найти подробные инструкции здесь .

Создайте новый класс с именем BusProvider и добавьте следующий код:

 public class BusProvider { private static final Bus BUS = new Bus(); public static Bus getInstance (){ return BUS; } public BusProvider (){} } 

В классе MainActivity получите значения из элементов EditText и используйте их в качестве параметров для вызова сервера. Измените класс MainActivity на:

 public class MainActivity extends AppCompatActivity { private Communicator communicator; private String username, password; private EditText usernameET, passwordET; private Button loginButtonPost, loginButtonGet; private TextView information, extraInformation; private final static String TAG = "MainActivity" ; public static Bus bus; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); communicator = new Communicator(); usernameET = (EditText)findViewById(R.id.usernameInput); passwordET = (EditText)findViewById(R.id.passwordInput); //This is used to hide the password's EditText characters. So we can avoid the different hint font. passwordET.setTransformationMethod( new PasswordTransformationMethod()); loginButtonPost = (Button)findViewById(R.id.loginButtonPost); loginButtonPost.setOnClickListener( new View.OnClickListener() { @Override public void onClick (View v) { username = usernameET.getText().toString(); password = passwordET.getText().toString(); usePost(username, password); } }); loginButtonGet = (Button)findViewById(R.id.loginButtonGet); loginButtonGet.setOnClickListener( new View.OnClickListener() { @Override public void onClick (View v) { username = usernameET.getText().toString(); password = passwordET.getText().toString(); useGet(username, password); } }); information = (TextView)findViewById(R.id.information); extraInformation = (TextView)findViewById(R.id.extraInformation); } private void usePost (String username, String password){ communicator.loginPost(username, password); } private void useGet (String username, String password){ communicator.loginGet(username, password); } } 

Подводя итог тому, что вы сделали до сих пор. Значения элементов EditText передаются usePost() . Затем вызывается метод communicator который использует эти значения для создания своего адаптера отдыха и выполнения вызова.

Теперь, когда метод публикации почти завершен, приложение должно обрабатывать ответы от сервера. Для этого Retrofit использует классы под названием Model . Для этого приложения имя ModelServerResponse и вызывается внутри метода Interface как обратный RestAdapter и при создании RestAdapter . В обоих случаях указывается тип ответа, ожидаемого от сервера.

Создайте новый класс с именем ServerResponse и добавьте следующий код:

 public class ServerResponse { public class ServerResponse implements Serializable { @SerializedName ( "returned_username" ) private String username; @SerializedName ( "returned_password" ) private String password; @SerializedName ( "message" ) private String message; @SerializedName ( "response_code" ) private int responseCode; public ServerResponse (String username, String password, String message, int responseCode){ this .username = username; this .password = password; this .message = message; this .responseCode = responseCode; } public String getUsername () { return username; } public void setUsername (String username) { this .username = username; } public String getPassword () { return password; } public void setPassword (String password) { this .password = password; } public String getMessage () { return message; } public void setMessage (String message) { this .message = message; } public int getResponseCode () { return responseCode; } public void setResponseCode ( int responseCode) { this .responseCode = responseCode; } } } 

Модели Retrofit обычно реализуют Serializable потому что они должны анализировать данные из объектов, в данном случае, из JSONObject. Этот класс объявляет переменные в сочетании с именем ключа JSON, данные которого они обрабатывают.

Чтобы показать ответы сервера, Retrofit использует «События». Чтобы получить ответ сервера, вам нужно создать классы событий. Создайте новый класс с именем ServerEvent .

 public class ServerEvent { private ServerResponse serverResponse; public ServerEvent (ServerResponse serverResponse) { this .serverResponse = serverResponse; } public ServerResponse getServerResponse () { return serverResponse; } public void setServerResponse (ServerResponse serverResponse) { this .serverResponse = serverResponse; } } 

При вызове этот класс создает ServerResponse .

Создайте другой класс с именем ErrorEvent и добавьте следующий код:

 public class ErrorEvent { private int errorCode; private String errorMsg; public ErrorEvent ( int errorCode, String errorMsg) { this .errorCode = errorCode; this .errorMsg = errorMsg; } public int getErrorCode () { return errorCode; } public void setErrorCode ( int errorCode) { this .errorCode = errorCode; } public String getErrorMsg () { return errorMsg; } public void setErrorMsg (String errorMsg) { this .errorMsg = errorMsg; } } 

Эти методы получают больше информации об ответе сервера или любых ошибках. Для этого примера приложения классы Event создаются, но не используются. Добавьте эти методы в класс Communicator перед закрывающей скобкой:

 ... @Produce public ServerEvent produceServerEvent (ServerResponse serverResponse) { return new ServerEvent(serverResponse); } @Produce public ErrorEvent produceErrorEvent ( int errorCode, String errorMsg) { return new ErrorEvent(errorCode, errorMsg); } ... 

RestAdapter завершен, самая важная часть кода:

 ... Call<ServerResponse> call = service.post( "login" ,username,password); call.enqueue( new Callback<ServerResponse>() { @Override public void onResponse (Call<ServerResponse> call, Response<ServerResponse> response) { // response.isSuccessful() is true if the response code is 2xx BusProvider.getInstance().post( new ServerEvent(response.body())); Log.e(TAG, "Success" ); } @Override public void onFailure (Call<ServerResponse> call, Throwable t) { // handle execution failures like no internet connectivity BusProvider.getInstance().post( new ErrorEvent(- 2 ,t.getMessage())); } }); ... 

Это указывает, что обратный вызов будет иметь структуру ServerResponse и реализует два переопределенных метода: success и failure . Первый метод вызывается, когда клиент получает ответ от сервера, а второй — когда сервер не найден или возникает ошибка соединения.

Retrofit использует BusProvider для получения данных с сервера при вызове метода success или failure . BusProvider похож на канал, где передается каждый ответ. В зависимости от случая BusProvider ServerEvent , откуда вы можете получить нужные данные, или ErrorEvent который содержит информацию об ошибке.

На одном конце канала находится Retrofit Builder который отправляет данные ответа, а на другом — любое действие, которое ожидает данные.

Чтобы заставить класс MainActivity ждать события, сначала вам нужно реализовать два метода. Добавьте следующее к классу:

 @Override public void onResume (){ super .onResume(); BusProvider.getInstance().register( this ); } @Override public void onPause (){ super .onPause(); BusProvider.getInstance().unregister( this ); } 

Это приводит к тому, что действие ожидает события, регистрируя его на шине событий, теперь событие должно быть перехвачено. Поскольку вы создали два типа событий, ServerEvent и ErrorEvent , они оба должны быть обнаружены путем реализации двух методов Subscribed . Добавьте следующие методы в MainActivity :

 @Subscribe public void onServerEvent (ServerEvent serverEvent){ Toast.makeText( this , "" +serverEvent.getServerResponse().getMessage(), Toast.LENGTH_SHORT).show(); if (serverEvent.getServerResponse().getUsername() != null ){ information.setText( "Username: " +serverEvent.getServerResponse().getUsername() + " || Password: " +serverEvent.getServerResponse().getPassword()); } extraInformation.setText( "" + serverEvent.getServerResponse().getMessage()); } @Subscribe public void onErrorEvent (ErrorEvent errorEvent){ Toast.makeText( this , "" +errorEvent.getErrorMsg(),Toast.LENGTH_SHORT).show(); } 

Теперь класс подписывается на события, предоставляемые шиной, и в зависимости от типа события пользователь получает информацию.

Серверная сторона

PHP скрипт

Для этого примера приложения сервер состоит из простого сценария PHP. Для простоты этот скрипт не связан с базой данных. Создайте api.php и добавьте следующее:

 <?php //Post Method here if ( isset ( $_POST [ 'method' ]) == 'login' ){ $username = $_POST [ 'username' ]; $password = $_POST [ 'password' ]; if ( $username == "admin" && $password == "admin" ){ $response = array ( 'returned_username' => "-admin-" , 'returned_password' => "-admin-" , 'message' => "Your credentials are so weak [USING_POST]!" , 'response_code' => "1" ); echo json_encode( $response ); } else { $response = array ( 'response_code' => "-1" , 'message' => "invalid username or password" ); echo json_encode( $response ); } } 

Первая часть получает параметры из метода POST и если они соответствуют ожиданиям, оператор true и ответ JSON отправляется обратно клиенту. Если утверждение неверно, ответ на сообщение об ошибке JSON отправляется обратно клиенту.

Вторая часть почти такая же, как и первая, но параметры отправляются методом GET . Варианты ответа одинаковы. Если ни один из методов не используется, общий ответ с кодом ошибки генерируется как объект JSON.

Добавьте следующее в ваш файл PHP:

 //Get Method here else if ( isset ( $_GET [ 'method' ]) == 'login' ){ $username = $_GET [ 'username' ]; $password = $_GET [ 'password' ]; if ( $username == "admin" && $password == "admin" ){ $response = array ( 'returned_username' => "=admin=" , 'returned_password' => "=admin=" , 'message' => "Your credentials are so weak [USING_GET]!" , 'response_code' => "1" ); echo json_encode( $response ); } else { $response = array ( 'response_code' => "-1" , 'message' => "invalid username or password" ); echo json_encode( $response ); } } //If no method else { $response = array ( 'response_code' => "-2" , 'message' => "invalid method" ); echo json_encode( $response ); } ?> 

Запустите свой сервер PHP и приложение для Android. Вы должны получить следующие результаты в зависимости от того, что вы вводите в текстовые поля и какую кнопку вы нажимаете.

Вывод

После некоторой начальной настройки библиотека модернизации позволяет вам добавлять сетевые вызовы в приложение Android, и я использовал его в большинстве разрабатываемых мной приложений. Я надеюсь, что вам нравится пользоваться библиотекой, и я с нетерпением жду ваших комментариев и вопросов.