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();
}
}