Android Programjaim

Android, Flutter/Dart, Kotlin, Java, Unity, HarmonyOS

Retrofit - okhttp es sima html oldalak lekerese

2016. április 01. 09:54 - lacas8282

Mostanaban ezek a hangzatos kulcsszavak, ha mesz allasinterjura ezeket erdemes ismerni:

dagger, dagger2, android annotations, realm, sugarORM, butterknife, okhttp, dependency injection, picasso, glide
es Jake Wharton nevet is erdemes ismernunk :)


Tehat ezeket erdemes ismerni, githubon fen is vannak, vagy sima google search es tadamm...Mindegyikrol van youtube video is, igy ha erdekes utananezel. Kesobb foglalkozom ezekkel is, de csak szukszavuan, ahogyan szoktam.

Tehat lassuk a retrofit-et eloszor a sorban, mi ez?

http://square.github.io/retrofit/

https://github.com/square/retrofit

A type-safe HTTP client for Android and Java

OK lassuk mi ez magyarul: Vegulis ezzel is GET, POST, PUT stb-t tudunk lekerni a netrol dolgokat, mint eddig, foleg json-ban, vagy akar xml-ben, stb amiben szeretnenk. Multipart entityt is tudunk egyebkent hasznalni, es persze sync es async lekeres is van.

Na de akkor miert jobb ez mint mas?

A lenyege, hogy egy interface valositja meg az egesz http lekerest, tehat:

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

 

Ez mar azert sokkal szebb, mint egy Volley nemde? Es nem csak szep, de sokkal gyorsabb is! Lasd elozo POST.

Meghivni ezt igy tudjuk majd akarhonnan:

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com/")
    .build();

GitHubService service = retrofit.create(GitHubService.class);

 

Mit csinal ez az egesz?

Az interface-be annotaciokat aggatunk magara a fuggvenyre, ebbol tudja majd Retrofit baratunk, hogy mit kell tennie az adott URL-el.

Nezzuk az interface-t. Ez egy Web-service deklaralasa tulajdonkeppen. $GET("akarmi") kozli vele, hogy GET lesz a lekeres nem POST vagy mas. Nem terek ki a kulonbsegekre, feltetelezem, hogy minimalis html, php tudasod megvan ehhez...Az akarmi helyere az url-t irjuk, a BASE_URL nelkul. Tehat csak a veget:)
Itt azert mar lehet latni, hogyha "{user}" van a kodban, akkor az valami template cucc lesz, es bizony igyis van.
Ezt fogja a retrofit kicserelni a

@Path("user") String user

 

-ben megadott ertekkel.

Tehat eleg konnyu eddig a dolog, szep is jo is, gyors is, primitiv is.

Alapbol azt hiszem amugy json (gson) parser van alatta, igy azt fog visszaadni, en egyelore csak sima String request-et kertem le vele, mint Volleynal. Teszteles miatt.

Nezzuk egy konkretabb peldat, mert nem nagyon van a neten, vagy stackoverflowon sem, nekem kellett ezt a megoldast megszulnom.

TEHAT:

Android Studioban dependencies koze:

compile 'com.squareup.retrofit2:retrofit:2.0.1'
compile 'com.squareup.retrofit2:converter-gson:2.0.0'
compile 'com.squareup.retrofit2:converter-scalars:2.0.0'
compile 'com.squareup.okhttp3:logging-interceptor:3.0.1'


Majd Sync Gradle. Ilyenkor ugye letolti ezeket es inkludolja a projectbe. Azaz ezek utan elerheted a funkcioit.
Retrofit 2.0.1 a legujabb library ezert azt importolom. gson converter nekunk elvileg nem kell, mert a scalars-al sima string koverziot akarunk lekerni majd.

Es van a logging-interceptor, ami ahhoz kell, hogy loggolni tudd, hogy miket csinal retrofit a hatterben. Itt eleve szivtam a verziokkal, mert a 3.0.1-ben teljesen mas kod van ugye, mint mondjuk az elozoekben, igy nem volt teljesen kompatibilis nehany esetben az input output a fuggvenyeknel. Vegulis kifiloztam, hogy ez kell nekem.

