You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin Android应用API调用失败:无法加载网站数据求助

Android API连接失败排查方案

问题背景

这是我开发的第一个Android应用,使用Kotlin+最新版Android Studio开发,调用个人网站API时提示连接失败,无法加载数据。已通过Postman验证API功能正常,推测问题出在应用端,以下是完整代码及排查修复方案:


可能的问题及修复方案

1. API响应字段不匹配(最可能原因)

检查JobModel的字段名是否和API返回的JSON字段完全一致:

  • 当前JobModel中字段为app_contect,若API返回的是app_content(拼写错误),Gson解析会失败,导致response.body()为空。

修复代码(JobModel.kt):

package com.khm.jobstest

data class JobModel(
    val name: String,
    val app_content: String, // 修正拼写错误
    val app_image: String
)

同时修改JobAdapter中的对应字段:

// JobAdapter.kt onBindViewHolder方法
holder.jobDetails.text = job.app_content // 同步修正

2. Retrofit实例重复创建

MainActivity中手动创建Retrofit实例,而项目已存在RetrofitInstance单例类,重复创建可能导致配置不一致。

修复代码(MainActivity.kt):

// 替换原Retrofit创建代码
val apiService = RetrofitInstance.retrofit.create(ApiService::class.java)

3. 网络状态检测不全面

NetworkUtils仅检测WIFI和蜂窝网络,忽略了VPN、以太网等其他网络类型,可能误判网络状态。

修复代码(NetworkUtils.kt):

package com.khm.jobstest
import android.content.Context
import android.net.ConnectivityManager
import android.net.NetworkCapabilities

object NetworkUtils {
    fun isOnline(context: Context): Boolean {
        val connectivityManager =
            context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
        val network = connectivityManager.activeNetwork ?: return false
        val capabilities = connectivityManager.getNetworkCapabilities(network) ?: return false
        return capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) ||
                capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) ||
                capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN) || // 添加VPN支持
                capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) // 添加以太网支持
    }
}

4. Android 网络安全配置(若网站证书存在问题)

如果你的网站使用自签名证书或证书不被Android信任,需配置网络安全策略:

  1. 创建res/xml/network_security_config.xml:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <domain-config cleartextTrafficPermitted="false">
        <domain includeSubdomains="true">trainermods.net</domain>
        <!-- 允许信任该域名的证书 -->
        <trust-anchors>
            <certificates src="system" />
            <certificates src="user" /> <!-- 若使用自签名证书,添加这行 -->
        </trust-anchors>
    </domain-config>
</network-security-config>
  1. 在AndroidManifest.xml的application标签中添加配置:
android:networkSecurityConfig="@xml/network_security_config"

5. 增强错误日志排查

onFailure中仅显示错误消息,添加完整堆栈日志便于定位问题:

修复代码(MainActivity.kt):

override fun onFailure(call: Call<List<JobModel>>, t: Throwable) {
    val errorMsg = "فشل تحميل البيانات: ${t.message}"
    Toast.makeText(this@MainActivity, errorMsg, Toast.LENGTH_SHORT).show()
    t.printStackTrace() // 打印完整堆栈到Logcat
}

完整代码参考

AdMobHelper.kt

package com.khm.jobstest

import android.app.Activity
import com.google.android.gms.ads.AdRequest
import com.google.android.gms.ads.AdView
import com.google.android.gms.ads.MobileAds

object AdMobHelper {
    fun initializeAdMob(activity: Activity) {
        MobileAds.initialize(activity) {}
    }

    fun loadBannerAd(adView: AdView) {
        val adRequest = AdRequest.Builder().build()
        adView.loadAd(adRequest)
    }
}

ApiService.kt

package com.khm.jobstest

import retrofit2.Call
import retrofit2.http.GET

interface ApiService {
    @GET("jobs/") // 确认端点与API一致
    fun getJobs(): Call<List<JobModel>>
}

JobAdapter.kt

package com.khm.jobstest

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide

class JobAdapter(private val jobs: List<JobModel>) :
    RecyclerView.Adapter<JobAdapter.JobViewHolder>() {

    class JobViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        val jobImage: ImageView = view.findViewById(R.id.jobImage)
        val jobName: TextView = view.findViewById(R.id.jobName)
        val jobDetails: TextView = view.findViewById(R.id.jobDetails)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): JobViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.item_job, parent, false)
        return JobViewHolder(view)
    }

    override fun onBindViewHolder(holder: JobViewHolder, position: Int) {
        val job = jobs[position]
        holder.jobName.text = job.name
        holder.jobDetails.text = job.app_content // 修正字段名
        Glide.with(holder.itemView.context).load(job.app_image).into(holder.jobImage)
    }

    override fun getItemCount(): Int = jobs.size
}

