筱团Blog筱团のBlog

安卓开发(试水篇)

筱团·2022-12-03 21:21·235 次阅读

安卓开发(试水篇)

Prerequisite#

Android Studio 下载网站:https://developer.android.com/studio 【默认安装最新版】

前置知识#

  • SDK 全称 Software Development Kit,译为软件开发工具包
  • Android Studio 运行项目途径
    • 真机(建议)
    • 模拟器
    • IDE 自带虚拟设备(不建议)
  • 采用 XML 文件进行配置工程相关参数(如布局)

环境#

【下载 SDK 组件】下载刚完成,还需要配置一些东西,但我忘了
【环境变量】打开 SDK 安装目录(C:\Users\XXX\AppData\Local\Android\Sdk),将以下两个目录添加到环境变量

  • C:\Users\XXX\AppData\Local\Android\Sdk\platform-tools
  • C:\Users\XXX\AppData\Local\Android\Sdk\tools
    【真机调试】我用的是小米 6,需要开启开发者模式,并开启 USB 调试USB 安装

终端常用命令#

更多参考

Copy
# 查看设备 adb devices # 关闭 adb adb kill-server # 开启 adb adb start-server # 进入 shell 模式 adb shell # 查看安卓版本 adb shell getprop ro.build.version.release # 查看 SDK 版本 adb shell getprop ro.build.version.sdk # 获取 root 权限(确保已经获取权限) adb root # 安装 apk adb install C:\Users\xxx\Desktop\Magisk.apk

快捷键(官方指南#

  • 代码格式化【Ctrl + Alt + L】

入门目标#

  1. 安卓 UI 和后台逻辑
  2. 网络请求
  3. 序列化和反序列化
  4. 保存 XML 文件

最终可以实现一个含有登录和跳转功能的小型 app
PS:创建项目选择 empty project

安卓 UI 和后台逻辑#

切换到 Project 项目视图,其中后台逻辑和 UI 界面的功能是相对应的

  • main
    • java
      • com.example.android_1
        • MainActivity【后台逻辑】
    • res
      • layout
        • activity_main.xml【UI 界面】
      • values
        • strings.xml【字符串变量】
Copy
<?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="200dp" android:layout_marginTop="150dp" android:background="#ddd" android:orientation="vertical"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:text="@string/txt" android:textAlignment="center" android:textSize="20dp" /> <LinearLayout android:layout_width="match_parent" android:layout_height="50dp" android:paddingLeft="50dp" android:paddingRight="50dp"> <TextView android:layout_width="60dp" android:layout_height="wrap_content" android:gravity="right" android:text="@string/user" /> <EditText android:id="@+id/txt_user" android:layout_width="match_parent" android:layout_height="match_parent" android:inputType="text"></EditText> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="50dp" android:paddingLeft="50dp" android:paddingRight="50dp"> <TextView android:layout_width="60dp" android:layout_height="wrap_content" android:gravity="right" android:text="@string/pwd" /> <EditText android:id="@+id/txt_pwd" android:layout_width="match_parent" android:layout_height="match_parent" android:inputType="textPassword"></EditText> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="50dp" android:gravity="center"> <Button android:id="@+id/btn_login" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginRight="10dp" android:text="登 录"></Button> <Button android:id="@+id/btn_reset" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="10dp" android:text="重 置"></Button> </LinearLayout> </LinearLayout> </LinearLayout>

上面代码是实现 UI 界面,下面会详细介绍:

一、XML 基本格式

Copy
<?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"> <!-- 此时就相当于 HTML 中的空壳 <div><div> --> <!-- LinearLayout 表示线性布局 --> </LinearLayout>

二、各个组件的属性

Copy
// 组件类型 LinearLayout 是线性布局 TextView 是文字 EditText 是输入框 Button 是按钮 // 组件属性(android) // LinearLayout layout_width="match_parent" 长度匹配父组件 layout_height="200dp" 宽度200dp layout_marginTop="150dp" 距离顶部15dp background="#ddd" 背景颜色是灰色 orientation="vertical" 子组件方向竖着排 // TextView text="@string/txt" 文字引用变量txt textAlignment="center" 文字内容居中 textSize="20dp" 文字大小20dp // EditText id="@+id/txt_pwd" 关联绑定 inputType="textPassword" 输入内容是密码类型 // Button layout_height="wrap_content" 高度是内容的长度

关于 dp 和 px 的区别:

  • dp 是虚拟像素,在不同的像素密度的设备上会自动适配
  • px 是真实像素,是固定的
  • 例如当像素密度为 160,1 dp = 1 px
  • 例如当像素密度为 240,1 dp = 1.5 px

三、存储变量的 XML

Copy
<resources> <string name="app_name">Project_1</string> <string name="txt">用户登录</string> <string name="user">用户名:</string> <string name="pwd">密码:</string> </resources>

四、AndroidManifest.xml

AndroidManifest.xml 是文件是整个应用程序的信息描述文件,每当新建一个 xml 页面时,就要多加一个 activity 组件

比如下面展示我有两个页面(HomeMainActivity):

Copy
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <uses-permission android:name="android.permission.INTERNET" /> <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/network_security_config" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.Android_1" tools:targetApi="31"> <activity android:name=".Home"></activity> <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> <meta-data android:name="android.app.lib_name" android:value="" /> </activity> </application> </manifest>

UI 界面写完了,就轮到交互环节了

Copy
package com.example.android_1; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.TextView; import org.w3c.dom.Text; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); TextView txtUser = findViewById(R.id.txt_user); TextView txtPwd = findViewById(R.id.txt_pwd); Button btnLogin = findViewById(R.id.btn_login); btnLogin.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Log.e("日志", "登入"); } }); Button btnReset = findViewById(R.id.btn_reset); btnLogin.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Log.e("日志", "重置"); txtUser.setText(""); txtPwd.setText(""); } }); } }

