jsoup으로 JTBC 뉴스 RSS 읽어오는 예제 - 2 [Android]
JTBC의 RSS 서비스 링크
http://news.jtbc.joins.com/Etc/RssService.aspx
<RSS 서비스 메인>
<속보 RSS xml>
Jsoup을 이용해 rss 서비스 주소와 이름을 가져오고 이걸로 안드로이드에 Tab layout(탭)으로 만든다
원하는 메뉴(속보, 정치, 경제, 사회, 국제, 문화, 연예, 스포츠, 풀영상, 뉴스랭킹, 뉴스룸, 아침&, 뉴스현장, 정치부회의)를 누르면
해당 서비스 주소에 나오는 뉴스를 ListView(리스트)로 보여준다
[예제 따라하기 2 ~ Jsoup을 이용해 rss서비스 메뉴이름을 탭으로 만든다]
1. Jsoup으로 가져와 찍을 탭의 형태 생각하기
RSS 서비스 메인(http://news.jtbc.joins.com/Etc/RssService.aspx)에는 RSS 서비스 주소와 이름을 제공한다
속보 | http://fs.jtbc.joins.com/RSS/newsflash.xml |
---|
위와 같이 이름에 해당하는 속보, 그리고 실제로 속보 뉴스정보를 볼 수 있는 xml주소로 이루어져있다
이 사이트의 html을 전체 읽어와서 내가 원하는 텍스트인 '속보', 'http://fs.jtbc.joins.com/RSS/newsflash.xml' 만 읽어올 수 있게 만들 예정이다
사용하기 편하게 이 카테고리 제목과 URL주소를 Category라는 하나의 클래스로 만들어서 속보~정치부회의까지 제목과 URL주소를 List<Category>의 객체에 담자
클래스를 가지고 만든 변수명은 객체라고 부른다 (int a 같은 거는 변수 a라고 부른다)
2. 사용하기 편하게 데이터를 담을 Category 클래스 만들기
public class Category {
private String title;
private String url;
//Getter&Setter 추가
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; }
//toString 추가(생략 가능)
@Override
public String toString() { return title+": "+url; }
}
'속보'가 들어갈 title과 'http://fs.jtbc.joins.com/RSS/newsflash.xml'가 들어갈 link를 만들고 이에 대해 Getter와 Setter를 추가했다
대부분은 Getter, Setter는 우클릭해서 생성할수 있는 기능들이 있으니까 그걸 활용하면 편하다
Android Studio에서는
private String title;
private String url;
만 써놓고 그냥 우클릭, Generate를 누르면 메뉴중에 Getter and Setter라고 있다
그걸 누르고 title하고 url 둘다 선택해서 ok하면 알아서 써진다
3. 주소에 접근해 데이터를 읽어오는 CategoryTask 클래스를 내부클래스로 만든다
public class MainActivity extends AppCompatActivity {
private ViewPager mViewPager;
private TabLayout tabLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
mViewPager = (ViewPager) findViewById(R.id.container);
tabLayout = (TabLayout) findViewById(R.id.tabs);
tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
tabLayout.setTabMode(TabLayout.MODE_SCROLLABLE);
//어플이 실행되면 RSS서비스 메인 주소에 접근한다
String rssURL = "http://news.jtbc.joins.com/Etc/RssService.aspx";
new CategoryTask().execute(rssURL);
}
}
class CategoryTask extends AsyncTask<String,Void,Void> {
}
tabLayout의 탭메뉴에 설정을 바꾼다
같은비율로 배치하고 tabLayout.setTabGravity(TabLayout.GRAVITY_FILL)
스크롤할 수 있게 한다 tabLayout.setTabMode(TabLayout.MODE_SCROLLABLE)
rssURL주소를 넣어주고 String rssURL = "http://news.jtbc.joins.com/Etc/RssService.aspx
rssURL주소에 접근한다 new CategoryTask().execute(rssURL)
** 인터넷 주소에 접근할 때는 반드시 쓰레드로 접근해야하는데 이 예제에서는 AsyncTask를 상속받은 클래스를 execute하는 것으로 해결한다
4. rssURL주소의 소스를 jsoup을 이용해 읽어오고 원하는 값을 저장할 List<Category> 객체를 만든다
class CategoryTask extends AsyncTask<String,Void,Void> {
List<Category> categories; //읽어온 데이터를 리스트로 저장
//실제 CategoryTask의 주요 작업
@Override
protected Void doInBackground(String... strings) {
return null;
}
//가장 먼저 실행
@Override
protected void onPreExecute() {
super.onPreExecute();
categories = new ArrayList<>();
}
}
사실 3번까지 하게 되면 맨 밑에
class CategoryTask extends AsyncTask<String,Void,Void> {
}
이 코드가 빨간색으로 뜬다 반드시 작성해야 하는 DoInBackground메소드가 안만들어져서 그렇다
Android Studio에서는 빨간 밑줄 그어진 부분 마지막 위치에서 Alt+Enter하고 Enter를 한번 더 누르면 알아서 만들어진다
이때 AsyncTask 뒤에 입력되는 값의 의미
// 첫번째가 doInBackground로 전달되는 자료형
// 두번째는 onProgressUpdate로 전달되는 자료형
// 세번째는 onPostExecute로 전달되는 자료형
이 CategoryTask라는 클래스에서는 rssURL에 있는 많은 정보 중에서 속보~정치부회의까지의 제목과 그에 따른 xml주소만 가져올건데
이걸 담기 위한 Category 클래스를 이미 만들었다
하나의 Category클래스에는 하나의 제목과 하나의 주소를 저장할 수 있으므로 이걸 다시 List로 묶어준다면
이 이미지상에 있는 정보를 List<Category> categories 객체 하나로 접근할 수 있게 된다
5. doInBackground메소드를 수정하여 본격적으로 Jsoup으로 읽어와서 categories에 전체 정보 저장하기
//실제 CategoryTask의 주요 작업
@Override
protected Void doInBackground(String... strings) {
String rssURL = strings[0];
try {
Document document = Jsoup.connect(rssURL).get();
Elements elements = document.select("table.common_table tbody tr");
Category category;
//한줄씩 가져와서 Category에 저장
for (Element element : elements) {
//title
category = new Category();
category.setTitle(element.select("th").get(0).text());
category.setUrl(element.select("a").get(0).text());
//한줄을 다 읽었으면 categories에 다시 보관
categories.add(category);
}
Log.d("목록 다 읽었는지 찍어볼게요",categories.toString());
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
<아래 Run을 누르면 이런식으로 로그가 찍혀있는걸 볼 수 있다
이번 예제의 가장 중요한 부분이라고 볼 수 있다
실제로 rssURL의 html소스를 분석해서 Jsoup으로 rssURL을 연결하고 select로 원하는 태그만 읽어와서 값을 얻어내는 부분으로
html소스보기를 통해 내가 원하는 값이 있는 태그를 분석할 줄 알아야 한다 (매우매우 중요)
이때 Elements, Element는 모두 Jsoup의 클래스를 import 해야한다
먼저 우리가 3번에서 new CategoryTask().execute(rssURL) 를 하여 rssURL을 가지고 이 클래스에 들어왔다
doInBackground로 전달되는 값은 매개변수에 있는 strings에 순서대로 들어오는데 우리는 String을 하나만 넘겼다(rssURL)
따라서 rssURL값은 아래와 같이 이동되어 CategoryTask안에서 쓸 수 있도록 rssURL을 다시 저장했다
rss메인 주소값을 가진 rssURL String rssURL = "http://news.jtbc.joins.com/Etc/RssService.aspx;
이 rssURL을 CategoryTask를 전달하여 실행 new CategoryTask().execute(rssURL);
doInBackground의 전달받은 값은 strings에 순차적으로 저장된다 strings[0]에 "http://news.jtbc.joins.com/Etc/RssService.aspx"값이 들어있는 것
클래스내부에 rssURL을 만들어 전달받은 rssURL을 저장한다 String rssURL = strings[0];
이제 doInBackground에서 rssURL과 Jsoup을 이용해서 URL에 접근하고 값을 얻어내자
rssURL에 접근해 html문서를 읽어오고 Document document = Jsoup.connect(rssURL).get()
밖의 태그를 먼저 제거한다고 생각하자 Elements elements = document.select("table.common_table tbody tr")
여기까지 하면 elements에는 위의 이미지에서
<tr>
<th>제목</th>
<td><a href="~~~~">링크</a></td>
</tr>
이렇게 생긴게 다 들어가있다고 생각하면 된다
그럼 tr 하나씩 돌아가면서 그 안에 있는 th의 제목 가져오고 a의 주소를 가져오면 우리가 원하는 값만 가져오는 것이다
이제 한줄씩 가져와서 Category에 저장하고 이 값을 categories에 차곡차곡 담자
<tr>를 하나씩 돌아가면서 접근하자 for (Element element : elements) {
저장할 Category 객체를 할당하자 category = new Category()
category에 title 값을 저장하자 category.setTitle(element.select("th").get(0).text()
category에 url 값을 저장하자 category.setURL(element.select("a").get(0).text()
한줄의 정보를 모두 저장한 category를 categories에 추가한다 categories.add(category)
여기서 쓰는 select는 반환형이 Elements로 되어있다
그래서 <th>나 <a>를 select하여 그안의 제목 값이나 주소값을 가져올때 사이에 get(0)을 써주는 것이다
현재 <tr>안에는 <th>나 <a> 둘다 태그가 하나씩 밖에 없으므로 0번째 값을 가져오면 우리가 원하는 값인 것이다
(만약 여러개 있었다면 해당 index를 get(index)해준다)
6. categories를 받아서 탭으로 만드는 TabPagerAdapter 클래스를 만든다
public class TabPagerAdapter extends FragmentStatePagerAdapter {
private int tabCount;
private List<Category> categories;
public TabPagerAdapter(FragmentManager fm, int tabCount, List<Category> categories) {
super(fm);
this.tabCount = tabCount;
this.categories = categories;
}
@Override
public Fragment getItem(int position) {
return null;
}
@Override
public int getCount() {
return 0;
}
@Nullable
@Override
public CharSequence getPageTitle(int position) {
return super.getPageTitle(position);
}
}
이제 데이터는 categories에 저장되어 있으므로 이걸로 탭을 만드는 클래스를 만들어보자
가장 먼저 FragmentStatePagerAdapter를 상속받아주고 tabCount와 categories 선언한다
tabCount와 categories 전부 받는 생성자를 만들고 getPageTitle메소드를 오버라이딩 한다
getItem과 getCount는
public class TabPagerAdapter extends FragmentStatePagerAdapter {
}
여기까지만 쓰면 빨간줄 뜨니까 Alt+Enter로 오버라이딩 해주면 된다
7. CategoryTask에서 만든 TabPagerAdapter를 사용한다
//doInBackground가 끝나면 실행
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
//Category 개수만큼 반복
for (Category c : categories) {
tabLayout.addTab(tabLayout.newTab());
}
TabPagerAdapter adapter = new TabPagerAdapter
(getSupportFragmentManager(),tabLayout.getTabCount(),categories);
mViewPager.setAdapter(adapter);
tabLayout.setupWithViewPager(mViewPager);
}
CategoryTask에 onPostExecute를 오버라이딩 한다
Category 개수만큼 for (Category c : categories) {
탭을 추가해주고 tabLayout.addTab(tabLayout.newTab())
TabPagerAdapter adapter를 만들어서 mViewPager에 어뎁터를 붙인다 mViewPager.setAdapter(adapter)
개인적인 생각으로는 TabLayout을 사용하면 탭이 위에 순서대로 나오고 이 탭을 눌렀을 때의 화면이 나와야 하기 때문에
이 탭을 관리하는게 TabLayout, 탭을 눌렀을 때 나오는 화면을 관리하는게 ViewPager인 것 같다
그래서 TabLayout에 ViewPager를 연결해주고 tabLayout.setupWithViewPager(mViewPager)
이 ViewPager가 실질적으로 보여줄 화면(View)은 Adapter로 다시 연결해주는 것 같다 mViewPager.setAdapter(adapter)
8. mViewPager가 실질적으로 보여줄 화면을 Fragment로 만든다
<MainActivity>
/**
* A simple {@link Fragment} subclass.
* Activities that contain this fragment must implement the
* {@link JTBCFragment.OnFragmentInteractionListener} interface
* to handle interaction events.
* Use the {@link JTBCFragment#newInstance} factory method to
* create an instance of this fragment.
*/
public class JTBCFragment extends Fragment {
// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private static final String ARG_PARAM1 = "param1";
private static final String ARG_PARAM2 = "param2";
// TODO: Rename and change types of parameters
private String mParam1;
private String mParam2;
private OnFragmentInteractionListener mListener;
public JTBCFragment() {
// Required empty public constructor
}
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* @param param1 Parameter 1.
* @param param2 Parameter 2.
* @return A new instance of fragment JTBCFragment.
*/
// TODO: Rename and change types and number of parameters
public static JTBCFragment newInstance(String param1, String param2) {
JTBCFragment fragment = new JTBCFragment();
Bundle args = new Bundle();
args.putString(ARG_PARAM1, param1);
args.putString(ARG_PARAM2, param2);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mParam1 = getArguments().getString(ARG_PARAM1);
mParam2 = getArguments().getString(ARG_PARAM2);
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_jtbc, container, false);
}
// TODO: Rename method, update argument and hook method into UI event
public void onButtonPressed(Uri uri) {
if (mListener != null) {
mListener.onFragmentInteraction(uri);
}
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof OnFragmentInteractionListener) {
mListener = (OnFragmentInteractionListener) context;
} else {
throw new RuntimeException(context.toString()
+ " must implement OnFragmentInteractionListener");
}
}
@Override
public void onDetach() {
super.onDetach();
mListener = null;
}
/**
* This interface must be implemented by activities that contain this
* fragment to allow an interaction in this fragment to be communicated
* to the activity and potentially other fragments contained in that
* activity.
* <p>
* See the Android Training lesson <a href=
* "http://developer.android.com/training/basics/fragments/communicating.html"
* >Communicating with Other Fragments</a> for more information.
*/
public interface OnFragmentInteractionListener {
// TODO: Update argument type and name
void onFragmentInteraction(Uri uri);
}
}
[삭제 후 메인]
public class JTBCFragment extends Fragment {
private static final String ARG_PARAM1 = "param1";
private static final String ARG_PARAM2 = "param2";
private String mParam1;
private String mParam2;
public JTBCFragment() {
}
public static JTBCFragment newInstance(String param1, String param2) {
JTBCFragment fragment = new JTBCFragment();
Bundle args = new Bundle();
args.putString(ARG_PARAM1, param1);
args.putString(ARG_PARAM2, param2);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mParam1 = getArguments().getString(ARG_PARAM1);
mParam2 = getArguments().getString(ARG_PARAM2);
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_jtbc, container, false);
}
}
9. TabPagerAdapter에 실제로 탭 제목을 표시하고 누르면 JTBCFragment로 연결되게 한다
public class TabPagerAdapter extends FragmentStatePagerAdapter {
private int tabCount;
private List<Category> categories;
public TabPagerAdapter(FragmentManager fm, int tabCount, List<Category> categories) {
super(fm);
this.tabCount = tabCount;
this.categories = categories;
}
@Override
public Fragment getItem(int position) {
Category category = categories.get(position);
Fragment fragment = JTBCFragment.newInstance(category.getTitle(),category.getUrl());
return fragment;
}
@Override
public int getCount() {
return tabCount;
}
@Nullable
@Override
public CharSequence getPageTitle(int position) {
return categories.get(position).getTitle();
}
}
getPageTitle메소드는 각 탭의 제목을 표시하게 해준다
탭은 0번째부터 시작하며 여기에선 position으로 몇번째 탭의 제목인지가 위치가 넘어오므로
categories.get(position)을 이용하여 0번째 탭에서는 categories[0]번째 정보("속보","http://~~.xml")를 사용하겠다
여기에서 return값으로 주는 문자열이 탭에 표시되는 제목이므로 categories.get(position).getTitle()로
0번째 탭에는 "속보"가 제목으로 표시되고 1번째 탭에는 "정치" 순으로 제목이 표시된다
getItem은 탭 메뉴를 눌렀을 때 처리되는 내용을 쓴다
이때 몇번째 탭을 눌렀는지가 position으로 넘어오기 때문에 이를 이용하여 categories의 정보를 활용한다
0번째 탭을 누르면 0번째 탭의 title과 url을 전달받아 만든 JTBCFragment를 띄운다는 내용이다
Fragment fragment = JTBCFragment.newInstance(category.getTitle(),category.getUrl());
여기까지 진행했다면 실행결과를 아래와 같이 얻을 수 있다
각 탭을 누르면 밑에가 움직인다
아직까지는 탭을 눌렀을때 JTBCFragment화면이 나오게만 한 것이지 JTBCFragment에 뉴스가 찍히게 코드를 수정한게 아니기 때문에
탭에 제목만 표시되고 이 탭을 누르면 밑에 Hello blank fragment만 나오게 된다
이제 다음 예제를 통해 실제로 탭을 눌렀을 때 해당 rss의 뉴스까지 나오게 할 예정이다
기본은 예제 2번의 Jsoup을 connect하고 select하여 html 태그를 잘라내는것과 동일하며
xml에서 뉴스가 보여질 화면을 꾸미고 이 xml을 이용해 View 클래스를 생성하고 View에 붙일 Adapter클래스를 만든다
'게임 개발 초보자 > 안드로이드 활용 예제' 카테고리의 다른 글
jsoup으로 JTBC 뉴스 RSS 읽어오는 예제 - 코드 전체보기 [Android] (1) | 2018.06.25 |
---|---|
jsoup으로 JTBC 뉴스 RSS 읽어오는 예제 - 3 [Android] (0) | 2018.06.25 |
jsoup으로 JTBC 뉴스 RSS 읽어오는 예제 - 1 [Android] (0) | 2018.06.22 |