Elso korben azert kell loggolni minden szart LogCat-re retrofittel, mert nem biztos, hogy az osszes annotaciot megertetted.

Alapbol amugy a retrofit POJO modellekkel dolgozik. POJO: Plain Old Java Object, kb igy nez ki:

import com.google.gson.annotations.SerializedName;

public class UserPass {

@SerializedName("name")
public String name;
@SerializedName("remember")
public String remember;
@SerializedName("password")
public String password;

public UserPass(String name, String remember, String password) {
this.name = name;
this.remember = remember;
this.password = password;
}

}


OK, ezt nem tudom miert irtam ide, mert a mostani peldanal nem fontos, de ha tnaulni akarsz akkor igen:)
Mint emlitettem nem fogok POJO-t es annotaciokat hasznalni itt.

POJO generator online


Nezzuk tovabb, en egy sima http lekerest akarok, nem akarok json-t, kutyafulet, sima stringet akarok vissza. Es POST-olni szeretnek egy sima stringet, nem POJO-t.

Nezzunk egy sajat service-t, ez a chat szervize lesz, vagyis nem teljesen, de majdnem

public interface ChatHttpService {

@GET("/")
Call<ResponseBody> getBase();

@GET("/client_start")
Call<ResponseBody> getClientStart(@Header("Cookie") String cookie);

@FormUrlEncoded
@POST("/belepes")
Call<ResponseBody> getLogin(@Header("Cookie") String cookie, @Field("username") String username, @Field("remember") String remember, @Field("password") String password);
...

}

 

Tehat ez az interface reprezentalja nalunk a chat web-service-t, de ez esetben ne nevezzuk Service-nek, mert en service-nek json-t, xml-t, etc-t hivom. Itt viszont sima html-t kapunk majd vissza.

$GET-el get-el fogunk kuldeni "/"-el pedig a sima BASE_URL-t. ResponseBody-t akarunk visszakapni, nem sajat class-t. Nezzuk a tobbi lekerest.

Ugye ez 3 lekeres. $GET es $POST is van benne. A masodikban mar van egy uj dolog.

@Header("Cookie") String cookie

 

Itt tulajdonkeppen a header-eket tudjuk atadni majd a lekeresbe, termesezetesen statikus $Headers annotacio is van, de nekem dinamikus kellett. A statikus $Headers annotacio a fuggveny fele kerul, nem a fuggveny bemenetere, ahogy a dinamikus.

A kov lekeres egy POST tipusu, formurlencoded, es ujdonsag benne

@Field("username") String username

 

itt azt mondjuk meg mit akarunk post-olni. Tehat magat a data-t.

Ok le van gyartva az interface es megvannak az url-ek, most hogyan tovabb?

Hat kerlek szep, kell nyilvan egy retrofit instance valahol az alkalmazasunkba, es be kell allitani rajta dolgokat, nezzuk is hogyan tortenik ez meg

public static ChatHttpService getRetrofitService(boolean useProxy) {

ChatHttpUrls.useProxy = useProxy;

if (retrofit == null) {
OkHttpClient.Builder clientBuilder = new OkHttpClient().newBuilder();

if (useClientLogging) {
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
clientBuilder.addInterceptor(logging);
}

if (useProxy) {
Proxy p = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("94.23.85.67", 80)); //Spain-fast http
clientBuilder.proxy(p);
}

Logger.E("useProxy:" + useProxy);

client = clientBuilder.build();

retrofit = new Retrofit.Builder()
.baseUrl(ChatHttpUrls.BASE_URL)
.client(client)
//.addConverterFactory(GsonConverterFactory.create())
.addConverterFactory(ScalarsConverterFactory.create())
.build();

chatHttpService = retrofit.create(ChatHttpService.class);
}

return chatHttpService;
}

En egyelore igy oldottam meg, nem tul elegans, de tesztre jo.
A retrofittel egy okhttp-t hasznalunk majd kliensnek. Bovebb leiras errol:

 

https://github.com/square/okhttp/wiki

Amennyiben loggolni akarjuk, hogy miket kuldozget el es kap az okhttp/retrofit, ez esetben hozza kell adnunk egy interceptort. Ennek kulonbozo szintjei vannak, annak fuggvenyeben mit akarunk a LogCat-en viszont latni.

