凌峰创科服务平台

Android如何与PHP服务器端通信?

这篇指南将从零开始,涵盖从环境搭建、核心 API 设计,到 Android 客户端调用的完整流程。

Android如何与PHP服务器端通信?-图1
(图片来源网络,侵删)

目录

  1. 第一部分:PHP 服务器端

    • 1 环境搭建 (推荐 XAMPP)
    • 2 数据库设计 (MySQL)
    • 3 核心概念:RESTful API
    • 4 实践:编写 PHP API
      • 连接数据库
      • 实现用户注册接口 (/register.php)
      • 实现用户登录接口 (/login.php)
      • 实现获取数据接口 (/get_data.php)
    • 5 安全性考虑 (非常重要!)
  2. 第二部分:Android 客户端

    • 1 网络请求库 (推荐 Retrofit + OkHttp)
    • 2 实践:调用 PHP API
      • 添加依赖
      • 创建数据模型 (POJO/Model)
      • 定义 Retrofit 接口
      • 实现网络请求
  3. 第三部分:进阶与最佳实践


第一部分:PHP 服务器端

1 环境搭建

对于初学者,最简单的方式是使用集成环境包,如 XAMPPWAMP

Android如何与PHP服务器端通信?-图2
(图片来源网络,侵删)
  1. 下载并安装 XAMPP:从 Apache Friends 官网 下载适合你操作系统的版本。
  2. 启动 XAMPP Control Panel
  3. 启动 Apache (Web 服务器) 和 MySQL (数据库服务器)。
  4. 创建项目目录
    • 找到 XAMPP 的安装目录,进入 htdocs 文件夹。
    • htdocs 中创建一个新文件夹,my_android_app
    • 你的 PHP 项目根目录就是 http://localhost/my_android_app/

2 数据库设计

我们需要一个简单的用户表来存储数据。

  1. 打开浏览器,访问 http://localhost/phpmyadmin/
  2. 点击“新建”,创建一个数据库,命名为 app_db
  3. app_db 中,执行以下 SQL 语句创建 users 表:
