主頁 > 移動端開發 > Retrofit+Okhttp實作注冊登錄+后端代碼超詳細步驟

Retrofit+Okhttp實作注冊登錄+后端代碼超詳細步驟

2021-01-29 14:32:48 移動端開發

前言

這幾天想學習網路相關的東西,那么就從最簡單的開始吧,但是在網上搜了一下,百度還是谷歌都沒有完整的教程,要不只有移動端代碼,要不只有后端代碼,不想使用別人的api,那就自己做一個,所以我在完成之后想把這些記錄下來,中間的坑遇到了不少,在這做一個簡簡單單的總結,從搭建環境開始一步一步來,代碼比較粗糙,不過基本功能沒問題,本文章用到的框架不會的可以提前學起來了,

環境介紹

移動端:

 1. AndroidStudio 4.1.1 
 2. 編程語言:kotlin
 3. 架構:mvp
 4. 框架庫:retrofit2+okhttp3(這里沒用rxjava是因為有kotlin)

后端:

1.JDK 8
2.IntelliJ IDEA 2020
3.Tomcat 8
4.MySQL 5.7.29
5.Navicat Premium 15
6.springboot+mybatis

搭建環境

安卓端的環境搭建不會的可以看我的其他文章,這里不再多說,
對于一個沒有接觸過后端的,首先是要學會搭建環境!這里主要是后端的環境搭建:可以參考這篇文章,寫的很詳細,絕對夠用:
手把手教你搭建開發環境之Java開發

知識儲備

既然我們用retrofit和okhttp,那么我們總得知道怎么使用吧,這里推薦個不錯的文章:

你應該知道的HTTP基礎知識

你真的會用Retrofit2嗎?Retrofit2完全教程

后端的不會的可以直接看b站的視頻:
我們本篇文章的主要目的不是學習后端

SpringBoot系列–SSM1: 注冊登錄

后端環境搭建好之后,基本上跟著視頻來一步一步的做出來就沒問題了,如果有問題可以去看原始碼對比一下,不過小關哥的第三個視頻跳的稍微有點快,對于后端沒碰過的我,是真的菜,這里我遇到了幾個坑,下面會給大家提出來避免入坑,最后也會放上完整代碼的鏈接!

Android端

1.添加依賴

implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.okhttp3:okhttp:4.9.0'
implementation 'com.squareup.okhttp3:logging-interceptor:4.9.0'

2.布局

activity_login.xml(部分資源查看原始碼)

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_horizontal"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin">

    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/username_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <EditText
            android:id="@+id/username"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="@string/login_username"
            android:maxLines="1"/>

    </com.google.android.material.textfield.TextInputLayout>

    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/password_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/username_layout">

        <EditText
            android:id="@+id/password"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="@string/login_password"
            android:imeActionLabel="@string/action_sign_in_short"
            android:imeOptions="actionUnspecified"
            android:inputType="textPassword"
            android:maxLines="1"/>

    </com.google.android.material.textfield.TextInputLayout>

    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/email_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/password_layout">

        <EditText
            android:id="@+id/email"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="@string/prompt_email"
            android:inputType="textEmailAddress"
            android:maxLines="1"/>

    </com.google.android.material.textfield.TextInputLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/email_layout"
        android:layout_marginTop="16dp"
        android:orientation="horizontal">

        <Button
            android:id="@+id/register"
            style="?android:textAppearanceSmall"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="@string/login_register"
            android:textStyle="bold"/>

        <Button
            android:id="@+id/login"
            style="?android:textAppearanceSmall"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="@string/login_login"
            android:textStyle="bold"/>

    </LinearLayout>

</RelativeLayout>