Proxy-val is tudunk csatlakozni ra, lasd a useProxy feltetelt. Vegul egy build()-el es builder pattern-el megkapjuk magat az object-et. Johet a retrofit, Szinten buildert hasznal, beallitjuk a BASE_URL-t, tehat pl http://valami.hu, majd hozzaaadjuk a client-t. Converterfactory-val hozzaadjuk a GSON helyett a Scalarst, mivel nem gson kell most nekunk. Majd create-el generaljuk a Service classt.

Ezek utan a service-t meg tudjuk hivni, es a service-ben levo url-eket is le tudjuk kerni, pl.:

private void loginPart1(final ValueGetCallback<String> callback) {

Call<ResponseBody> result = ChatHttpUrls.getRetrofitService(useProxy).getBase();

result.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {

if (response.isSuccessful()) {

try {
String html = response.headers().toString() + new String(response.body().bytes(), "UTF-8");

php_sess_id = stringCutter.getPHP_SESSIONID(html);

if (php_sess_id.equals("")) {
callback.error("Err 1/3 Sessid null");
} else {
callback.ok("1/3 ok");
//tovabb...
}


} catch (IOException e) {
e.printStackTrace();
}
} else {
callback.error("Err 1/3 response not succesfull");
}

}

@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
t.printStackTrace();
callback.error("Err 1/3 " + t.toString());
}
});
}

 

 En egy sajat callback-et is hasznaltam itt, ez igy nez ki:

public interface ValueGetCallback<T> {

void ok(T result);

void error(T err);
}

 

Szoval, a callback baromi egyszeru, ugye String-et fogunk itt hasznalni, tehat T-t helyettesitjuk arra, majd van egy ok, es egy error callback. Ezeket a MainActivityben vagy akarhol mashol hasznosithatjuk.
Nezzuk tovabb:
 

   Call<ResponseBody> result = ChatHttpUrls.getRetrofitService(useProxy).getBase();

 

useProxy-val eldontjuk akarunk e http proxy-t hasznalni, feltetelezem tudod mi a proxy, igy nem teszek errol se emlitest, most a retrofitet targyaljuk ki.:) (de a lenyeg, hogy http-re http proxy kell, es vannak free proxy list oldalak, sock-ra pedig sock proxy, azonban eleg lassuak, es nem nagyon erdemes hasznalni, nehez talalni oket. azert hasznaljuk oket, hogy ne a sajat ip-nkkel csatlakozzunk vmihez)

getBase()-el lekerjuk ugye a sima BASE_URL-t, valami.hu-t

result.enqueue(new Callback<ResponseBody>()

 


-el pedig elinditjuk a lekerest es egy callback-el megkapjuk, ha a lekeres sikeres volt e vagy errort dobott.

 Ezeket le tudjuk kezelni ott.

En ide ugye sajat callback-et raktam ok es error funkciokkal, ezeket hivom meg egy sima String-el, hogy mi tortent.

 String html = response.headers().toString() + new String(response.body().bytes(), "UTF-8");

Ha a lekeres sikerult, akkor a response.headers() -el tudjuk megnezni milyen headereket
adott vissza, ez akkor jo pl, ha parse-olni akarjuk eztis, pl PHP_SESSID kell.

 

 new String(response.body().bytes(), "UTF-8")

 

O pedig siman a responsebody bytes-jaibol general egy String-et UTF8 kodolassal.
Tehat maga a html oldal lesz string-ben.

Ok, hat egyelore ennyi, hogy hogyan hasznaljuk a retrofitet/okhttp-t arra, hogy html oldalakat kerjunk le.

Nagyon sokmindenre lehet a retrofitet hasznalni, json, xml, multipart, upload files, stb.
Ha erdekel mindennek utananezel, tenyleg nem nehez ez az egesz.

Es gyorsabb, szebb!

A lenyeg kimaradt: 5+2 weboldal html tartalmat 600-700 ms alatt kerte le. Ez eleg jo nemigaz?

komment
Címkék: retrofit
süti beállítások módosítása