CREATE TABLE `users` (
  `id` INT(11) NOT NULL AUTO_INCREMENT,
  `username` VARCHAR(50) NOT NULL UNIQUE,
  `email` VARCHAR(100) NOT NULL UNIQUE,
  `password` VARCHAR(255) NOT NULL, -- 存储哈希后的密码
  `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

注意password 字段我们使用 VARCHAR(255),因为后面我们会存储哈希值,它比原始密码长得多。

3 核心概念:RESTful API

你的 Android 应用和 PHP 服务器之间通过 API (应用程序编程接口) 进行通信,现代 Web API 遵循 RESTful 风格,其核心是:

  • 使用 HTTP 动词
    • GET: 获取数据 (获取用户信息)。
    • POST: 创建新数据 (注册新用户)。
    • PUT: 更新数据 (修改用户资料)。
    • DELETE: 删除数据 (删除用户)。
  • 使用 URL 表示资源
    • http://yourdomain.com/api/users -> 代表所有用户资源。
    • http://yourdomain.com/api/users/123 -> 代表 ID 为 123 的用户资源。
  • 数据格式:通常使用 JSON,因为它轻量且易于被 Android 解析。

4 实践:编写 PHP API

我们将创建几个 PHP 文件来处理不同的请求,所有文件都放在 htdocs/my_android_app/api/ 目录下。

Android如何与PHP服务器端通信?-图3
(图片来源网络,侵删)

api/config.php - 数据库配置文件

<?php
// 数据库连接配置
define('DB_SERVER', 'localhost');
define('DB_USERNAME', 'root'); // XAMPP 默认用户名
define('DB_PASSWORD', ''); // XAMPP 默认密码为空
define('DB_NAME', 'app_db');
// 创建连接
$conn = new mysqli(DB_SERVER, DB_USERNAME, DB_PASSWORD, DB_NAME);
// 检查连接
if ($conn->connect_error) {
    // 在生产环境中,不要直接输出错误信息,而是记录到日志
    die("Connection failed: " . $conn->connect_error);
}
// 设置字符集,防止中文乱码
$conn->set_charset("utf8mb4");
?>

api/register.php - 处理用户注册

这个脚本接收 POST 请求,包含 username, email, password

<?php
header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Origin: *"); // 允许所有来源的请求(开发环境使用,生产环境需限制)
header("Access-Control-Allow-Methods: POST");
header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");
// 1. 引入配置和必要的库
require_once 'config.php';
require_once 'vendor/autoload.php'; // 如果使用 Composer 的 password_hash
// 2. 获取 POST 数据
$data = json_decode(file_get_contents("php://input"));
// 3. 验证数据
if (!empty($data->username) && !empty($data->email) && !empty($data->password)) {
    $username = $data->username;
    $email = $data->email;
    // 4. 密码哈希 (非常重要!)
    $hashed_password = password_hash($data->password, PASSWORD_DEFAULT);
    // 5. 准备 SQL 语句防止 SQL 注入
    $sql = "INSERT INTO users (username, email, password) VALUES (?, ?, ?)";
    if ($stmt = $conn->prepare($sql)) {
        // 绑定参数
        $stmt->bind_param("sss", $username, $email, $hashed_password);
        // 执行
        if ($stmt->execute()) {
            // 注册成功
            http_response_code(201); // Created
            echo json_encode(array("message" => "User was created."));
        } else {
            // 可能是用户名或邮箱已存在
            http_response_code(503); // Service unavailable
            echo json_encode(array("message" => "Unable to create user. User or email may already exist."));
        }
        $stmt->close();
    }
} else {
    // 数据为空
    http_response_code(400); // Bad request
    echo json_encode(array("message" => "Data is incomplete."));
}
$conn->close();
?>

api/login.php - 处理用户登录

<?php
header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: POST");
header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");
require_once 'config.php';
$data = json_decode(file_get_contents("php://input"));
if (!empty($data->email) && !empty($data->password)) {
    $email = $data->email;
    $sql = "SELECT id, username, password FROM users WHERE email = ?";
    if ($stmt = $conn->prepare($sql)) {
        $stmt->bind_param("s", $email);
        $stmt->execute();
        $result = $stmt->get_result();
        if ($result->num_rows > 0) {
            $user = $result->fetch_assoc();
            // 验证密码
            if (password_verify($data->password, $user['password'])) {
                // 登录成功
                http_response_code(200); // OK
                echo json_encode(array(
                    "message" => "Login successful.",
                    "user" => array(
                        "id" => $user['id'],
                        "username" => $user['username']
                    )
                ));
            } else {
                // 密码错误
                http_response_code(401); // Unauthorized
                echo json_encode(array("message" => "Invalid password."));
            }
        } else {
            // 用户不存在
            http_response_code(401); // Unauthorized
            echo json_encode(array("message" => "User not found."));
        }
        $stmt->close();
    }
} else {
    http_response_code(400);
    echo json_encode(array("message" => "Data is incomplete."));
}
$conn->close();
?>

api/get_data.php - 获取需要展示的数据 (示例)

<?php
header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Origin: *");
require_once 'config.php';
$sql = "SELECT id, title, content FROM posts ORDER BY created_at DESC";
if ($result = $conn->query($sql)) {
    $items = array();
    if ($result->num_rows > 0) {
        while($row = $result->fetch_assoc()) {
            $items[] = $row;
        }
        echo json_encode($items);
    } else {
        echo json_encode(array("message" => "No posts found."));
    }
} else {
    http_response_code(500);
    echo json_encode(array("message" => "Unable to fetch data."));
}
$conn->close();
?>

5 安全性考虑

  • 密码哈希:永远不要存储明文密码!使用 password_hash()password_verify() 是 PHP 的标准做法。
  • 防止 SQL 注入:始终使用 预处理语句 (prepare, bind_param, execute),而不是直接将变量拼接到 SQL 字符串中。
  • 输入验证:在处理任何用户输入前,都要验证其格式和合法性(如邮箱格式、用户名长度等)。
  • HTTPS:在生产环境中,必须使用 HTTPS 协议来加密客户端和服务器之间的通信,防止数据被窃听。
  • CORS (Access-Control-Allow-Origin): 允许任何域名访问你的 API,这在开发时很方便,但在生产环境中应该只允许你的 Android 应用的特定域名。

第二部分:Android 客户端

我们将使用 Retrofit,一个类型安全的 HTTP 客户端,它能极大地简化网络请求。

1 添加依赖

在你的 app/build.gradle 文件的 dependencies 代码块中添加:

// Retrofit & GSON (用于 JSON 解析)
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
// OkHttp (Retrofit 的默认网络客户端)
implementation 'com.squareup.okhttp3:okhttp:4.9.3'
implementation 'com.squareup.okhttp3:logging-interceptor:4.9.3' // 用于打印网络日志
// Coroutines (用于处理异步任务)
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'

2 实践:调用 PHP API

创建数据模型 (POJO/Model)

这些类必须与 PHP 返回的 JSON 结构完全匹配。

User.kt

data class User(
    val id: Int,
    val username: String
)
data class LoginResponse(
    val message: String,
    val user: User? // 登录成功时才有 user
)

Post.kt

data class Post(
    val id: Int,
    val title: String,
    val content: String
)

定义 Retrofit 接口

这个接口声明了所有可用的 API 端点。

ApiService.kt

import retrofit2.Call
import retrofit2.http.*
interface ApiService {
    // 注册
    @POST("api/register.php")
    fun registerUser(
        @Body user: RegisterRequest // 请求体
    ): Call<ApiResponse>
    // 登录
    @POST("api/login.php")
    fun loginUser(
        @Body loginRequest: LoginRequest
    ): Call<LoginResponse>
    // 获取数据
    @GET("api/get_data.php")
    fun getPosts(): Call<List<Post>>
}
// 用于注册的请求体
data class RegisterRequest(
    val username: String,
    val email: String,
    val password: String
)
// 用于登录的请求体
data class LoginRequest(
    val email: String,
    val password: String
)
// 通用的 API 响应
data class ApiResponse(val message: String)

创建 Retrofit 实例并实现请求

ApiClient.kt (单例模式)

import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
object ApiClient {
    private const val BASE_URL = "http://10.0.2.2/my_android_app/" // Android 模拟器访问 localhost 的地址
    // 如果是真实设备,请使用你的电脑局域网 IP,如 "http://192.168.1.100/my_android_app/"
    val apiService: ApiService by lazy {
        Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
            .create(ApiService::class.java)
    }
}

在 Activity 或 ViewModel 中调用

LoginActivity.kt (使用 Kotlin Coroutines)

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import android.widget.Toast
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import retrofit2.HttpException
class LoginActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_login)
        val loginButton: Button = findViewById(R.id.login_button)
        loginButton.setOnClickListener {
            val email = "test@example.com" // 从输入框获取
            val password = "password123"   // 从输入框获取
            // 在 IO 线程执行网络请求
            CoroutineScope(Dispatchers.IO).launch {
                try {
                    val response = ApiClient.apiService.loginUser(LoginRequest(email, password))
                    // 请求成功,在主线程更新 UI
                    if (response.isSuccessful) {
                        val loginResponse = response.body()
                        runOnUiThread {
                            Toast.makeText(this@LoginActivity, loginResponse?.message, Toast.LENGTH_SHORT).show()
                            // 可以保存用户信息,并跳转到主界面
                        }
                    } else {
                        // 服务器返回错误 (如 401, 400)
                        runOnUiThread {
                            Toast.makeText(this@LoginActivity, "Login failed: ${response.code()}", Toast.LENGTH_SHORT).show()
                        }
                    }
                } catch (e: HttpException) {
                    // 处理 HTTP 错误
                    runOnUiThread {
                        Toast.makeText(this@LoginActivity, "Network error: ${e.message}", Toast.LENGTH_SHORT).show()
                    }
                } catch (e: Exception) {
                    // 处理其他异常 (如无网络)
                    runOnUiThread {
                        Toast.makeText(this@LoginActivity, "Error: ${e.message}", Toast.LENGTH_SHORT).show()
                    }
                }
            }
        }
    }
}

第三部分:进阶与最佳实践

  1. 身份验证与授权

    • 登录成功后,服务器会返回一个用户信息,你应该生成一个 Token (如 JWT - JSON Web Token) 返回给客户端。
    • 客户端在后续需要登录才能访问的 API 请求中,需要在请求头中带上这个 Token。
    • PHP 服务器端需要创建一个中间件来验证每个请求的 Token 是否有效。
  2. 使用 Composer

    • 对于更复杂的项目,强烈建议使用 Composer 来管理 PHP 依赖,你可以用它来安装 firebase/php-jwt 来生成和验证 JWT。
  3. 项目结构

    • 将业务逻辑从 PHP 文件中分离出来,创建一个 UserRepository.php 类来处理所有与用户相关的数据库操作,register.phplogin.php 只负责接收请求和返回响应。
  4. 错误处理和日志

    • 在 PHP 中,不要将 mysqlierror_log 的直接输出返回给客户端,应该记录详细的错误日志,并向客户端返回一个通用的、友好的错误信息。
  5. 分页

    • 当数据量很大时(如 get_data.php),实现分页功能至关重要,可以通过在 URL 中传递 pagelimit 参数来实现。

这个指南为你提供了一个从零开始的完整框架,你可以基于这个结构,不断扩展功能,构建出健壮、安全的 Android 应用后端。

分享:
扫描分享到社交APP
上一篇
下一篇