Computer >> 컴퓨터 >  >> 문제 해결 >> Android

안드로이드 앱에서 웹 API 데이터 가져오기 구현하는 방법

앱 개발자라면 사용자에게 다양한 데이터를 제공하기 위해 웹 API를 연동해야 하는 경우가 자주 있습니다. 다행히 생각만큼 어렵지 않으며, XML 또는 JSON 형식으로 구현할 수 있습니다. 이 튜토리얼에서는 안드로이드 앱에 간단한 웹 API 데이터 가져오기(fetcher) 기능을 구축하는 방법을 단계별로 살펴보겠습니다.

이 튜토리얼에서는 XML 대신 JSON을 사용합니다.

JSON으로 코드 작성하기

먼저 새로운 리스트 뷰 XML 파일을 만들고 아래 코드를 입력합니다.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent">
<ListView android:id="@+id/list_title_list" android:layout_width="match_parent" android:layout_height="match_parent"> </ListView>
</LinearLayout>

다음으로 또 하나의 XML 파일을 생성합니다. 이 파일에는 화면에 표시할 요소들(ImageView, TextView 등)을 정의합니다.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="80dp"
android:orientation="vertical"
android:padding="5dp" >
<ImageView
android:id="@+id/iv_icon_social"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_centerVertical="true"
android:visibility="gone" />
<LinearLayout
android:id="@+id/thumbnail"
android:layout_width="fill_parent"
android:layout_height="85dp"
android:layout_marginRight="50dp"
android:layout_marginTop="0dp"
android:layout_toRightOf="@+id/iv_icon_social"
android:gravity="center_vertical"
android:orientation="vertical"
android:padding="5dip"
android:visibility="visible" >

<TextView
android:id="@+id/txt_ttlsm_row"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingLeft="10dp"
android:text="Sample text"
android:textSize="18dp"
android:textStyle="bold" />

<TextView
android:id="@+id/txt_ttlcontact_row2"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="0dp"
android:layout_marginTop="3dp"
android:paddingLeft="10dp"
android:maxEms="20"
android:maxLines="2"
android:singleLine="false"
android:ellipsize="end"
android:text="Sample text2"
android:textColor="#808080"
android:textSize="15dp"
android:textStyle="normal" />

</LinearLayout>

</RelativeLayout>

AsyncTask로 API 데이터 가져오기

API에서 데이터를 가져오려면 메인 액티비티로 이동한 뒤 AsyncTask를 사용해야 합니다. API 호출은 메인 스레드에서 수행할 수 없기 때문입니다. AsyncTask는 백그라운드 프로세스를 처리하면서 그 결과를 UI에 표시해 주는 스레드입니다. 네트워크 작업을 메인 스레드에서 실행하면 대개 앱이 비정상 종료됩니다. 이 부분이 다소 혼란스럽다면 RapidAPI에서 제공하는 심화 API 튜토리얼을 참고하는 것도 좋은 방법입니다.

class DownloadFilesTask extends AsyncTask < Void, Void, String >
{
private final ProgressDialog dialog = new ProgressDialog(MainActivity.this);
@Override
protected void onPreExecute()
{
super.onPreExecute();
this.dialog.setMessage("Signing in...");
this.dialog.show();
}
@Override
protected String doInBackground(Void...params)
{
ServiceHandler sh = new ServiceHandler();
String jsonStr = sh.makeServiceCall("https://jsonplaceholder.typicode.com/albums/", ServiceHandler.GET);
Log.d("res1", jsonStr);
return jsonStr;

}
@Override
protected void onPostExecute(String response)
{
super.onPostExecute(response);
Log.d("res2", response);
dialog.dismiss();
if (response != null)
{
try
{

JSONArray arr = new JSONArray(response);
DataModel mDatModel = new DataModel();
for (int i = 0; i < arr.length(); i++)
{

JSONObject c = arr.getJSONObject(i);
String id=c.getString(ID);
String title = c.getString(TITLE);
String uid = c.getString(USER_ID);
id_array.add(id);

// Toast.makeText(getApplicationContext(), title, Toast.LENGTH_LONG).show();
}

adapter = new AAdapter(MainActivity.this, id_array);
l.setAdapter(adapter);
}

catch (Exception e)
{}
}
}
}

MainActivity.java

public class MainActivity extends AppCompatActivity implements AdapterView.OnItemClickListener