activity_register.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="20dp">
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/login_username"
        android:textSize="16sp"
        android:textColor="#2b2b2b"/>

    <EditText
        android:id="@+id/input_name"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:maxLength="18"
        android:maxLines="1"
        android:hint="請輸入用戶名"
        android:textSize="14sp"
        android:inputType="text"/>

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="15dp"
        android:text="@string/login_password"
        android:textSize="16sp"
        android:textColor="#2b2b2b"/>

    <EditText
        android:id="@+id/input_pwd"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:inputType="text|textPassword"
        android:maxLength="18"
        android:hint="請輸入密碼"
        android:textSize="14sp"
        android:maxLines="1"/>

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="15dp"
        android:text="@string/confirm_pwd"
        android:textSize="16sp"
        android:textColor="#2b2b2b"/>

    <EditText
        android:id="@+id/input_confirm_pwd"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:inputType="text|textPassword"
        android:maxLength="18"
        android:hint="請再次輸入密碼"
        android:textSize="14sp"
        android:maxLines="1"/>

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="15dp"
        android:text="@string/email"
        android:textSize="16sp"
        android:textColor="#2b2b2b"/>

    <EditText
        android:id="@+id/input_email"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:inputType="text"
        android:maxLength="18"
        android:hint="請輸入郵箱"
        android:textSize="14sp"
        android:maxLines="1"/>

    <Button
        android:id="@+id/btn_register"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:textSize="18sp"
        android:text="@string/register"/>
</LinearLayout>

效果圖:
在這里插入圖片描述
在這里插入圖片描述

Entity

data class Accounts(val status: String,
                         val msg: String,
                         val result: User):Serializable

data class  User(val id: String, val nickname: String,val password:String,val username:String)

這里的物體我們得和后端同步,首先定義User類,用戶id,昵稱,用戶名和密碼
然后再建一個Accounts賬戶類,包括回應狀態碼,回應資訊,回應資料就是我們的User類,

API

class APIService {

    /**
     * 注冊
     */
    interface Register {
        @FormUrlEncoded
        @POST("register")
        fun toRegister(
            @Field("username") username: String,
            @Field("password") password: String
        ): Call<Accounts>
    }

    /**
     * 登錄
     */
    interface Login {
        @FormUrlEncoded
        @POST("login")
        fun toLogin(
            @Field("username") username: String,
            @Field("password") password: String
        ):Call<Accounts>
    }
}

這里我們采用post請求方式,回傳的資料就是我們定義的Accounts類,其他請求方式不再介紹,具體的使用方法見上面的推薦文章,剛開始不理解注解后面的引數含義,就想自己搭建一個后端來看看怎么鏈接的,我的 BaseUrl 是:

const val REQUEST_BASE_URL:String = "http://47.111.233.78:8080/api/user/"

這里我沒有使用localhost而是使用的ip地址,因為我的服務器在云端上面,(這里是使用的阿里云服務器,可以自己搭建一個云端服務器來使用,中間有些狗血的坑,比如我用mstsc來連接云端連接不上,得弄什么注冊表權限,還有就是搭建完之后,一定要在云端的網站配置安全組的常用埠8080,總之比較坎坷,挑選自己的服務器,如果只是測驗,可以選個一個月免費,或者是三個月的比較便宜的輕量型服務器,)
好了,我們看一下一個完整的請求連接是什么樣的:

http://localhost:8080/api/user/login?username=wyq&password=321

我的baseurl是http://localhost:8080/api/user/
而我們需要在api中進行一些操作,相當于login是一個請求登錄的操作,然后傳入你的引數用戶名和密碼

http://localhost:8080/api/user/register?username=wyq&password=321

這就是注冊請求操作,這里只是演示一下,以便于理解,post不能使用這種請求操作,get請求才能,
接著retrofit和okhttp會自動拼接這些引數并傳給后端,后端接收到這些引數會拆分,根據你的請求引數是login還是register來進行進一步的資料庫查詢還是插入操作,
retrofitManager.kt

class RetrofitManager {

    private val BASE_URL = "http://47.111.233.78:8080/api/user/"

