开发此App期间参考了大量文章与帖子,由于数量众多,来不及一 一记录。在此一并感谢!
如果在本文中发现与您的程序十分雷同的部分(主要为Http请求部分,Stream转String部分,以及主线程与子线程的通信部分),不胜感激。如有无意冒犯之处,请与我联系。
包括: java文件、布局文件、资源文件、AndroidManifest文件。需要Gson和另一个库(忘了)。
https://download.csdn.net/download/zxylv/12411877
非常详细的安装教程: https://developer.android.google.cn/studio/install?hl=zh-cn
安装之后顺便按照以上网址创建第一个简单的安卓app
需求来源:一方面,一直想尝试一下Android app开发。另一方面,最近因为疫情宅在家,但每天仍需要app签到,操作步骤为:打开app -- 等待加载--找到签到功能--等待加载--填写信息--提交。十分麻烦。抓包之后发现分析了签到数据格式,用Python简单试验之后可行,但python脚本签到需要打开电脑,不如写个手机app。
总体思路:将抓包数据粘贴进app,app对数据进行保存,之后每次打开app自动签到。所以签到步骤变为:打开app--关闭app,十分简洁。
UI构想:一个粘贴数据的编辑框(以免日后签到数据发生变化,方便修改),一个显示签到是否成功以及错误信息的文本框,一个保存数据的按钮。
UI预览:
定位方式为:
1.所有控件均为居中
2.绿框相对于四周定位,cookie框相对于绿框定位,Json框相对于Cookie框定位,按钮框相对于Json框定位。
3.框左上角的文件相对于框定位。
关于定位方式的详细介绍,可以看这里:https://developer.android.google.cn/guide/topics/ui/declaring-layout?hl=zh-cn
UI实际效果如下:
逻辑框图如下:
首先读取数据:数据分为Cookie和Data两部分。Cookie用于身份验证,Data用于存储签到信息例如位置等
public Map myRead(String name) {
//根据name读取不同的信息
SharedPreferences userData = getSharedPreferences(name, Context.MODE_PRIVATE);
Map Data = userData.getAll();
return Data;
}
然后根据读取到的数据是否合理,判断数据是否以及存储
public void myUpload(Handler message) {
//http请求在子线程中完成,所以需要通过Handler将消息发送给主线程
Log log = new Log();
Gson gson = new Gson();
final NetWork netWork = new NetWork();
Headers headers = new Headers();
Map Cookies = myRead("config_cookies");//读取cookie
Map Data = myRead("config_data"); //读取Data
final Map headerPost = headers.post(); //读取 post的请求头
final Map headerGet = headers.get(); //读取 get 的请求头
headerPost.put("Cookie", Cookies.get("cookies")); //在Header中添加cookie信息
headerGet.put("Cookie", Cookies.get("cookies")); //在Header中添加cookie信息
//根据Data长度,判断数据是否已经存储
if (Data.toString().length() > 200) {
......//此处为签到相关程序部分
} else {
//将消息发送给主线程
log.show(message, "code_6:请填入Cookie和Json并保存,下次打开app自动签到");
}
}
请求头设置
public class Headers {
public Map post(){
Map header=new HashMap();
header.put("Host", Your Host);
header.put("Connection", "keep-alive");
header.put("User-Agent", "Mozilla/5.0 ...");
header.put("X-Tag", ...);
header.put("content-type", "application/json");
header.put("Referer", "https://...");
header.put("Accept-Encoding", "gzip, deflate, br");
return header;
}
public Map get(){
Map header=new HashMap();
header.put("Host","Your Host");
header.put("Connection", "keep-alive");
header.put("User-Agent", "Mozilla/5.0...");
header.put("X-Tag", "...");
header.put("content-type", "application/json");
header.put("Referer", "https:...");
header.put("Accept-Encoding", "gzip, deflate, br");
return header;
}
}
发送Get请求,判断签到情况
if (Data.toString().length() > 200) {
//如果读取到的数据合理,则进入签到相关工作
//显示部分提交信息
log.show(message, "__" + (Data.get("Item 1").getClass()).toString() + "__" + Data.get("Item 2") + "__" + Data.get("Item"));
//发送请求
NetWork check_res = netWork.get("https://...", headerGet);
if (check_res.statusCode == 200) {
Map result = gson.fromJson(check_res.result, Map.class);
//将返回的json数据转换为Map结构,方便提取信息
//检查签到情况
if (result.get("item 1").toString().equals("true") && result.get("item 2").toString().equals("true")) {
log.show(message, "无需重复签到:" + result.toString());
} else {
log.show(message, "暂未签到:" + result.toString());
//签到
NetWork upload = netWork.post("https://...", headerPost, Data);
if (upload.statusCode == 200) {
result = gson.fromJson(upload.result, Map.class);
//检查签到情况
if (result.get("item 1").toString().equals("true") && result.get("item 2").toString().equals("true")) {
//显示部分提交信息
log.show(message, "_" + upload.posted);
log.show(message, "签到成功:\n" + upload.result);
}else{
log.show(message, "签到失败:\n" + upload.result);
}
} else {
log.show(message, "code_3:网络连接错误" + upload.result);
}
}
} else if (check_res.statusCode == 0) {
log.show(message, "code_8:程序出错" + check_res.result);
} else {
log.show(message, "code_9:检查状态失败");
}
}
以下为Post的封装,get也是类似的操作,篇幅原因就省略了
public NetWork post(String url_String, Map headers, Map data){
this.statusCode=0;
this.result = null;
try {
URL url = new URL(url_String);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setReadTimeout(5000);
connection.setConnectTimeout(5000);
connection.setDoOutput(true);
connection.setDoInput(true);
connection.setUseCaches(false);
//遍历请求头的每一项,生成请求头
for (Object Key : headers.keySet()) {
connection.setRequestProperty(Key.toString(), headers.get(Key).toString());
}
JSONObject jsonObject = new JSONObject(data);
JSONArray nameList = new JSONArray();
nameList = jsonObject.names();
//将字符串形式的数值改为整数形式的数值
for (int i = nameList.length(); i > 0; i--) {
try {
jsonObject.put(nameList.getString(i - 1), Integer.valueOf(jsonObject.getString(nameList.getString(i - 1))));
} catch (Throwable e) {
}
}
String Data = jsonObject.toString();
//截取四项有代表性的信息作为返回,以便判断签到数据是否设置正确
JSONObject temp = new JSONObject();
temp.put("item 1",jsonObject.get("item 1"));
temp.put("item 2",jsonObject.get("item 2"));
temp.put("item 3",jsonObject.get("item 3"));
temp.put("item 4",jsonObject.get("item 4"));
this.posted = temp.toString();
connection.getOutputStream().write(Data.getBytes());
this.statusCode = connection.getResponseCode();
if (this.statusCode == 200) {
InputStream is = connection.getInputStream();
connection.disconnect();
//将请求得到的Stream格式的数据转换为String形式
this.result = Stream.Stream2String(is);
} else {
this.result = "";
}
}catch(Throwable e){}
return this;
}
Stream转换为String的方法
public class Stream {
public static String Stream2String(InputStream inStream) throws Exception{
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
byte[] buffer = new byte[2048];
int len = 0;
while((len = inStream.read(buffer)) != -1)
{
outStream.write(buffer,0,len);
}
inStream.close();
String output= new String(outStream.toByteArray());
return output;
}
}
从输入框读取数据,并保存
public void save_data(View view) {
//------------------读取字符串---------------------//
TextView message_box = (TextView) findViewById(R.id.logView);
Log log = new Log();
EditText editData = (EditText) findViewById(R.id.editData);
String Data = editData.getText().toString();
EditText editCookies = (EditText) findViewById(R.id.editCookies);
String Cookies = editCookies.getText().toString();
//存储数据
if (mySave("config_data", Data) && mySave("config_cookies", "cookies: " + Cookies)) {
toast("code_3:" + "数据已保存,请再次打开app填报");
} else {
toast("code_2:Cookies或Data有误,数据未保存");
}
}
其中,mySave() 按名称存储数据
public boolean mySave(String name, String Data_Str) {
//根据名称name存储数据Data_Str
myData mydata = new myData();
mydata = mydata.DataFormat(Data_Str);//将数据格式化为Json格式
if (mydata.status == true) {
SharedPreferences userData = getSharedPreferences(name, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = userData.edit();
for (Object Key : mydata.data_map.keySet()) {
//遍历每一项数据,依次写入文件
editor.putString(Key.toString(), mydata.data_map.get(Key).toString());
}
if (editor.commit()) {
return true;
} else {
return false;
}
} else {
return false;
}
}
其中,mydata.DataFormat() 将输入的数据格式化为规范的Json格式
public class myData {
public boolean status;
public Map data_map ;
public myData DataFormat(String Data_Str){
this.status= false;
//数据Json化
Data_Str = Data_Str.replaceAll("^\\{*\"*","");
Data_Str = Data_Str.replaceAll("\"*\\}*$","");
Data_Str = Data_Str.replaceAll("\"*\\s*:\\s+\"*","\" : \"");
Data_Str = Data_Str.replaceAll("\"*\\s*,\\s*\\n\\s*\"*","\" ,\n \"");
Data_Str = Data_Str.replaceAll(":\\s*\"\\s*,\\s*\\n",": \"\",\n");
Data_Str = "{\""+ Data_Str + "\"}";
Gson gson=new Gson();
//检查Data长度,以免存储空数据
if(Data_Str.length()>20){
try{
JSONObject json = new JSONObject(Data_Str);
this.data_map = gson.fromJson(json.toString(), Map.class);
this.status = true;
}catch(JSONException err){
this.status= false;
}
}else {
this.status = false;
}
return this;
}
}
public void toast(String data) {
TextView message = (TextView) findViewById(R.id.logView);
String old_data = message.getText().toString();
//新现实的信息跟在旧信息之后,并以换行符隔开
old_data = old_data + "\n" + data;
int _n_count = 0;
int i;
//限制显示的行数为6行
for (i = old_data.length(); i > 0; i--) {
if (old_data.substring(i - 1, i).equals("\n")) {
_n_count++;
if (_n_count > 6) {
break;
}
}
}
old_data = old_data.substring(i );
//去除开头的换行符,以免第一行为空行
if(old_data.startsWith("\n")){
old_data = old_data.substring(1);
}
//限制显示字符数为240
if (old_data.length() > 240) {
old_data = old_data.substring(old_data.length() - 240);
}
//显示信息
message.setText(old_data);
}
-------------完---------------------