暑期课程,感觉作业量相当大,不过想到隔壁cloudwego还要手搓网关,心里就平衡了:-)

  1. 使用.addTextChangedListener无效

    这个问题到现在也没解决,这个代码就是不触发监听事件,搞了一下午也想不通,论坛里大家都是这么用的,但是我就是不行?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    newsTitleElement.addTextChangedListener {
    object : TextWatcher {
    override fun beforeTextChanged(
    s: CharSequence?, start: Int, count: Int, after: Int
    ) {
    // 这里不需要处理
    }

    override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
    // 这里不需要处理
    Log.i("abcdefg", "text change了")
    }

    override fun afterTextChanged(s: Editable?) {
    // 在文本更改后保存EditText中的文本到contentText
    newsTitle = s.toString()
    Log.i("abcdefg","标题赋值")
    }
    }
    }

    换成kotlin提供的api就可以…

    1
    2
    3
    newsContextElement.doAfterTextChanged {
    Log.i("abcdefg", "changee text!!!")
    }

    这说不通,因为本质上来说这个api无非就是将java里的写法封装了下…

  2. 在主线程使用数据库被系统拒绝

    本来我想用户一点击提交按钮就直接将实体往数据库存

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    private fun setUpOkButton(){
    val okButton: ImageButton = findViewById(R.id.post_news_ok_button)
    okButton.setOnClickListener {
    if (newsTitle.isEmpty() || newsAbstract.isEmpty() || newsContext.isEmpty()){
    Toast.makeText(this, "标题、摘要、正文不能为空", Toast.LENGTH_SHORT).show()
    return@setOnClickListener
    }
    val newsId: Long = db.newsBriefDao.insert(NewsBriefEntity(null,newsTitle,"","《史记》","火爆"))
    db.newsContentDao.insert(NewsContentEntity(newsId,"",newsAbstract,newsContext))
    Toast.makeText(this, "发布啦", Toast.LENGTH_SHORT).show()
    finish()
    }
    }

    但是这样是在Ui 线程里进行数据库操作,会把ui界面阻塞,所以被系统拒绝了

    1
    2
    java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.

    解决方案是启动kotlin协程

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    private fun setUpOkButton(){
    val okButton: ImageButton = findViewById(R.id.post_news_ok_button)
    okButton.setOnClickListener {
    if (newsTitle.isEmpty() || newsAbstract.isEmpty() || newsContext.isEmpty()){
    Toast.makeText(this, "标题、摘要、正文不能为空", Toast.LENGTH_SHORT).show()
    return@setOnClickListener
    }
    // 在子线程中执行数据库操作
    GlobalScope.launch(Dispatchers.IO) {

    val newsId = db.newsBriefDao.insert(NewsBriefEntity(null,newsTitle,"","",""))
    db.newsContentDao.insert(NewsContentEntity(newsId,"",newsAbstract,newsContext))

    // UI操作需要回到主线程
    withContext(Dispatchers.Main) {
    Toast.makeText(this@PostNewsActivity, "发布成功", Toast.LENGTH_SHORT).show()
    finish()
    }

    }
    }
    }

    协程我还没有弄明白,这部分代码是claude给我写的

  3. kotlin的用于解析Room注解的依赖声明方式与java不同

    这个问题也是相当炸裂

    java引入room注解的方式是

    1
    annotationProcessor "androidx.room:room-compiler:$room_version" 

    而kotlin是

    1
    kapt "androidx.room:room-compiler:$room_version" 

    最让我恼火的是如果使用了第一种写法,根本没有任何显式的机制来提醒我,所有的room注解都不会引起IDE的报错或提示,程序会正常通过编译,APK甚至可以在真机上运行直到首次尝试实例化数据库时,APK直接崩溃并提示不存在database_impl,也就是说room实际上没有对注解做任何事情,我想现在我明白为什么这么多人要选择用react_native来开发APP了

  4. 一个比较好的全局主题

    默认的主题button背景强制是紫色的,所以需要自己覆盖默认主题里的设置,同时把丑陋的导航栏也禁用了,状态栏也换成了更好看的透明式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <style name="Base.Theme.MyApplication" parent="Theme.Material3.DynamicColors.DayNight">
    <!-- Customize your light theme here. -->
    <!-- <item name="colorPrimary">@color/my_light_primary</item> -->
    <item name="windowActionBar">false</item>//无ActionBar
    <item name="windowNoTitle">true</item> //无标题
    <item name="android:buttonStyle">@style/AppButtonStyle</item>
    <item name="android:windowTranslucentStatus">true</item>
    <item name="android:fitsSystemWindows">true</item>
    </style>
  5. content provider提供的地址在其他activity中失效

    这个问题让我深刻体会到了什么是“维护的结束就是软件死刑的宣告”,仅仅在三年间,Android对于一个最简单的获取一张照片地址的鉴权方式来去变化了好几次,我在解决问题时遇到了包括但不限于以下报错:

    1
    WRITE_EXTERNAL_STORAGE is deprecated (and is not granted) when targeting Android 13+. If you need to write to shared storage, use the MediaStore.createWriteRequest intent

    这是因为Android从13+开始不再允许这种一次申请到处读取的鉴权方式,并且从10+开始,即使开发者声明了这个权限,app也不会得到任何鉴权

    1
    2
    Process: com.liuyaoli.myapplication, PID: 14712
    java.lang.RuntimeException: Unable to start activity ComponentInfo{com.liuyaoli.myapplication/com.liuyaoli.myapplication.PostNewsActivity}: java.lang.SecurityException: UID 10451 does not have permission to content://com.android.providers.media.documents [user 0]; you could obtain access using ACTION_OPEN_DOCUMENT or related APIs

    这里是我想用

    1
    this.grantUriPermission(targetPackageName, contentUri, permissionFlags)

    来为相关activity鉴权,但是提供照片选取器的content provider是被保护起来的,不允许这样鉴权

    1
    java.lang.SecurityException: Permission Denial: reading com.android.providers.media.MediaDocumentsProvider uri content://com.android.providers.media.documents/document/image%3A255293 from pid=10706, uid=10450 requires that you obtain access using ACTION_OPEN_DOCUMENT or related APIs

    这里是我开始用系统提供的照片选择器来避免去申请存取权限的问题

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
                headImgPickImage.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly))

    private val headImgPickImage =
    registerForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri ->
    if (uri != null) {
    // 在这里展示选择的图片,可以将 URI 传递给相应的 ImageView 或其他展示图片的组件
    uploadHeadImgButton.setImageURI(uri)
    // thumbnailUri = Uri.fromFile(File(getRealPathFromURI(this,uri)!!)).toString()
    headImgUri = uri.toString()
    Log.i("ttttt", headImgUri)
    }
    }

    但是即使是这样,即便申请获取图片的activity有了权限,同一app下的其他activity也无法获得权限

    接下来我又不知道从哪里弄了个鉴权方法,又换成报这个错

    1
    2
    Request threw uncaught throwable
    java.lang.SecurityException: Permission Denial: opening provider com.android.providers.media.MediaDocumentsProvider from ProcessRecord{1f9acfc 13239:com.liuyaoli.myapplication/u0a450} (pid=13239, uid=10450) requires that you obtain access using ACTION_OPEN_DOCUMENT or related APIs

    最后chatgpt告诉我

    “你应该使用 takePersistableUriPermission 来授予长期持久的权限。下面是使用 takePersistableUriPermission 的方法:”

    原来加上这几行获取持久权限就行了:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
     private val thumbnailsPickImage =
    registerForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri ->
    if (uri != null) {
    contentResolver.takePersistableUriPermission(
    uri,
    Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
    )
    // 在这里展示选择的图片,可以将 URI 传递给相应的 ImageView 或其他展示图片的组件
    uploadThumbnailsButton.setImageURI(uri)
    // thumbnailUri = Uri.fromFile(File(getRealPathFromURI(this,uri)!!)).toString()
    thumbnailUri = uri.toString()
    Log.i("ttttt", thumbnailUri)
    }
    }

    回过头来看,我debug的过程正好是Android发展的过程,从以前的程序申请一次权限就可以到处访问数据,到要用grantUriPermission来分content provider来鉴权,再到又套一层takePersistableUriPermission来要求申请持久权限,权限的管理越来越精细了