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信任,需配置网络安全策略:
- 创建
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>
- 在
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
相关产品推荐
相关产品推荐

