一 开发一个登录案例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) { |
| |
| 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) |
二 请求加密
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) |
| |
三 序列化
| |
| |
| |
| 1 在build.gradle中注册 |
| 2 新建类 |
| 3 序列化 |
| 4 反序列化 |
| |
| |
| |
| 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; |
| } |
| } |
| |
| CommonResponse testRes=new CommonResponse(100,"成功"); |
| String testString = new Gson().toJson(testRes); |
| Log.e("justin-main",testString); |
| |
| |
| LoginResponse obj = new Gson().fromJson(dataString,LoginResponse.class); |
| obj.code |
| obj.msg |
| obj.token |
3.1 登录成功后跳转到显示电影页面

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); |
| 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) { |
| |
| 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); |
| |
| 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) { |
| |
| 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(); |
| 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); |
| |
| 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) { |
| |
| 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); |
| |
| 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.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 |
| |
| |
| 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(){ |
| |
| } |
| |
| } |
| |
| package com.justin.justinapp01.entity; |
| |
| import java.util.ArrayList; |
| |
| public class Films{ |
| public int code; |
| public String msg; |
| public ArrayList<FilmsReal> films; |
| } |
| |
| |
| package com.justin.justinapp01.entity; |
| |
| public class FilmsReal { |
| public String name; |
| public String poster; |
| public String synopsis; |
| } |
| |
| |
| |
| 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持久化
| |
| |
| |
| 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(); |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| 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(); |
| String dataString = body.string(); |
| 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 { |
| |
| String ctime=String.valueOf(System.currentTimeMillis() / 1000); |
| String sign= Utils.md5(ctime); |
| |
| Request request = chain.request().newBuilder().addHeader("ctime", ctime).addHeader("sign", sign).build(); |
| |
| Response response = chain.proceed(request); |
| |
| 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(); |
| |
| |
| class OkHttpClient{ |
| class Builder{ |
| fun addInterceptor(Interceptor) |
| } |
| fun build(): OkHttpClient = OkHttpClient(this) |
| |
| } |
| |
六 retrofit
| |
| retrofit是对okhttp的封装,底层使用了okhttp,让我们发送网络请求更简单 |
| |
| |
| 1 build.gradle中引入retrofit |
| implementation "com.squareup.retrofit2:retrofit:2.9.0" |
| 2 编写接口,声明网络请求 |
| public interface HttpReq { |
| |
| @POST("/api/v1/post") |
| @FormUrlEncoded |
| Call<ResponseBody> postLogin(@Field("name") String userName, @Field("pwd") String password); |
| |
| |
| @GET("/api/v2/xxx") |
| Call<ResponseBody> getInfo(@Query("age") String age); |
| |
| |
| @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); |
| |
| @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() { |
| |
| |
| |
| |
| 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


【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
2022-10-19 flask-3
2022-10-19 flask-2
2022-10-19 flask-1
2022-10-19 Rich:Python开发者的完美终端工具!
2020-10-19 浅谈python print(xx, flush = True)
2020-10-19 Linux - 利用软链接解决目录空间不足的问题