最后点击运行即可,这就是真机上最简易的 app

网络请求#

采用安卓真机模拟发送网络请求,电脑端模拟 FLASK 服务器的方式

首先安卓发送网络请求(okhttp)需要进行以下配置:

  • src/build.gradle 的 dependencies 中添加 implementation "com.squareup.okhttp3:okhttp:4.9.1"
  • src/main/AndroidManifest.xml 中添加 <uses-permission android:name="android.permission.INTERNET" />
  • src/main/res/xml 中新建 network_security_config.xml 并添加以下内容(仅测试)
Copy
<?xml version="1.0" encoding="utf-8"?> <network-security-config> <!--禁用掉明文流量请求的检查--> <base-config cleartextTrafficPermitted="true" /> </network-security-config>
  • src/main/AndroidManifest.xml 中添加 android:networkSecurityConfig="@xml/network_security_config"(仅测试)

下面代码是完整的 MainActivity.java 代码,用于封装和发送网络请求(其他请求方式参考这篇文章

Copy
package com.example.android_1; import androidx.appcompat.app.AppCompatActivity; import android.content.Context; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.TextView; import java.io.IOException; import java.security.MessageDigest; import java.util.HashMap; import java.util.Map; import java.util.TreeMap; 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; public Context mContext; // 启动执行的函数,相当于主函数 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mContext = this; 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() { // 登录按钮 btnLogin.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { LoginForm(); } }); // 重置按钮 btnReset.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Log.e("日志", "重置"); txtUser.setText(""); txtPwd.setText(""); } }); } // 登录按钮执行的函数 private void LoginForm() { Log.e("日志", "登录"); // 用于存储用户信息 // 效果:{username:"123456", password:"666666"} TreeMap<String, String> dataMap = new TreeMap<String, String>(); // 获取用户输入的用户名和密码 HashMap<String, TextView> objMap = new HashMap<String, TextView>(); objMap.put("username", txtUser); objMap.put("password", txtPwd); for (Map.Entry<String, TextView> entry : objMap.entrySet()) { String key = entry.getKey(); TextView obj = entry.getValue(); String value = String.valueOf(obj.getText()); dataMap.put(key, value); } // 用于校验用户输入信息 // 效果:password666666username123456 StringBuilder sb = new StringBuilder(); for (Map.Entry<String, String> entry : dataMap.entrySet()) { String key = entry.getKey(); String value = entry.getValue(); sb.append(key); sb.append(value); } String dataString = sb.toString(); String signString = md5(dataString); // dataMap = {username:"123456", password:"666666", sign:"5a231fcdb710d73268c4f44283487ba2"} dataMap.put("sign", signString); // 使用线程,发送 HTTP 网络请求 new Thread() { @Override public void run() { OkHttpClient client = new OkHttpClient.Builder().build(); // 发送 post 请求 FormBody form = new FormBody.Builder() .add("user", dataMap.get("username")) .add("pwd", dataMap.get("password")) .add("sign", dataMap.get("sign")) .build(); // 请求网址为测试用本地 FLASK 服务器 IP Request req = new Request.Builder().url("http://10.10.10.111:5000/auth").post(form).build(); Call call = client.newCall(req); try { Response res = call.execute(); ResponseBody body = res.body(); String bodyString = body.string(); // 接收示例:{"status":true, "token":"b96efd24-e323-4efd-8813-659570619cde"} Log.e("获取相应的内容 -->", bodyString); } catch (IOException ex) { Log.e("请求异常 -->", "网络错误"); } } }.start(); } // MD5 加密函数 private String md5(String dataString) { try { MessageDigest instance = MessageDigest.getInstance("MD5"); byte[] nameBytes = instance.digest(dataString.getBytes()); // 十六进制展示 StringBuilder sb = new StringBuilder(); for (byte nameByte : nameBytes) { int val = nameByte & 255; // 负数转换为正数 if (val < 16) { sb.append("0"); } sb.append(Integer.toHexString(val)); } return sb.toString(); } catch (Exception e) { return null; } } }

接着需要获取本机 IP(局域网),方法如下:

Copy
# cmd 直接运行下面命令 python -c "import os, re; print(''.join(re.findall('无线局域网适配器 WLAN:?\n.*\n.*\n.*\n.*IPv4 地址 [\. ]+:(.*)', os.popen('ipconfig').read())[0].split()))"

下面代码是完整的 FLASK 服务器 Python 代码,用于模拟服务器接收网络请求,并返回对应内容

Copy
#! /usr/bin/env python # -*- coding: UTF-8 -*- # @Author: Xiaotuan from flask import Flask, request, jsonify app = Flask(__name__) @app.route('/auth', methods=['POST']) def auth(): print(request.form) # 1. 获取各个参数 user = request.form.get("user") pwd = request.form.get("pwd") sign = request.form.get('sign') # 2. sign 签名的校验(略) # 3. 根据用户名和密码去数据库校验,并返回对应内容 return jsonify({ 'status': True, 'token': "b96efd24-e323-4efd-8813-659570619cde" }) if __name__ == '__main__': # 电脑本机 IP app.run(host='10.10.10.111')

序列化和反序列化#

首先简单介绍一下概念:

  • 序列化:对象 -> 字符串
  • 反序列化:字符串 -> 对象

然后进行配置(添加 Gson 组件):

  • src/build.gradle 的 dependencies 中添加 implementation 'com.google.code.gson:gson:2.8.6'"

接着会用到反序列化(获取登录状态 + token),并创建新的 HttpResponse.java 类文件专门处理

Copy
package com.example.android_1; class HttpResponse { public boolean status; public String token; }

MainActivity.java 文件中添加下面代码:

Copy
// 获取登录状态 + token HttpResponse obj = new Gson().fromJson(bodyString, HttpResponse.class);

保存 XML 文件#

在安卓真机上,app 保存的 token 会存储在 XML 文件中(data/data/com.example.android_1),可使用 adb 来查看,也可以直接在 Android Studio 右侧查看(也只能查看,毕竟没有 root 权限)

使用下面 java 代码可直接处理 XML 文件(三种方式):

  • 保存
Copy
SharedPreferences sp = getSharedPreferences("sp_city", MODE_PRIVATE); SharedPreferences.Editor editor = sp.edit(); editor.putString("token", "b96efd24-e323-4efd-8813-659570619cde"); editor.commit();
  • 删除
Copy
SharedPreferences sp = getSharedPreferences("sp_city", MODE_PRIVATE); SharedPreferences.Editor editor = sp.edit(); editor.remove("token"); editor.commit();
  • 读取
Copy
SharedPreferences sp = getSharedPreferences("sp_city", MODE_PRIVATE); String token = sp.getString("token", "");

最后更新在 MainActivity.java 文件中修改的代码

Copy
try { Response res = call.execute(); ResponseBody body = res.body(); String bodyString = body.string(); // 接收示例:{"status":true, "token":"b96efd24-e323-4efd-8813-659570619cde"} // 1. 获取登录状态 + token HttpResponse obj = new Gson().fromJson(bodyString, HttpResponse.class); // 2. token 保存手机 -> 本地 XML 文件(登录凭证保存到 cookie) SharedPreferences sp = getSharedPreferences("sp_city", MODE_PRIVATE); SharedPreferences.Editor editor = sp.edit(); editor.putString("token", obj.token); editor.commit(); // 3. 验证成功,跳转到新页面 Intent in = new Intent(mContext, Home.class); startActivity(in); Log.e("获取相应的内容 -->", bodyString); } catch (IOException ex) { Log.e("请求异常 -->", "网络错误"); }
posted @   筱团  阅读(235)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示
目录