{

AAdapter adapter;

ArrayList < String > id_array = new ArrayList < String > ();

ArrayList < String > notice_array = new ArrayList < String > ();

Button b1;

ListView l;

private final static String SERVICE_URI = "https://jsonplaceholder.typicode.com/albums/";

private static final String TAG_QUESTIONS = "Questions";

private static final String USER_ID = "userId";

private static final String ID = "id";

private static final String TITLE = "title";

JSONArray questions = null;

protected void onSaveInstanceState(Bundle outState)

{

super.onSaveInstanceState(outState);

}

protected void onRestoreInstanceState(Bundle savedInstanceState)

{

super.onRestoreInstanceState(savedInstanceState);

}

@Override

protected void onCreate(Bundle savedInstanceState)

{

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

l = (ListView) findViewById(R.id.list);

new DownloadFilesTask().execute();

l.setOnItemClickListener(new AdapterView.OnItemClickListener()

{

@Override

public void onItemClick(AdapterView < ? > parent, View view, int position, long id)

{

String abc = id_array.get(position);

Toast.makeText(getBaseContext(), id_array.get(position), Toast.LENGTH_LONG).show();

Intent n = new Intent(MainActivity.this, Titleshow.class);

n.putExtra("id", id_array.get(position));

startActivity(n);

}

});

}

@Override

public void onItemClick(AdapterView < ? > parent, View view, int position, long id)

{}

메인 액티비티에서 API 응답을 확인할 수 있습니다.

ServiceHandler 클래스

아래의 ServiceHandler는 웹 API에 요청을 보내고 응답을 받아오는 역할을 담당하는 서비스 핸들러 클래스입니다. GET과 POST 두 가지 HTTP 요청 방식을 모두 지원합니다.

ServiceHandler sh = new ServiceHandler();

String jsonStr = sh.makeServiceCall("https://jsonplaceholder.typicode.com/albums/", ServiceHandler.GET);

this nothing but a service handle class to request and get the respose from web api so we will do all this from web api.

ServiceHandler.java

class ServiceHandler

{

static String response = null;

public final static int GET = 1;

public final static int POST = 2;

public ServiceHandler()

{}

/**

* Making service call

*

* @url - url to make request

* @method - http request method

*/

public String makeServiceCall(String url, int method)

{

return this.makeServiceCall(url, method, null);

}

/**

* Making service call

*

* @url - url to make request

* @method - http request method

* @params - http request params

*/

public String makeServiceCall(String url, int method, List < NameValuePair > params)

{

try

{

// http client

DefaultHttpClient httpClient = new DefaultHttpClient();

HttpEntity httpEntity = null;

HttpResponse httpResponse = null;

// Checking http request method type

if (method == POST)

{

HttpPost httpPost = new HttpPost(url);

// adding post params

if (params != null)

{

httpPost.setEntity(new UrlEncodedFormEntity(params));

}

httpResponse = httpClient.execute(httpPost);

}

else if (method == GET)

{

// appending params to url

if (params != null)

{

String paramString = URLEncodedUtils.format(params, "utf-8");

url += "?" + paramString;

}

HttpGet httpGet = new HttpGet(url);

httpResponse = httpClient.execute(httpGet);

}

httpEntity = httpResponse.getEntity();

response = EntityUtils.toString(httpEntity);

}

catch (UnsupportedEncodingException e)

{

e.printStackTrace();

}

catch (ClientProtocolException e)

{

e.printStackTrace();

}

catch (IOException e)

{

e.printStackTrace();

}

return response;

}

}

커스텀 어댑터(Adapter) 만들기

다음으로 AsyncTask 내부에서 사용할 어댑터 클래스가 필요합니다. setAdapter 메서드를 사용하려면 커스텀 어댑터를 직접 만들어야 합니다.

public class AAdapter extends BaseAdapter

{

private Activity activity;

// private ArrayList<HashMap<String, String>> data;

private static ArrayList title;

private static LayoutInflater inflater = null;

public AAdapter(Activity a, ArrayList b)

{

activity = a;

this.title = b;

inflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

}

public int getCount()

{

return title.size();

}

public Object getItem(int position)

{

return position;

}

public long getItemId(int position)

{

return position;

}

public View getView(int position, View convertView, ViewGroup parent)

{

View vi = convertView;

if (convertView == null) vi = inflater.inflate(R.layout.abcd, null);

TextView title2 = (TextView) vi.findViewById(R.id.txt_ttlsm_row); // title

String song = title.get(position).toString();

title2.setText(song);

TextView title22 = (TextView) vi.findViewById(R.id.txt_ttlcontact_row2); // notice

String song2 = title.get(position).toString();

title22.setText(song2);

return vi;

}

}

안드로이드에서 어댑터 클래스는 추상 클래스로 취급되므로 모든 메서드를 반드시 구현해야 합니다. 그중에서도 getView() 메서드가 특히 중요한데, 여기에 추가 코드를 작성하여 두 번째 XML 레이아웃 파일을 화면에 표시하게 됩니다.

inflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

이미지 로더용 어댑터

API에서 가져온 데이터에 이미지가 포함되어 있다면, 인터넷에서 이미지를 불러올 수 있는 어댑터 클래스도 추가로 필요합니다.

public class MyAdapter extends BaseAdapter

{

ImageLoader imageLoader;

private Context ctx;

ArrayList < AlbumData > list;

public MyAdapter(Context ctx, ArrayList < AlbumData > mDataList)

{

list = mDataList;

this.ctx = ctx;

}

public int getCount()

{

return list.size();

}

public Object getItem(int position)

{

return position;

}

public long getItemId(int position)

{

return position;

}

public View getView(int position, View convertView, ViewGroup parent)

{

// View vi = convertView;

ViewHolder viewHolder;

if (convertView == null)

{

LayoutInflater vi = (LayoutInflater) ctx.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

convertView = vi.inflate(R.layout.abcde, null);

viewHolder = new ViewHolder();

viewHolder.imageView = (ImageView) convertView.findViewById(R.id.image);

viewHolder.textView = (TextView) convertView.findViewById(R.id.txt_ttlsm_row);

convertView.setTag(viewHolder);

}

else

{

viewHolder = (ViewHolder) convertView.getTag();

}

AlbumData data = list.get(position);

String a = data.getThumbnailUrl();

if (data != null)

{

viewHolder.textView.setText(data.getTitle());

String url = a;

Glide.with(ctx).load(url).centerCrop().placeholder(R.drawable.abhi).crossFade().into(viewHolder.imageView);

/* Picasso.with(ctx)

.load("https://preview.keenthemes.com/conquer/assets/plugins/jcrop/demos/demo_files/image2.jpg")

.into(viewHolder.imageView);*/

/* ImageLoader imageLoader = ImageLoader.getInstance();

imageLoader.displayImage("https://www.keenthemes.com/preview/conquer/assets/plugins/jcrop/demos/demo_files/image1.jpg", viewHolder.imageView);*/

}

return convertView;

}

public class ViewHolder

{

ImageView imageView;

TextView textView;

}

코드 주석에는 Picasso 이미지 로더 사용 예시도 함께 담았습니다. Picasso는 안드로이드를 위한 강력한 오픈소스 이미지 다운로더이자 캐싱 라이브러리로, 앱에서 가벼운 용량을 유지할 수 있다는 장점이 있습니다. 반면 GIF 지원, 더 나은 메모리 관리, 폭넓은 커스터마이징이 필요하다면 Glide 이미지 로더를 사용하는 것이 좋습니다.

ViewHolder 패턴

public class ViewHolder

{

ImageView imageView;

TextView textView;

}

ViewHolder 클래스는 ViewHolder의 정적(static) 인스턴스를 생성하여 첫 로딩 시 뷰 항목에 연결합니다. 이후 호출에서는 뷰 태그(tag)에서 해당 인스턴스를 꺼내 재사용합니다.

getView() 메서드는 ListView에 항목이 많을 때처럼 매우 자주 호출됩니다. 따라서 setter와 getter 메서드를 담은 데이터 모델 클래스를 함께 구현해 두면 배열 리스트의 요소를 손쉽게 다룰 수 있어 이후 개발 과정이 한결 편리해집니다.

데이터 모델 클래스

public class AlbumData

{

int albumId;

int id;

String title;

String url;

String thumbnailUrl;

public int getAlbumId()

{

return albumId;

}

public void setAlbumId(int albumId)

{

this.albumId = albumId;

}

public int getId()

{

return id;

}

public void setId(int id)

{

this.id = id;

}

public String getTitle()

{

return title;

}

public void setTitle(String title)

{

this.title = title;

}

public String getUrl()

{

return url;

}

public void setUrl(String url)

{

this.url = url;

}

public String getThumbnailUrl()

{

return thumbnailUrl;

}

public void setThumbnailUrl(String thumbnailUrl)

{

this.thumbnailUrl = thumbnailUrl;

}

}

마무리 및 테스트

지금까지 소개한 내용만으로 기본적인 구현에 필요한 모든 요소를 갖추었습니다. 완성된 API 데이터 가져오기 기능은 BlueStacks 같은 앱 에뮬레이터에서 출력 결과를 테스트해 볼 수 있습니다.