    companion object{

        fun <T> getService(url:String,service: Class<T>):T{
            //根據你傳的service來進行不同的請求
            return createRetrofit(url).create(service)
        }

        private fun createRetrofit(url: String):Retrofit{
            val level: HttpLoggingInterceptor.Level = HttpLoggingInterceptor.Level.BODY
            val loggingInterceptor = HttpLoggingInterceptor(object :HttpLoggingInterceptor.Logger{
                override fun log(message: String) {
                    Log.i("kotlin", "OkHttp: $message")
                }
            })
            loggingInterceptor.level = level

            val okHttpClientBuilder = OkHttpClient.Builder()
            //連接超時
            okHttpClientBuilder.connectTimeout(30, TimeUnit.SECONDS)
            //讀取超時
            okHttpClientBuilder.readTimeout(10, TimeUnit.SECONDS)
            //添加攔截器
            okHttpClientBuilder.addInterceptor(loggingInterceptor)
            //創建retrofit物件
            return Retrofit.Builder()
                            .baseUrl(url)
                            .client(okHttpClientBuilder.build())
							//json資料轉換    
                        	.addConverterFactory(GsonConverterFactory.create())
                            .build()
        }
    }
}

LoginTask.kt

class LoginTask:LoginContract.Task {

    private var callBack: LoginContract.Presenter.OnLoginCallBack? = null


    override fun login(
        username: String?,
        password: String?,
        onLoginCallBack: LoginContract.Presenter.OnLoginCallBack
    ) {
        callBack = onLoginCallBack
        val mLogin = RetrofitManager.getService(Constant.REQUEST_BASE_URL, APIService.Login::class.java)
        if (username!!.isNotEmpty() && password!!.isNotEmpty()){

            val longCall = mLogin.toLogin(username, password)
            longCall.enqueue(object : Callback<Accounts> {

                override fun onFailure(call: Call<Accounts>, t: Throwable) {
                    callBack?.loginFail("登錄失敗")
                }

                override fun onResponse(call: Call<Accounts>, response: Response<Accounts>) {

                    var result: Accounts? = response.body()
                    if (result != null && "0" == result.status){
                        callBack?.loginSuccess()
                    }else{
                        callBack?.loginFail(result!!.msg)
                    }
                }
            })
        }
    }
}

registerTask.kt

class RegisterTask : RegisterContract.Task {
    private var callback: RegisterContract.Presenter.OnRegisterCallBack? = null

    override fun goRegister(
        name: String,
        pwd: String,
        onRegisterCallBack: RegisterContract.Presenter.OnRegisterCallBack
    ) {
        callback = onRegisterCallBack

        val registerService =
            RetrofitManager.getService(Constant.REQUEST_BASE_URL, APIService.Register::class.java)
        val registerCallBack = registerService.toRegister(name, pwd)
        registerCallBack.enqueue(object : Callback<Accounts> {

            override fun onFailure(call: Call<Accounts>, t: Throwable) {
                callback?.registerFail("注冊失敗")
            }

            override fun onResponse(call: Call<Accounts>, response: Response<Accounts>) {
                var result: Accounts? = response!!.body()
                if (result != null && "0" == result.status) {
                    callback?.registerSuccess(result)
                } else {
                    callback?.registerFail(result!!.msg)
                }
            }
        })
    }
}

后端:

這里就放主要代碼:

@Slf4j
@Api(tags = "用戶介面")
@AllArgsConstructor
@RequestMapping(Const.API + "user")
@RestController
public class UserController {

    private final UserService userService;
    private final UserMapper userMapper;


    @ApiOperation("登陸介面,回傳用戶資料")
    @PostMapping("login")
    public Result<User> login(
            @RequestParam String username,
            @RequestParam String password) {
        return userService.login(username, password);
    }


    @ApiOperation("注冊用戶介面")
    @PostMapping("register")
    public Result<User> register(@RequestParam String username,
                                 @RequestParam String password, String nickname) {
        return userService.addUser(username, password, nickname);
    }

看到postMapping里面是不是和android里面APIService很相像,是的,這就相當于一個約定,我們在android移動端和后端要約定好介面,才能進行資料請求和回應,

@Slf4j
@AllArgsConstructor
@Service
public class UserService {

