互联网应用
实验目的
1.了解android客户端访问互联网的原理;
2.了解http协议、Web API和JSON;
3.掌握WebView和OkHttp库的使用方法。
实验内容
1.创建一个新闻客户端,从指定的网站的Web API接口获取JSON数据并进行解析为Java对象;
2.根据获得的数据(图片需要下载)填充新闻列表并呈现;
3.当用户点击某一条新闻时,打开一个包含WebView的Activity显示新闻内容;
4.用户可以通过单击顶部的“返回”按钮回到新闻列表,然后再查看下一条新闻的详情。
5.本次实验的新闻数据来自于news.163.com和money.163.com。
实验完成后能获得新闻列表并查看新闻,如图1和图2所示。
图1新闻列表
图2 新闻详情
实验原理:
Android客户端访问互联网的方式有多种,其中最常见的一种是访问在互联网(云端)的服务器,通过Web API接口请求数据,数据以JSON形式返回到客户端,客户端解析后(可能需要进一步下载数据)将结果呈现给用户(如图3所示)。
图3 Android应用访问互联网时序图
具体来说,就是:Android客户端负责发出HTTP请求(可以使用系统原生组件HttpClient和UrlConnection或者使用第三方库如OkHttp实现);服务器响应请求,返回数据(JSON格式),Android客户端收到数据后进行解析,一般会使用GSON将其转化为Java对象以便进一步处理;为了将数据友好地呈现出来,客户端一般会使用一些控件(Widget如ListView)显示数据,为此需要某种类型的Adapter实现用数据填充控件;为了数据能够流畅展示,需要使用Holder模式;为了获得更好的用户体验(如下拉刷新和返回顶部),一般需要使用一些额外的UI组件;当用户点击某一新闻的标题时,客户端将打开一个新的Activtiy,它包含了一个用来显示网页内容的WebView,新闻内容将在这里显示,用户可以随时通过左上角的按钮返回到新闻列表。
实验步骤
分析项目:
该项目使用的是https://news.163.com网站,于是找到国内网站的url,发现其直接显示再前端,一般容易抓取信息
再分析https://money.163.com网站,发现其也是直接暴露再href标签上
1 使用Android Studio(3.0以上)打开提供的初始工程包(Android API28),修改包名为com.example.internetapp_自己的学号,如“com.example.internetapp_1751302”。具体方法为,在项目的包上右键在菜单中依次选择Refactor->Rename(图4),在弹出的警告框中选择Rename package(图5),在Rename对话框中修改包名(注意:只需要填写”internetapp_自己的学号”即可,图6),修改后点击Refactor将打开预览窗口,单击窗口左下角的Do Refactor按钮完成修改(图7)。
2.添加Title和TitleAdapter类,如图8所示。
图8添加类菜单
图9 添加类对话框
3.在Title类中添加以下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| private String title; private String picSrc; private String link;
public Title(String title, String picSrc, String link){ this.title = title; this.picSrc= picSrc; this.link = link; }
public String getTitle() { return title; }
public String getPicSrc() { return picSrc; }
public String getLink() { return link; }
|
4 修改TitleAdapter类的内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| package com.example.internetapp_1759116;
import android.content.Context; import android.support.annotation.LayoutRes; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.ImageView; import android.widget.TextView; import com.bumptech.glide.Glide; import java.util.List;
public class TitleAdapter extends ArrayAdapter<Title> { private int resourceId;
public TitleAdapter(@NonNull Context context, @LayoutRes int resource, @NonNull List<Title> objects) { super(context, resource, objects); resourceId = resource; } @NonNull @Override public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { Title title = getItem(position); View view; ViewHolder viewHolder;
if (convertView == null){ view = LayoutInflater.from(getContext()).inflate(resourceId,parent,false); viewHolder = new ViewHolder(); viewHolder.titleText = (TextView)view.findViewById(R.id.title_text); viewHolder.titlePic = (ImageView) view.findViewById(R.id.title_pic); view.setTag(viewHolder); }else{ view = convertView; viewHolder = (ViewHolder) view.getTag(); }
Glide.with(getContext()).load(title.getPicSrc()).into(viewHolder.titlePic); viewHolder.titleText.setText(title.getTitle()); return view;
} public class ViewHolder{ TextView titleText; ImageView titlePic; } }
|
5.在MainActivity中添加一个titleList和adapter成员,如图10
图10 titleList和adapter成员
6.在MainActivity中添加一个request函数,封装http请求,当服务器返回数据后,它将JSON数据转换为News对象放入List中,并且设置TitleList供TitleAdapter使用(见Response回调函数)。注意:address变量为请求的服务器地址,本实验服务器地址支持以下三个,同学们可以随意选择一个:
尝试了
http://10.199.105.121:8800/api/news/world
http://10.199.105.121:8800/api/news/money
PS.服务器使用的是海洋大学内网地址,宿舍网络和手机需要使用SSL VPN连接。由于服务器丢包严重(学校网络问题),可能会有数据获取不到的情况,过段时间应该就会好的
详细代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| public void requestNew(){
String address = "http://10.199.105.121:8800/api/news/domestic"; HttpUtil.sendOkHttpRequest(address, new Callback() { @Override public void onFailure(Call call, final IOException e) { runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(MainActivity.this, e.getMessage(), Toast.LENGTH_SHORT).show(); } }); }
@Override public void onResponse(Call call, Response response) throws IOException { final String responseText = response.body().string();
final NewsList newlist = Utility.parseJsonWithGson(responseText);
titleList.clear(); for (News news:newlist.newsList){ Title title = new Title(news.Title,news.PicSrc,news.Link); titleList.add(title); }
runOnUiThread(new Runnable() { @Override public void run() { adapter.notifyDataSetChanged(); listView.setSelection(0); refreshLayout.setRefreshing(false); }; }); } }); }
|
7.在MainActivity的OnCreate中添加如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| refreshLayout = (SwipeRefreshLayout)findViewById(R.id.swipe_layout); refreshLayout.setColorSchemeColors(getResources().getColor(R.color.colorPrimary)); listView = (ListView)findViewById(R.id.list_view); adapter = new TitleAdapter(this,R.layout.list_view_item, titleList); listView.setAdapter(adapter); listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { Intent intent = new Intent(MainActivity.this,ContentActivity.class); @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { Title title = titleList.get(position); intent.putExtra("title",actionBar.getTitle()); intent.putExtra("link",title.getLink()); startActivity(intent); } });
Button button=findViewById(R.id.rollback); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { listView.smoothScrollToPositionFromTop(0,0); } }); refreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { @Override public void onRefresh() { refreshLayout.setRefreshing(true); requestNew(); } });
requestNew();
|
8.在ContentActivity的OnCreate函数中加入如下代码:
1 2 3 4 5 6 7 8 9 10 11
|
String uri = getIntent().getStringExtra("link"); String title = getIntent().getStringExtra("title"); actionBar.setDisplayShowTitleEnabled(true); actionBar.setTitle(title);
webView.loadUrl(uri);
|
9.检查最终项目的结构如图11,构建并在模拟器运行程序,测试功能是否正常。
图11 项目结构
思考:
1.查看项目完整代码(包括AndroidManifest和Gradle脚本),理解项目的结构,学习新的控件的使用方法,掌握OkHttp库的使用。
2.如果客户端需要登陆才能查看新闻,该如何实现?
1.控件之前都已经学习过。该实验没有可以学习的控件,通过列表动态新增行的话已经在我的大作业中实现
这个为okhttp工具的前期配置
TitleAdapter为对布局和实例进行渲染,建立ListView界面与数据之间的桥梁
contentActivity为对各种控件加以逻辑操作
2.可以建立一个BaseFragment作为父类,在加载数据前对BaseFragment中的用户登陆情况进行验证。
自己按照水产新闻对其进行接口解析
http://www.linyujing.club:59017/json
换成该外网网址
String address = “http://192.168.169.17:5901/json";
内网地址
json解析结果
本文使用 CC BY-NC-SA 3.0 中国大陆 协议许可
具体请参见 知识共享协议
本文链接:https://zyhang8.github.io/2019/10/06/android-exp8/