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

Kotlin实现ContentProvider onCreate()报Context类型不匹配错误

问题描述

我正在尝试实现ContentProvider的onCreate()方法,始终无法找到解决以下报错的方案:

"Type mismatch: inferred type is Context? but Context was expected(类型不匹配:推断类型为可空Context?,但要求传入非空Context)"

现有代码实现

DatabaseHandler.kt

package com.example.rewardreacher

import android.annotation.SuppressLint
import android.content.ContentValues
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import android.widget.Toast


const val DATABASE_NAME = "GoalDB"
const val TABLE_NAME = "Goals"
const val COL_NAME = "name"
const val COL_FREQUENCY = "frequency"
const val COL_PERFORMED = "performed"
const val COL_ID = "id"

class DatabaseHandler(var context: Context) : SQLiteOpenHelper(context, DATABASE_NAME, null, 1) {


    override fun onCreate(db: SQLiteDatabase?) {
        // No DB - Then create
        val createTable = "CREATE TABLE $TABLE_NAME (" +
                "$COL_ID INTEGER PRIMARY KEY AUTOINCREMENT," +
                "$COL_NAME VARCHAR(256), " +
                "$COL_FREQUENCY INTEGER(1), " +
                "$COL_PERFORMED INTEGER(256))"

        db?.execSQL(createTable)
    }


    override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
        db?.execSQL("DROP TABLE IF EXISTS $DATABASE_NAME");
        onCreate(db);
    }

    fun insertData(goal : Goal) {
        val db = this.writableDatabase
        val cv = ContentValues()
        cv.put(COL_NAME, goal.name)
        cv.put(COL_FREQUENCY, goal.frequency)
        cv.put(COL_PERFORMED, goal.performed)

        val result = db.insert(TABLE_NAME, null, cv)

        if (result == (0).toLong())
            Toast.makeText(context, "Failed", Toast.LENGTH_LONG).show()
        else
            Toast.makeText(context, "Success", Toast.LENGTH_LONG).show()
    }

    @SuppressLint("Range")
    fun readData() : MutableList<Goal> {
        val list: MutableList<Goal> = ArrayList()

        val db = this.readableDatabase
        val query = "Select * from $TABLE_NAME"
        val result = db.rawQuery(query, null)
        if (result.moveToFirst()) {
            do {
                val goal = Goal()
                goal.id = result.getString(result.getColumnIndex(COL_ID)).toInt()
                goal.name = result.getString(result.getColumnIndex(COL_NAME))
                goal.frequency = result.getString(result.getColumnIndex(COL_FREQUENCY)).toInt()
                goal.performed = result.getString(result.getColumnIndex(COL_PERFORMED)).toInt()
                list.add(goal)
            } while (result.moveToNext())
        }

        result.close()
        db.close()
        return list
    }


}

MyContentProvider.kt

package com.example.rewardreacher.provider

import android.content.ContentProvider
import android.content.ContentValues
import android.content.UriMatcher
import android.database.Cursor
import android.net.Uri
import com.example.rewardreacher.DatabaseHandler

const val DATABASE_NAME = "GoalDB"
const val GOALS_TABLE = "Goals"
const val COL_NAME = "name"
const val COL_FREQUENCY = "frequency"
const val COL_PERFORMED = "performed"
const val COL_ID = "id"

class MyContentProvider : ContentProvider() {

    private var dbHandler: DatabaseHandler? = null


    override fun onCreate(): Boolean {
        dbHandler =  DatabaseHandler(context)
        return false
    }

    private val GOALS = 1
    private val GOALS_ID = 2

    private val sURIMatcher = UriMatcher(UriMatcher.NO_MATCH)

    init {
        sURIMatcher.addURI(AUTHORITY, GOALS_TABLE, GOALS)
        sURIMatcher.addURI(AUTHORITY, GOALS_TABLE + "/#",
            GOALS_ID)
    }

    companion object {
        val AUTHORITY = "com.example.rewardreacher.provider.MyContentProvider"
        val CONTENT_URI : Uri = Uri.parse("content://" + AUTHORITY + "/" +
                GOALS_TABLE)
    }

    override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
        TODO("Implement this to handle requests to delete one or more rows")
    }

    override fun getType(uri: Uri): String? {
        TODO(
            "Implement this to handle requests for the MIME type of the data" +
                    "at the given URI"
        )
    }

    override fun insert(uri: Uri, values: ContentValues?): Uri? {
        TODO("Implement this to handle requests to insert a new row.")
    }

    override fun query(
        uri: Uri, projection: Array<String>?, selection: String?,
        selectionArgs: Array<String>?, sortOrder: String?
    ): Cursor? {
        TODO("Implement this to handle query requests from clients.")
    }

    override fun update(
        uri: Uri, values: ContentValues?, selection: String?,
        selectionArgs: Array<String>?
    ): Int {
        TODO("Implement this to handle requests to update one or more rows.")
    }
}

我没有Android Studio与Kotlin开发经验,猜测这是个比较基础的问题,但始终无法自行修复。

报错原因

这是Kotlin空安全机制的基础校验问题:

  • ContentProvider自带的getContext()方法返回值是可空类型Context?,系统定义里这个字段有可能为null
  • 你写的DatabaseHandler构造函数明确要求传入非空的Context类型
  • 直接把可空类型传给要求非空的参数,就会触发你看到的类型不匹配报错
  • 另外你当前onCreate()方法返回false是错误的,ContentProvider初始化成功需要返回true,返回false会被系统判定为初始化失败。
修复方法

两种常用写法,选一种替换你原来的onCreate()实现即可:

  • 安全判空写法(新手推荐,不会触发空指针崩溃)
override fun onCreate(): Boolean {
    // 仅当context不为空时才执行初始化逻辑
    context?.let {
        dbHandler = DatabaseHandler(it)
    }
    // 初始化成功(dbHandler不为空)返回true
    return dbHandler != null
}
  • 非空断言写法(代码更简洁,正常场景无风险)
override fun onCreate(): Boolean {
    // 强制断言context不为空,ContentProvider调用onCreate时context必然已经初始化完成
    dbHandler = DatabaseHandler(context!!)
    return true
}

补充说明:!!是Kotlin的非空断言操作符,如果你断言的对象实际为null,会直接抛出空指针异常。但根据Android系统的ContentProvider生命周期,onCreate()被调用时Provider已经附着到Context上,context一定非空,所以这种写法在实际生产环境是安全的。

内容的提问来源于stack exchange,提问作者dev.AMS

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.03 10:01:28