Android4.0后,规定耗时操作(网络连接,大数据操作)不能在主线程中运行,应放置到子线程,否则会产生Anr(应用无响应)异常。
Android同样规定,UI 操作只能在主线程中进行,这就需要在获取请求后要通知主线程进行 UI 修改。
Android提供通知主线程的方式
1.Context提供的方法
new Thread(new Runnable() {
@Override
public void run() {
runOnUiThread(new Runnable() {
@Override
public void run() {
}
});
}
}).start();
2.使用Handler通知
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
}
};
下面通过简单的登录案例,来了解通信流程。
服务端
@WebServlet(value = "/login")
public class LoginServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
resp.setContentType("text/html;charset=UTF-8");
String userName = req.getParameter("userName");
String userPwd = req.getParameter("userPwd");
if (userName.equals("admin") && userPwd.equals("123"))
resp.getWriter().print("登录成功");
else
resp.getWriter().print("登录失败");
}
}
Android端,Get方式
添加网络权限
<uses-permission android:name="android.permission.INTERNET" />
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private EditText mEtUserName, mEtUserPwd;
private Button mBtnLogin;
private Context mContext;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
initEvent();
}
private void getLogin() {
try {
HttpURLConnection conn = (HttpURLConnection)
new URL(getRequestUrl()).openConnection();
conn.setReadTimeout(3 * 1000);
conn.setRequestMethod("GET");
//Android HttpURLConnection 的输出流开关默认是关闭的
conn.setDoInput(true);
conn.setDoOutput(true);
if (conn.getResponseCode() == 200) {
InputStream is = conn.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
final String result = br.readLine();
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(mContext, result, Toast.LENGTH_LONG).show();
}
});
} else {
Log.i(TAG, "Request fail");
}
} catch (IOException e) {
e.printStackTrace();
}
}
private void initEvent() {
mBtnLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread(new Runnable() {
@Override
public void run() {
getLogin();
}
}).start();
}
});
}
private void initView() {
mEtUserName = findViewById(R.id.et_user_name);
mEtUserPwd = findViewById(R.id.et_user_pwd);
mBtnLogin = findViewById(R.id.btn_login);
mContext = this;
}
private String getRequestUrl() {
return "http://192.168.0.105:8080/login?userName="
+ getUserName() +
"&userPwd="
+ getUserPwd();
}
private String getUserName() {
return mEtUserName.getText().toString().trim();
}
private String getUserPwd() {
return mEtUserPwd.getText().toString().trim();
}
}
Post方式
差别在于参数的传递
private String getRequestUrl() {
return "http://192.168.0.105:8080/login";
}
private void getLogin() {
try {
HttpURLConnection conn = (HttpURLConnection)
new URL(getRequestUrl()).openConnection();
conn.setReadTimeout(3 * 1000);
conn.setRequestMethod("POST");
conn.setDoInput(true);
conn.setDoOutput(true);
//写入参数
String requestParameter = "userName=" +
getUserName() +
"&userPwd=" +
getUserPwd();
conn.getOutputStream().write(requestParameter.getBytes());
if (conn.getResponseCode() == 200) {
InputStream is = conn.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
final String result = br.readLine();
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(mContext, result, Toast.LENGTH_LONG).show();
}
});
} else {
Log.i(TAG, "Request fail");
}
} catch (IOException e) {
e.printStackTrace();
}
}
Handler
Handler是Android提供的一套异步消息处理机制,当子线程处理完数据后,通知主线程更新UI。
Handler组成
Message,Handler,MessageQueue,Looper
1.Message
用来在线程间传递消息,并且可以携带数据,在不同线程间进行交换。
2.Handler
用于发送和处理消息
3.MessageQueue
消息队列,用于存放所有通过Handler发送的消息。
这部分消息会一直存在消息队列中,等待处理。
每个线程只会有一个MessageQueue对象。
4.Looper
Looper是每个线程中的MessageQueue的管家。
Looper调用loop()后,会进入无限循环。
然后每当发现MessageQueue中存在一条消息,就将它取出,并传递到Handler的handlerMessage()中。
每个线程也只有一个Looper对象。
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 201:
String result = (String) msg.obj;
Toast.makeText(mContext, result, Toast.LENGTH_LONG).show();
break;
}
}
};
private void getLogin() {
try {
HttpURLConnection conn = (HttpURLConnection)
new URL(getRequestUrl()).openConnection();
conn.setReadTimeout(3 * 1000);
conn.setRequestMethod("POST");
conn.setDoInput(true);
conn.setDoOutput(true);
//写入参数
String requestParameter = "userName=" +
getUserName() +
"&userPwd=" +
getUserPwd();
conn.getOutputStream().write(requestParameter.getBytes());
if (conn.getResponseCode() == 200) {
InputStream is = conn.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
final String result = br.readLine();
//消息通知
Message message = Message.obtain();
message.what = 201;
message.obj = result;
mHandler.sendMessage(message);
} else {
Log.i(TAG, "Request fail");
}
} catch (IOException e) {
e.printStackTrace();
}
}
获取图片的案例
服务端
将图片放在Tomcat,Root文件夹下。
新建文本文档,存放图片的路径。
Android端
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private Button mBtnPre, mBtnNext;
private ImageView mIvShowImg;
private ArrayList<String> mImgUrlList;
private String mImgUrls = "http://192.168.0.105:8080/img/imgUrl.txt";
private int mCurImgIndex;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
Bitmap bitmap = (Bitmap) msg.obj;
mIvShowImg.setImageBitmap(bitmap);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
initData();
}
private void initData() {
mImgUrlList = new ArrayList<>();
new Thread(new Runnable() {
@Override
public void run() {
getImgUrls();
getImg(0);
}
}).start();
}
//读取图片路径,放入集合
private void getImgUrls() {
InputStream is = getUrlResource(mImgUrls);
if (is != null) {
try {
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String len;
while ((len = br.readLine()) != null) {
mImgUrlList.add(len);
}
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//根据图片路径,获取图片
private void getImg(int index) {
String imgUrl = mImgUrlList.get(index);
String imgName = getImgName(imgUrl);
//判断图片是否已缓存,若已缓存,则不从服务器读取
File goalImg = new File(getFilesDir(), imgName);
if (goalImg.exists() && goalImg.length() > 0) {
Bitmap bitmap = BitmapFactory.decodeFile(goalImg.getAbsolutePath());
Message message = Message.obtain();
message.obj = bitmap;
mHandler.sendMessage(message);
return;
}
InputStream is = getUrlResource(imgUrl);
try {
if (is != null) {
Bitmap bitmap = BitmapFactory.decodeStream(is);
//将图片缓存到本地,
//参数:图片类型,图片缓存的质量 0~100,图片写入
bitmap.compress(Bitmap.CompressFormat.PNG, 100,
openFileOutput(imgName, MODE_PRIVATE));
Message message = Message.obtain();
message.obj = bitmap;
mHandler.sendMessage(message);
is.close();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private void initView() {
mBtnPre = findViewById(R.id.btn_pre);
mBtnNext = findViewById(R.id.btn_next);
mIvShowImg = findViewById(R.id.iv_show_img);
mBtnPre.setOnClickListener(this);
mBtnNext.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_pre:
mCurImgIndex--;
if (mCurImgIndex < 0) {
mCurImgIndex = mImgUrlList.size() - 1;
}
showImg();
break;
case R.id.btn_next:
mCurImgIndex++;
if (mCurImgIndex > mImgUrlList.size() - 1) {
mCurImgIndex = 0;
}
showImg();
break;
}
}
//获取服务器上的资源
private InputStream getUrlResource(String path) {
try {
HttpURLConnection conn = (HttpURLConnection)
new URL(path).openConnection();
if (conn.getResponseCode() == 200) {
return conn.getInputStream();
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
private String getImgName(String path) {
return path.substring(path.lastIndexOf("/") + 1);
}
private void showImg() {
new Thread(new Runnable() {
@Override
public void run() {
getImg(mCurImgIndex);
}
}).start();
}
}