JobModel.kt

package com.khm.jobstest

data class JobModel(
    val name: String,
    val app_content: String, // 修正拼写错误
    val app_image: String
)

MainActivity.kt

package com.khm.jobstest

import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.gms.ads.AdRequest
import com.google.android.gms.ads.AdView
import com.google.android.gms.ads.MobileAds
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response

class MainActivity : AppCompatActivity() {

    private lateinit var recyclerView: RecyclerView
    private lateinit var adView: AdView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // 初始化RecyclerView
        recyclerView = findViewById(R.id.recyclerView)
        recyclerView.layoutManager = LinearLayoutManager(this)

        // 初始化AdMob
        MobileAds.initialize(this) {}
        adView = findViewById(R.id.adView)
        adView.loadAd(AdRequest.Builder().build())

        // 检查网络连接
        if (!NetworkUtils.isOnline(this)) {
            Toast.makeText(this, "يجب أن تستخدم الإنترنت للتصفح، حاول لاحقًا.", Toast.LENGTH_LONG).show()
            return
        }

        // 使用单例Retrofit实例获取数据
        val apiService = RetrofitInstance.retrofit.create(ApiService::class.java)
        apiService.getJobs().enqueue(object : Callback<List<JobModel>> {
            override fun onResponse(call: Call<List<JobModel>>, response: Response<List<JobModel>>) {
                if (response.isSuccessful && response.body() != null) {
                    recyclerView.adapter = JobAdapter(response.body()!!)
                } else {
                    Toast.makeText(this@MainActivity, "البيانات غير صالحة", Toast.LENGTH_SHORT).show()
                    // 打印响应码和错误信息
                    println("API响应失败: 码=${response.code()}, 消息=${response.message()}")
                }
            }

            override fun onFailure(call: Call<List<JobModel>>, t: Throwable) {
                val errorMsg = "فشل تحميل البيانات: ${t.message}"
                Toast.makeText(this@MainActivity, errorMsg, Toast.LENGTH_SHORT).show()
                t.printStackTrace() // 打印完整错误堆栈
            }
        })
    }
}

NetworkUtils.kt

package com.khm.jobstest
import android.content.Context
import android.net.ConnectivityManager
import android.net.NetworkCapabilities

object NetworkUtils {
    fun isOnline(context: Context): Boolean {
        val connectivityManager =
            context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
        val network = connectivityManager.activeNetwork ?: return false
        val capabilities = connectivityManager.getNetworkCapabilities(network) ?: return false
        return capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) ||
                capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) ||
                capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN) ||
                capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)
    }
}

RetrofitInstance.kt

package com.khm.jobstest

import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

object RetrofitInstance {
    private const val BASE_URL = "https://trainermods.net/api/"

    val retrofit: Retrofit by lazy {
        Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    }

    fun getRetrofitInstance(): Retrofit {
        return retrofit
    }
}

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">

    <!-- 网络访问权限 -->
    <uses-permission android:name="android.permission.INTERNET"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.JobTest"
        android:networkSecurityConfig="@xml/network_security_config" <!-- 添加网络安全配置 -->
        tools:targetApi="31">

        <!-- AdMob应用ID -->
        <meta-data
            android:name="com.google.android.gms.ads.APPLICATION_ID"
            android:value="ca-app-pub-3940256099942544~3347511713"/>

        <!-- 主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>
        </activity>
    </application>
</manifest>

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:ads="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.google.android.gms.ads.AdView
        android:id="@+id/adView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        ads:adSize="BANNER"
        ads:adUnitId="ca-app-pub-3940256099942544/6300978111" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

item_job.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="8dp">

    <ImageView
        android:id="@+id/jobImage"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:scaleType="centerCrop" />

    <TextView
        android:id="@+id/jobName"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="اسم الوظيفة"
        android:textSize="18sp"
        android:textStyle="bold"
        android:paddingTop="8dp" />

    <TextView
        android:id="@+id/jobDetails"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="وصف الوظيفة"
        android:textSize="14sp"
        android:paddingTop="4dp" />
</LinearLayout>

内容的提问来源于stack exchange,提问作者devo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 07:05:54