day09-安卓开发基础2
一 开发一个登录案例app
1.1 安卓端xml编写
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="216dp"
android:layout_marginTop="150dp"
android:background="#dddddd"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="用户登录"
android:textAlignment="center"
android:textSize="25dp"></TextView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="15dp"
android:paddingRight="15dp">
<TextView
android:layout_width="60dp"
android:layout_height="match_parent"
android:gravity="center"
android:text="用户名"
></TextView>
<EditText
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/txt_user">
</EditText>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="15dp"
android:paddingRight="15dp">
<TextView
android:layout_width="60dp"
android:layout_height="match_parent"
android:gravity="center"
android:text="密码"></TextView>
<EditText
android:layout_width="match_parent"
android:layout_height="match_parent"
android:inputType="textPassword"
android:id="@+id/txt_pwd">
</EditText>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="60dp"
android:gravity="center">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="5dp"
android:id="@+id/btn_login"
android:text="登录">
</Button>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:id="@+id/btn_reset"
android:text="重置">
</Button>
</LinearLayout>
</LinearLayout>
</LinearLayout>
1.2 安卓端java编写
package com.justin.justinapp;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.os.Looper;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import java.io.IOException;
import okhttp3.Call;
import okhttp3.FormBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
public class MainActivity extends AppCompatActivity {
private TextView txtUser, txtPwd;
private Button btnLogin, btnReset;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
initListener();
}
private void initView() {
// 先找到所有的有用的标签
txtUser = findViewById(R.id.txt_user);
txtPwd = findViewById(R.id.txt_pwd);
btnLogin = findViewById(R.id.btn_login);
btnReset = findViewById(R.id.btn_reset);
}
private void initListener() {
btnReset.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 点击btn_reset标签,执行方法
txtUser.setText("");
txtPwd.setText("");
}
});
btnLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
loginForm();
}
});
}
private void loginForm() {
String username = String.valueOf(txtUser.getText());
String password = String.valueOf(txtPwd.getText());
Toast t= Toast.makeText(MainActivity.this, "登录成功", Toast.LENGTH_SHORT);
new Thread() {
@Override
public void run() {
OkHttpClient client = new OkHttpClient.Builder().build();
FormBody form = new FormBody.Builder().add("user", username).add("pwd", password).build();
Request req = new Request.Builder().url("http://192.168.1.12:8080/login").post(form).build();
Call call = client.newCall(req);
try {
Response res = call.execute();
ResponseBody body = res.body();
String dataString = body.string();
t.show();
Log.e("请求发送成功", dataString);
} catch (IOException ex) {
Log.e("Main", "网络请求异常");
}
}
}.start();
}
}
1.3 配置安卓发送http请求
1.引入,在build.gradle中 implementation "com.squareup.okhttp3:okhttp:4.9.1"
2.配置,在AndroidManifest.xml中
<uses-permission android:name="android.permission.INTERNET"/>
3.支持http(仅测试使用)
-在res/xml下新建security.xml,写入
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true" />
</network-security-config>
-在AndroidManifest.xml加入
<application
...
android:networkSecurityConfig="@xml/security"
...>
1.4 Python后端Flask编写
import uuid
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/login', methods=['POST'])
def login():
username = request.form.get('username')
password = request.form.get('password')
if username == 'justin' and password == '123':
token = str(uuid.uuid4())
return jsonify({'code': 100, 'msg': "登录成功", 'token': token})
else:
return jsonify({'code': 100, 'msg': "用户名或密码错误"})
if __name__ == '__main__':
app.run('0.0.0.0',8080)
二 请求加密
# 一般移动端为了防止被用户抓包,模拟发送请求,一般都会携带一些加密参数,我们模拟这个过程
# 我们使用md5加密来模拟
2.1 移动端
private void loginForm() {
String username = String.valueOf(txtUser.getText());
String password = String.valueOf(txtPwd.getText());
Utils utils = new Utils();
String t1 = String.valueOf(System.currentTimeMillis() / 1000);
String sign = utils.md5(username+"justin");
Toast t = Toast.makeText(MainActivity.this, "登录成功", Toast.LENGTH_SHORT);
new Thread() {
@Override
public void run() {
OkHttpClient client = new OkHttpClient.Builder().build();
FormBody form = new FormBody.Builder().add("username", username).add("password", utils.md5(password)).add("sign", sign).build();
Request req = new Request.Builder().url("http://192.168.1.12:8080/login").post(form).addHeader("ctime", t1).build();
Call call = client.newCall(req);
try {
Response res = call.execute();
ResponseBody body = res.body();
String dataString = body.string();
t.show();
Log.e("main", dataString);
} catch (IOException ex) {
Log.e("Main", "网络请求异常");
Log.e("Main", ex.toString());
}
}
}.start();
}
Utils.java
package com.justin.justinapp01;
import java.security.MessageDigest;
public class Utils {
public String md5(String dataString) {
try {
MessageDigest instance = MessageDigest.getInstance("MD5");
byte[] nameBytes = instance.digest(dataString.getBytes());
// 十六进制展示
StringBuilder sb = new StringBuilder();
for (int i = 0; i < nameBytes.length; i++) {
int val = nameBytes[i] & 255;
if (val < 16) {
sb.append("0");
}
sb.append(Integer.toHexString(val));
}
return sb.toString();
} catch (Exception e) {
return null;
}
}
}
2.2 后端
import uuid
from flask import Flask, request, jsonify
import time
import hashlib
app = Flask(__name__)
def get_md5(s):
md5 = hashlib.md5()
md5.update(s.encode('utf-8'))
return md5.hexdigest()
@app.before_request
def before():
ctime = time.time()
in_ctime = int(request.headers.get('ctime'))
if ctime - in_ctime > 1000:
return jsonify({'code': 888, 'msg': "请求超时"})
@app.route('/login', methods=['POST'])
def login():
username = request.form.get('username')
password = request.form.get('password')
sign = request.form.get('sign')
real_sign = get_md5(username + 'justin')
if not sign == real_sign:
return jsonify({'code': 105, 'msg': "签名错误"})
if username == 'justin' and password == '202cb962ac59075b964b07152d234b70':
token = str(uuid.uuid4())
return jsonify({'code': 100, 'msg': "登录成功", 'token': token})
else:
return jsonify({'code': 101, 'msg': "用户名或密码错误"})
if __name__ == '__main__':
app.run('0.0.0.0', 8080)
三 序列化
# java的序列化包有很多,在安卓中一般使用谷歌的Gson
# 使用步骤
1 在build.gradle中注册
2 新建类
3 序列化
4 反序列化
#############1 新建的类##########################
public class CommonResponse {
public int code;
public String msg;
// 这个是给序列化用的
public CommonResponse(int code,String msg){
this.code=code;
this.msg=msg;
}
public CommonResponse(){
}
}
public class LoginResponse extends CommonResponse {
public String token;
public LoginResponse() {
}
// 这个是给序列化用的
public LoginResponse(int code, String msg, String token) {
this.code = code;
this.msg = msg;
this.token = token;
}
}
#############2 序列化案例##########################
CommonResponse testRes=new CommonResponse(100,"成功");
String testString = new Gson().toJson(testRes);
Log.e("justin-main",testString);
##########################3反序列化案例##########################
LoginResponse obj = new Gson().fromJson(dataString,LoginResponse.class);
obj.code
obj.msg
obj.token
3.1 登录成功后跳转到显示电影页面
// 1 登录成功后跳转
// 1 新建 IndexActivity.java
// 2 新建ListView适配器 layout/list_item.xml
MainActivity
package com.justin.justinapp01;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import com.google.gson.Gson;
import com.justin.justinapp01.entity.CommonResponse;
import com.justin.justinapp01.entity.LoginResponse;
import java.io.IOException;
import okhttp3.Call;
import okhttp3.FormBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
public class MainActivity extends AppCompatActivity {
private TextView txtUser, txtPwd;
private Button btnLogin, btnReset;
@Override
protected void onCreate(Bundle savedInstanceState) {
// 当页面创建时,就好执行这个代码
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); // 关联了layout下那个xml文件
initView();
initListener();
}
private void initView() {
// 先找到所有的有用的标签
txtUser = findViewById(R.id.txt_user);
txtPwd = findViewById(R.id.txt_pwd);
btnLogin = findViewById(R.id.btn_login);
btnReset = findViewById(R.id.btn_reset);
}
private void initListener() {
btnReset.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 点击btn_reset标签,执行方法
txtUser.setText("");
txtPwd.setText("");
}
});
btnLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
loginForm();
}
});
}
private void loginForm() {
String username = String.valueOf(txtUser.getText());
String password = String.valueOf(txtPwd.getText());
Utils utils = new Utils();
String t1 = String.valueOf(System.currentTimeMillis() / 1000);
String sign = utils.md5(username+"justin");
new Thread() {
@Override
public void run() {
OkHttpClient client = new OkHttpClient.Builder().build();
FormBody form = new FormBody.Builder().add("username", username).add("password", utils.md5(password)).add("sign", sign).build();
Request req = new Request.Builder().url("http://192.168.1.12:8080/login").post(form).addHeader("ctime", t1).build();
Call call = client.newCall(req);
try {
Response res = call.execute();
ResponseBody body = res.body();
String dataString = body.string();
LoginResponse obj = new Gson().fromJson(dataString,LoginResponse.class);
if(obj.code==100){
showToast("登录成功");
Intent intent = new Intent(MainActivity.this,IndexActivity.class);
// 启动新的 Activity
startActivity(intent);
}else {
showToast(obj.msg);
}
CommonResponse testRes=new CommonResponse(100,"成功");
String testString = new Gson().toJson(testRes);
Log.e("justin-main",testString);
Log.e("main", dataString);
} catch (IOException ex) {
Log.e("Main", "网络请求异常");
Log.e("Main", ex.toString());
}
}
}.start();
}
private void showToast(final String message) {
// 在 UI 线程中显示 Toast
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this, message, Toast.LENGTH_SHORT).show();
}
});
}
}
IndexActivity.java
package com.justin.justinapp01;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Context;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import com.bumptech.glide.Glide;
import com.google.gson.Gson;
import com.justin.justinapp01.entity.Films;
import java.io.IOException;
import java.net.URL;
import java.util.List;
import okhttp3.Call;
import okhttp3.FormBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
public class IndexActivity extends AppCompatActivity {
ListView listView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_index);
listView = findViewById(R.id.listview);
new Thread() {
@Override
public void run() {
OkHttpClient client = new OkHttpClient();
FormBody form = new FormBody.Builder().build();
Request request = new Request.Builder().url("http://192.168.1.12:8080/film").get().build();//get请求
Call call = client.newCall(request);
try {
Response res = call.execute();
ResponseBody body = res.body();
String dataString = body.string();
Films obj = new Gson().fromJson(dataString, Films.class);
if (obj.code == 100) {
showToast(obj.msg);
// 创建自定义适配器并设置给 ListView
MyAdapter adapter = new MyAdapter(IndexActivity.this, obj);
listView.setAdapter(adapter);
} else {
showToast(obj.msg);
}
} catch (IOException e) {
Log.e("tttt", e.toString());
}
}
}.start();
}
private void showToast(final String message) {
// 在 UI 线程中显示 Toast
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(IndexActivity.this, message, Toast.LENGTH_SHORT).show();
}
});
}
}
class MyAdapter extends BaseAdapter {
private Context context;
private Films dataList;
public MyAdapter(Context context, Films dataList) {
this.context = context;
this.dataList = dataList;
}
@Override
public int getCount() {
return dataList.films.size();
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
convertView = LayoutInflater.from(context).inflate(R.layout.list_item, parent, false);
// 根据布局文件实例化view
TextView tv = (TextView)convertView.findViewById(R.id.name);
tv.setText(this.dataList.films.get(position).name);
TextView synopsis = (TextView)convertView.findViewById(R.id.synopsis);
synopsis.setText(this.dataList.films.get(position).synopsis);
ImageView img = (ImageView)convertView.findViewById(R.id.pic);
//使用Glide加载图片
Glide.with(context)
//加载地址
.load(this.dataList.films.get(position).poster)
//加载失败时,设置默认的图片
.placeholder(R.mipmap.ic_launcher)
//显示的位置
.into(img);
return convertView;
}
}
layout/list_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal"
android:padding="5dp">
<ImageView
android:id="@+id/pic"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="demo"
android:textColor="#000000"
android:textSize="20dp" />
<TextView
android:id="@+id/synopsis"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="demo"
android:textColor="#000000"
android:textSize="15dp" />
</LinearLayout>
</LinearLayout>
配置加载网络图片的模块build.gradle
dependencies {
implementation "com.squareup.okhttp3:okhttp:4.9.1"
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'com.github.bumptech.glide:glide:4.11.0'
}
编写响应类
entity
CommonResponse
Films
FilmsReal
LoginResponse
// CommonResponse
package com.justin.justinapp01.entity;
public class CommonResponse {
public int code;
public String msg;
// 这个是给序列化用的
public CommonResponse(int code,String msg){
this.code=code;
this.msg=msg;
}
public CommonResponse(){
}
}
// Films
package com.justin.justinapp01.entity;
import java.util.ArrayList;
public class Films{
public int code;
public String msg;
public ArrayList<FilmsReal> films;
}
// FilmsReal
package com.justin.justinapp01.entity;
public class FilmsReal {
public String name;
public String poster;
public String synopsis;
}
// LoginResponse
public class LoginResponse extends CommonResponse {
public String token;
public LoginResponse() {
}
// 这个是给序列化用的
public LoginResponse(int code, String msg, String token) {
this.code = code;
this.msg = msg;
this.token = token;
}
}
AndroidManifest.xml 会自动添加
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/security"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.JustinApp01"
tools:targetApi="31">
<activity
android:name=".IndexActivity"
android:exported="false" />
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<uses-permission android:name="android.permission.INTERNET" />
</manifest>
后端flask
import uuid
import json
from flask import Flask, request, jsonify
import time
import hashlib
app = Flask(__name__)
def get_md5(s):
md5 = hashlib.md5()
md5.update(s.encode('utf-8'))
return md5.hexdigest()
@app.before_request
def before():
ctime = time.time()
in_ctime = request.headers.get('ctime')
if in_ctime:
in_ctime = int(in_ctime)
if ctime - in_ctime > 1000:
return jsonify({'code': 888, 'msg': "请求超时"})
@app.route('/login', methods=['POST'])
def login():
username = request.form.get('username')
password = request.form.get('password')
sign = request.form.get('sign')
real_sign = get_md5(username + 'justin')
if not sign == real_sign:
return jsonify({'code': 105, 'msg': "签名错误"})
if username == 'justin' and password == '202cb962ac59075b964b07152d234b70':
token = str(uuid.uuid4())
return jsonify({'code': 100, 'msg': "登录成功", 'token': token})
else:
return jsonify({'code': 101, 'msg': "用户名或密码错误"})
@app.route('/film', methods=['POST','GET'])
def film():
print('请求来了')
with open('./film.json', mode='rt', encoding='utf-8') as f:
dic = json.load(f)
return jsonify(dic)
if __name__ == '__main__':
app.run('0.0.0.0', 8080)
数据film.json
{
"code": 100,
"films": [
{
"name": "八角笼中",
"poster": "https://pic.maizuo.com/usr/movie/b6be063ea8bb7d4ecd09299b800fe510.jpg",
"synopsis": "暑期劲燃现实题材佳作!根据真实事件改编,王宝强六年打磨,动情演绎震撼人心!电影讲述了向腾辉(王宝强 饰)倾注心血想把当地无人照料的孩子培养成才,这让生活本没有出路的孩子们看到了一丝通向未来的曙光。然而,随着往日的表演视频被爆出,这些“残忍、血腥”的画面刺激了不明真相的人们的神经。一夜之间,舆论开始发酵。向腾辉的生活、孩子们的前途都陷入到人们以善良为名编织的大网中,让他们难以挣脱,重回泥沼,关于未来,他们的“出路”又将在哪……"
},
{
"name": "消失的她",
"poster": "https://pic.maizuo.com/usr/movie/149ddb9a7f64e06ed474d963cfc6c405.jpg",
"synopsis": "何非的妻子李木子在去往东南亚度假岛巴兰迪亚的结婚周年旅行中离奇消失。在何非苦寻无果之时,妻子再次现身,何非却坚持眼前的陌生女人并非妻子。女人拿出了身份证明进行自证,夫妻二人似乎都有不可告人的秘密,随着金牌律师陈麦以及当地警察郑成介入到这起离奇案件中,更多的谜团慢慢浮现……"
},
{
"name": "碟中谍7:致命清算(上)",
"poster": "https://pic.maizuo.com/usr/movie/825cd3106925b127e6027b300fb01026.jpg",
"synopsis": "刷新极限的传奇特工系列火线回归,感受今夏最真实震撼的银幕冒险!特工伊森·亨特(汤姆·克鲁斯饰)和他的IMF小组将开启迄今为止最危险的一次任务:追踪一种会危及全人类性命的新型武器,并阻止其落入坏人之手。与此同时,伊森曾对抗过的黑暗势力也正在卷土重来,整个世界的命运岌岌可危。面对神秘而强大的敌人,他不得不面对选择使命还是至爱的终极抉择。"
},
{
"name": "扫毒3:人在天涯",
"poster": "https://pic.maizuo.com/usr/movie/19c503c9c7df7f75dd3713039b4880e0.jpg",
"synopsis": "邱礼涛继《扫毒2》《拆弹专家2》后携《扫毒3:人在天涯》强势回归,古天乐郭富城刘青云三大影帝首次同台飙戏荡平金三角!毒枭康素差(刘青云 饰)和手下张建行(郭富城 饰)、欧志远(古天乐 饰)因一次意外有了过命交情,三人情同手足。康素差被香港警方通缉后带领团队逃到金三角发展,却意外发现身边藏有卧底。曾经的生死兄弟如今分崩离析,敌友难辨,何去何从……"
},
{
"name": "封神第一部:朝歌风云",
"poster": "https://pic.maizuo.com/usr/movie/f57040d1983ab2923e53aedb36a00cd4.jpg",
"synopsis": "中国国民神话史诗大片,震撼视效燃炸暑期!《封神第一部》讲述商王殷寿勾结狐妖妲己,暴虐无道,引发天谴。昆仑仙人姜子牙携“封神榜”下山,寻找天下共主,以救苍生。西伯侯之子姬发逐渐发现殷寿的本来面目,反出朝歌。各方势力暗流涌动,风云变幻端倪初显……三千年想象成真,恢弘史诗再创新,见证中国神话崛起!"
},
{
"name": "超能一家人",
"poster": "https://pic.maizuo.com/usr/movie/0ec798637174c1ba4eac7a2438019230.png",
"synopsis": "开心麻花首部春节档喜剧电影。主人公郑前(艾伦 饰)离家出走漂泊多年,开发了一款“理财神器”APP,不料却被家乡喀西契克市邪恶狡猾的市长乞乞科夫(沈腾 饰)盯上。而此时郑前一家人竟遇到天降陨石获得了超能力,但只要有人离开,超能力便会消失。郑前被迫和不靠谱的家人团结起来,共同抵抗乞乞科夫,上演一场超能力VS钞能力的爆笑故事……"
},
{
"name": "茶啊二中",
"poster": "https://pic.maizuo.com/usr/movie/9f21bcf792f7b12087c8052be3adea6e.jpg",
"synopsis": "在“茶啊二中”,有最懂“我们”的中国式青春!当“学渣”王强与“暴力班主任”石妙娜意外灵魂互换,会是怎样“翻身解气”的体验?暑假解压包式强喜剧,7月14日,一起回到笑得最大声的日子!"
},
{
"name": "超级飞侠:乐迪加速",
"poster": "https://pic.maizuo.com/usr/movie/aa0bd00e9e739e66887dd20fb296a580.jpg",
"synopsis": "超级飞侠乐迪接到一项新任务!一个神秘的包裹,一次跨越大洲的派送,乐迪却因此偶然介入了一场将世界陷于危机的恶作剧中。面对危机,乐迪和其他超级飞侠将发挥各自的救援能力,争分夺秒合力拯救世界!"
},
{
"name": "我爱你!",
"poster": "https://pic.maizuo.com/usr/movie/19dbbdc52057e722a4c6d76a788fc417.jpg",
"synopsis": "你是否想过自己会如何老去?四个花甲老人,两段迟暮之恋,他们的爱善良而纯粹、浪漫又浓烈。在生命这段有限的旅程里,趁还来得及,我要对你说声“我爱你!”。"
}
],
"msg": "ok"
}
四 xml持久化
# 后端获取的数据(如:token等),保存到手机中,一般保存在xml中
# 保存到手机上:`/data/data/包名`
adb shell
su
cd /data/data
cd 包名
cd shared_prefs
ls
cat sp_token.xml
# 保存
SharedPreferences sp = getSharedPreferences("sp_token", MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
editor.putString("token","111111");
editor.commit();
# 删除
SharedPreferences sp = getSharedPreferences("sp_token", MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
editor.remove("token");
#读取
SharedPreferences sp = getSharedPreferences("sp_token", MODE_PRIVATE);
String token = sp.getString("token","");
注意:后期逆向时经常使用,放在xml中的一般都是app刚启动时、刚登录时。
五 拦截器
# 问题
在安卓端发送请求时,每次都要携带固定的数据(请求头中携带),以后不需要每次都手动添加,只需要写一次,以后只要发请求就会携带,
# 解决
使用拦截器
# 使用步骤
1 定义拦截器--》可以定义多个-->
-方式一:写一个类,继承Interceptor,重写intercept方法
-方式二:直接实例化并得到对象
Interceptor interceptor = new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request().newBuilder().addHeader("ctime", "").addHeader("sign", "").build();
Response response = chain.proceed(request);
return response;
}
};
2 在发送请求时,使用拦截器
OkHttpClient client = new OkHttpClient.Builder().addInterceptor(interceptor).build();
5.1 实战
MainActivity.java
public void loginForm() {
// 获取输入框内容
String username = String.valueOf(txt_user.getText());
String password = String.valueOf(txt_pwd.getText());
String sign = Utils.md5(username + "justin");
String ctime = String.valueOf(System.currentTimeMillis() / 1000); // 字符串类型时间戳
Toast t = Toast.makeText(MainActivity.this, "登录成功", Toast.LENGTH_LONG);
// 创建一个新线程,发送网络请求
new Thread() {
@Override
public void run() {
// 使用方式一
CommonInterceptor interceptor=new CommonInterceptor();
OkHttpClient client = new OkHttpClient.Builder().addInterceptor(interceptor).build();
// 使用方式二:
// OkHttpClient client = new OkHttpClient.Builder().addInterceptor(new Interceptor() {
// @Override
// public Response intercept(Chain chain) throws IOException {
// // 1 获取当前时间,获取签名
// String ctime=String.valueOf(System.currentTimeMillis() / 1000);
// String sign= Utils.md5(ctime);
// // 2 把当前时间和签名添加到请求头中
// Request request = chain.request().newBuilder().addHeader("ctime", ctime).addHeader("sign", sign).build();
// // 3 继续执行下面的拦截器
// Response response = chain.proceed(request);
// //4 把返回的响应对象返回回去
// return response;
// }
// }).build();
FormBody form = new FormBody.Builder().add("username", username).add("password", Utils.md5(password)).add("sign", sign).build();
Request req = new Request.Builder().url("http://192.168.1.12:8080/login").post(form).build();
Call call = client.newCall(req);
try {
Response res = call.execute();
ResponseBody body = res.body();// 响应体内容,不是字符串类型,ResponseBody类型
String dataString = body.string(); // ResponseBody类型转成字符串类型
CommonResponse obj = new Gson().fromJson(dataString, CommonResponse.class);
t.show(); //
} catch (IOException e) {
Log.e("MainActivity", e.toString()); // 如果出错,打印日志
}
}
}.start();
}
interceptor/CommonInterceptor.java
import java.io.IOException;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
public class CommonInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
// 1 获取当前时间,获取签名
String ctime=String.valueOf(System.currentTimeMillis() / 1000);
String sign= Utils.md5(ctime);
// 2 把当前时间和签名添加到请求头中
Request request = chain.request().newBuilder().addHeader("ctime", ctime).addHeader("sign", sign).build();
// 3 继续执行下面的拦截器
Response response = chain.proceed(request);
//4 把返回的响应对象返回回去
return response;
}
}
5.2 安卓项目常用做法
定义一个静态变量
import com.justin.demo.interceptor.CommonInterceptor;
import okhttp3.Interceptor;
public class Common {
public static Interceptor interceptor=new CommonInterceptor();
}
以后用直接使用,不需要每次都实例化
OkHttpClient client = new OkHttpClient.Builder().addInterceptor(Common.interceptor).build();
5.3 逆向
# 逆向时,如何定位拦截器
1 根据关键字进行定位(Interceptor):
参数不在业务逻辑而是在拦截器中
关键字搜索 Interceptor ,很可能就是算法生成的位置
2 通过hook定位有哪些拦截器,通过Hook addInterceptor 看执行多少次,就能确定有多少拦截器
hook的通用的脚本
scr = """
Java.perform(function () {
var Builder = Java.user("okhttp3.OkHttpClient.Builder");
Builder.addInterceptor.implementation = function(interceptor){
console.log(interceptor, JSON.stringify(interceptor))
}
});
"""
# 原理解释
OkHttpClient client = new OkHttpClient.Builder().addInterceptor(interceptor).build();
# 在OkHttpClient内部有内部类,Builder,实例化得到对象后,调用Builder类对象的addInterceptor方法,最后调用Builder类对象的build方法,完成实例化OkHttpClient对象
# 所以我们要hook:okhttp3.OkHttpClient.Builder 类,的addInterceptor方法
class OkHttpClient{
class Builder{
fun addInterceptor(Interceptor)
}
fun build(): OkHttpClient = OkHttpClient(this)
}
六 retrofit
# retrofit介绍
retrofit是对okhttp的封装,底层使用了okhttp,让我们发送网络请求更简单
# 使用步骤:
1 build.gradle中引入retrofit
implementation "com.squareup.retrofit2:retrofit:2.9.0"
2 编写接口,声明网络请求
public interface HttpReq {
# 向/api/v1/post 发送POST请求,编码是UrlEncoded,传入userName和password,但被组装成 name=xx&pwd=xx
@POST("/api/v1/post")
@FormUrlEncoded
Call<ResponseBody> postLogin(@Field("name") String userName, @Field("pwd") String password);
# 向/api/v2/xxx发送get请求,传入age,拼接成 /api/v2/xxx?age=19
@GET("/api/v2/xxx")
Call<ResponseBody> getInfo(@Query("age") String age);
# 向/post/users 发送POST请求,编码是json格式 {name:xxxx,age:123}
@POST("/post/users")
Call<ResponseBody> postLoginJson(@Body RequestBody body);
}
3 发送请求
new Thread() {
@Override
public void run() {
Retrofit retrofit = new Retrofit.Builder().baseUrl("http://192.168.1.12:8080/").build();
HttpReq req = retrofit.create(HttpReq.class);
Call<ResponseBody> call = req.postLogin("justin","123");
try {
ResponseBody responseBody = call.execute().body();
String responseString = responseBody.string();
Log.e("Retrofit返回的结果", responseString);
} catch (Exception e) {
e.printStackTrace();
}
}
}.start();
4 简写成
Retrofit retrofit = new Retrofit.Builder().baseUrl("http://192.168.1.12:8080/").build();
try {
ResponseBody responseBody = retrofit.create(HttpReq.class).postLogin("justin","123").execute().body();
String responseString = responseBody.string();
Log.e("Retrofit返回的结果", responseString);
} catch (Exception e) {
e.printStackTrace();
}
6.1 实践
HttpReq
package com.justin.demo;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.http.Field;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.GET;
import retrofit2.http.POST;
import retrofit2.http.Query;
public interface HttpReq {
@POST("/login")
@FormUrlEncoded
Call<ResponseBody> postLogin(@Field("username") String userName, @Field("password") String password, @Field("sign") String sign);
//向/api/v2/xxx发送get请求,传入age,拼接成 /api/v2/xxx?age=19
@GET("/film")
Call<ResponseBody> getFilm(@Query("name") String name);
}
MainActivity
public void loginForm() {
// 获取输入框内容
String username = String.valueOf(txt_user.getText());
String password = String.valueOf(txt_pwd.getText());
String sign = Utils.md5(username + "justin");
Toast t = Toast.makeText(MainActivity.this, "登录成功", Toast.LENGTH_LONG);
// 创建一个新线程,发送网络请求
new Thread() {
@Override
public void run() {
// 使用拦截器
//OkHttpClient client = new OkHttpClient.Builder().addInterceptor(Common.interceptor).build();
//Retrofit retrofit = new Retrofit.Builder().baseUrl("http://192.168.1.12:8080/").client(client).build();
// 不使用拦截器
Retrofit retrofit = new Retrofit.Builder().baseUrl("http://192.168.1.12:8080/").build();
try {
ResponseBody responseBody = retrofit.create(HttpReq.class).postLogin(username,password,sign).execute().body();
String responseString = responseBody.string();
Log.e("Retrofit返回的结果", responseString);
t.show();
} catch (Exception e) {
e.printStackTrace();
}
}
}.start();
}
6.2 逆向
-1 定位代码:搜索URL,可以定位到接口的位置(除去域名和端口) /login
例如:合伙人app,搜索: /v1/partnerLogin/login
七 逆向自己的app
# 1 编译app
# 2 使用charles抓包
# 3 使用jadx打开