    private final UserMapper userMapper;

    public Result<User> login(String username, String password) {
        User user = getUserByUsernameAndPassword(username, password);
        if (user == null) {
            return Result.createByErrorMessage("登陸失敗");
        }
        HttpKit.getRequest().getSession().setAttribute("user", user);
        return Result.createBySuccess(user);
    }
    private User getUserByUsernameAndPassword(String username, String password) {
        User record = new User();
        record.setUsername(username);
        record.setPassword(password);
        PageHelper.startPage(1, 1);
        List<User> list = userMapper.select(record);
        return list.size() == 0 ? null : list.get(0);
    }
    public Result<User> addUser(String username, String password, String nickname) {
        User record = new User();
        record.setUsername(username);
        List<User> list = userMapper.select(record);
        if (!CollectionUtils.isEmpty(list)) {
            return Result.createByErrorMessage("用戶已經存在,無法添加");
        }
        record.setPassword(password);
        record.setNickname(nickname);
        int resultCount = userMapper.insertSelective(record);
        return resultCount == 0 ? Result.createByErrorMessage("添加失敗") : Result.createBySuccess(record);
    }

}

這里的userMapper.select(record); userMapper.insertSelective(record);是一個查詢資料庫操作,我們只需要把需要查詢的物件,或者需要插入的物件傳進去就可以了,是不是很方便,當然這里面用到了大量的注解,這些注解就不詳細介紹了,想學的可以自行百度,
注意一點,這里面用到了swagger框架,這個框架是可以幫助我們很方便的進行網路請求測驗:
在這里插入圖片描述
我們只需要在這里填入資料,就可以看到請求的結果
在這里插入圖片描述
點擊try it out就可以看到下面的結果集

問題總結:

1.搭建好環境代碼也完整,本地可訪問,其他電腦不可訪問,
java.net.UnknownServiceException: CLEARTEXT communication ** not permitted by network security polic
首先android端要在AndroidManifest.xml中加上:android:usesCleartextTraffic="true",Google表示,為保證用戶資料和設備的安全,針對下一代 Android 系統(Android P) 的應用程式,將要求默認使用加密連接,這意味著 Android P 將禁止 App 使用所有未加密的連接,因此運行 Android P 系統的安卓設備無論是接識訓者發送流量,未來都不能明碼傳輸,需要使用下一代(Transport Layer Security)傳輸層安全協議,而 Android Nougat 和 Oreo 則不受影響,

<application
        android:name=".common.MyApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:usesCleartextTraffic="true"
        android:theme="@style/Theme.LoginDemo">

一步一步用騰訊云服務器搭建一個tomcat專案,并用外網通過ip訪問專案
2.后端在后期寫的時候,由于使用的框架,在navicat建一個資料庫后,運行代碼會自動建表,這里出現的問題是沒有自動建表,解決:
在這里插入圖片描述
原來我自己敲的代碼在profiles這一行前面時沒有縮進的,導致自動創建失敗,是因為spring的yml檔案有著嚴格的結構層次,如果層次結構不對那么就會出現意想不到的結果,比如這個,

登陸注冊安卓端完整代碼:
https://github.com/1QQ6/LoginDemo
登陸注冊后端完整代碼:
https://github.com/guangee/login-server-demo

這里我也把注冊登錄功能集成到我的另一個開源專案【音樂播放器】了,想要進一步學習的可以看下哦,
鏈接:https://github.com/1QQ6/TTMusicApp

如果這篇文章有幫助到你,希望能點個贊再走哦!

你們的支持是我走下去的動力!

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/254084.html

標籤:其他

上一篇:Android開發彈性布局FlexboxLayout知識總結

下一篇:Android App Standby講解

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more