1. 程式人生 > >教你快速高效接入SDK——SDK接入抽象層的設計

教你快速高效接入SDK——SDK接入抽象層的設計

題記:很多做遊戲開發的人,估計都或多或少地接過渠道SDK,什麼UC,當樂,91,小米,360......據統計國內市場當前不下於100家渠道,還包括一些沒有SDK的小渠道。每個渠道SDK接入的方法呢,多是大同小異。但是,正是這些小異,又讓SDK的接入,產生了無窮無盡的變數。所以,接入SDK之前,如果你沒有經驗,或者沒有被SDK坑過,那麼當你看到這系列文章的時候,你很幸運,你可以避免這一切了。如果你之前被坑過,而且還在繼續被坑著,那麼現在,就是你解脫的時刻。

上一篇文章,我們總體地分析並設計了一套高效的SDK接入方案,也羅列出這套方案,我們需要完成的工作。這裡再羅列並回顧下:

1、統一抽象的SDK接入框架

2、各個SDK接入實現

3、一鍵打包工具

4、統一的登陸認證中心和支付中心

5、對多個平臺的支援,比如Unity3D,Cocos2D等

那麼接下來這篇文章,我們就開始第一部分:抽象的SDK接入框架的實現。在實現之前,我們再深入地想一下,抽象層需要提供哪些介面。因為,對於每個遊戲來說,都只需要接入這個抽象層,而所有SDK的接入就是實現這個抽象層。所以,這個抽象層設計的好壞,不僅影響到遊戲的接入,同時也影響到各個渠道SDK的實現。

沒有好的思路,我們可以看下AnySDK,或者稜鏡SDK他們的宣傳資料和文件,我們發現他們支援的元件有這些:


渠道SDK就不用說了,除了渠道SDK,他把部分支付SDK,廣告SDK,分享SDK,統計SDK,訊息推送SDK等都放到了這套統一SDK接入框架中來了。那麼,作為我們這套抽象框架,我們也需要考慮以後可能會加入這些其他非渠道的SDK。所以,我們總體的設計思想是:

1、遊戲各個渠道有一個主渠道SDK,比如UC,當樂,91等SDK。這個各個渠道只能同時有一個。不可能同時為UC也是91SDK

2、非渠道的功能性SDK,包括廣告,分享,統計,推送等。這些東西,我們作為外掛整合到這套抽象框架來。

3、所有SDK的實現可以很方便,而且結構比較統一

4、所有的渠道SDK也好,還是功能性SDK也好,SDK抽象層都抽象出對應的介面。方便遊戲層的呼叫,也方便具體外掛的實現。

那麼,接下來,我們就根據前一篇我們畫的那個登陸和支付流程圖,和上面提到的總體設計思路來實現這個抽象層。上篇文章說道,我們這套東西暫且命名為u8 sdk,那麼我們這個抽象層就叫U8 SDK。為了可以將各個功能作為外掛式開發,我們抽象介面的時候,也將各個功能分開。

首先,我們定義兩個介面,一個是登陸介面,一個是支付介面:

<span style="font-family:KaiTi_GB2312;font-size:18px;color:#333333;">package com.u8.sdk;


public interface IUser {

	public void login();
	
}</span>
<span style="font-family:KaiTi_GB2312;font-size:18px;color:#333333;">package com.u8.sdk;

public interface IPay {

	public void pay(PayParams data);
}
</span>
這兩個介面定義好了,我們簡單說下。一般SDK的登陸成功之後,都會拿到sid對吧,那這個登陸login方法並沒有返回值,而是後面,我們會定義一個總的SDK監聽器,通過監聽器來實現。支付需要遊戲層傳入一些支付引數。但是,有童鞋可能要問了,各個渠道需要的支付引數好像不太一樣吧?是的親,但是對於遊戲來說,我能提供的支付資料也就那麼些。我全傳給你,各個渠道sdk各自按需索取就可以了。那麼,我們簡單看下,我們這個PayParams類,有哪些屬性呢?
<span style="font-family:KaiTi_GB2312;font-size:18px;color:#333333;">package com.u8.sdk;

public class PayParams{
	
	private String productId;
	private String productName;
	private int price;
	private int buyNum;
	private int coinNum;
	private String serverId;
	private String roleId;
	private String roleName;
	private int roleLevel;
	private String extension;	
	
	public String getProductId() {
		return productId;
	}
	public void setProductId(String productId) {
		this.productId = productId;
	}
	public String getProductName() {
		return productName;
	}
	public void setProductName(String productName) {
		this.productName = productName;
	}
	public int getPrice() {
		return price;
	}
	public void setPrice(int price) {
		this.price = price;
	}
	public int getBuyNum() {
		return buyNum;
	}
	public void setBuyNum(int buyNum) {
		this.buyNum = buyNum;
	}
	public int getCoinNum() {
		return coinNum;
	}
	public void setCoinNum(int coinNum) {
		this.coinNum = coinNum;
	}
	public String getServerId() {
		return serverId;
	}
	public void setServerId(String serverId) {
		this.serverId = serverId;
	}
	public String getRoleId() {
		return roleId;
	}
	public void setRoleId(String roleId) {
		this.roleId = roleId;
	}
	public String getRoleName() {
		return roleName;
	}
	public void setRoleName(String roleName) {
		this.roleName = roleName;
	}
	public int getRoleLevel() {
		return roleLevel;
	}
	public void setRoleLevel(int roleLevel) {
		this.roleLevel = roleLevel;
	}
	public String getExtension() {
		return extension;
	}
	public void setExtension(String extension) {
		this.extension = extension;
	}

	
}
</span>

大家要問這裡引數怎麼抽象出來的,那麼我告訴你,不是我拍腦袋想出來的,是參考AnySDK文件中提供的充值引數資訊來的。(小賤一把)

兩個介面有了,緊接著,上層遊戲需要登入和支付的地方,怎麼呼叫呢?對於遊戲來說,這個介面需要new一個哪個實現?是UC還是當樂還是91呢?

所以,我們對每個外掛定義一個單例的包裝類。簡單地說,就是怎麼方便,怎麼搞。這樣就是方便上層遊戲層得呼叫。那麼,我們實現兩個包裝類:

<span style="font-family:KaiTi_GB2312;font-size:18px;color:#333333;">package com.u8.sdk.components;

import com.u8.sdk.IUser;
import com.u8.sdk.ComponentFactory;
import com.u8.sdk.U8SDK;

/**
 * 使用者外掛
 *
 */
public class U8User{
	private static U8User instance;
	
	private IUser userComponent;
	
	private U8User(){
		
	}
	
	public void init(){
		this.userComponent = (IUser)ComponentFactory.getInstance().initComponent(U8SDK.TYPE_LOGIN);
	}
	
	public static U8User getInstance(){
		if(instance == null){
			instance = new U8User();
		}
		
		return instance;
	}
	
	public void login(){
		if(userComponent==null){
			return;
		}
		
		userComponent.login();
	}
}
</span>
<span style="font-family:KaiTi_GB2312;font-size:18px;color:#333333;">package com.u8.sdk.components;


import com.u8.sdk.IPay;
import com.u8.sdk.PayParams;
import com.u8.sdk.ComponentFactory;
import com.u8.sdk.U8SDK;


/***
 * 支付外掛
 *
 */
public class U8Pay{
	
	private static U8Pay instance;
	
	private IPay payComponent;
	
	private U8Pay(){
		
	}
	
	public static U8Pay getInstance(){
		if(instance == null){
			instance = new U8Pay();
		}
		return instance;
	}
	
	public void init(){
		this.payComponent = (IPay)ComponentFactory.getInstance().initComponent(U8SDK.TYPE_PAY);
	}
	
	public void pay(PayParams data){
		if(this.payComponent == null){
			return;
		}
		this.payComponent.pay(data);
	}
}
</span>

關於這兩個包裝類,大家可以看到,有一個初始化init方法,然後就是所有的外掛對應的方法,他把外掛介面作為一個私有屬性,在init方法裡面,將對應的外掛介面賦值了。然後在外掛對應的方法裡面,間接地呼叫外掛對應的介面。那麼,這裡的關鍵就是ComponentFactory這個類。剛剛說了,我們這個後面可能有多個外掛,所以,我們需要一個外掛管理類。ComponentFactory就是我們的外掛管理類:
<span style="font-family:KaiTi_GB2312;font-size:18px;color:#333333;">package com.u8.sdk;

import java.io.IOException;
import java.io.StringReader;
import java.util.HashMap;
import java.util.Map;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import com.u8.sdk.utils.SDKTools;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.util.Log;
import android.util.Xml;

@SuppressLint("UseSparseArrays")
public class ComponentFactory {

	private static ComponentFactory instance;
	
	private Map<Integer, String> supportedComponents;
	
	private ComponentFactory(){
		supportedComponents = new HashMap<Integer, String>();
	}
	
	public static ComponentFactory getInstance(){
		if(instance == null){
			instance = new ComponentFactory();
		}
		
		return instance;
	}	
	
	public void init(Activity context){
		loadComponentInfo();
		
	}

	private boolean isSupportComponent(int type){
		
		return supportedComponents.containsKey(type);
	}
	
	private String getComponentName(int type){
		if(supportedComponents.containsKey(type)){
			return supportedComponents.get(type);
		}
		return null;
	}
	
	public SDKConfigData getSDKConfigData(){
		Map<String, String> configs = SDKTools.getAssetPropConfig(U8SDK.getInstance().getContext(), "developer_config.properties");
		return new SDKConfigData(configs);		
	}
	
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public Object initComponent(int type){
		Class localClass = null;
		
		try {
			
			if(!isSupportComponent(type)){
				
				Log.e("U8SDK", "The config of the U8SDK is not support plugin type:"+type);
				return null;
			}
			
			String name = getComponentName(type);
			
			localClass = Class.forName(name);
			
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return null;
		}
		
		try {
			return localClass.getDeclaredConstructor(new Class[]{Activity.class}).newInstance(new Object[]{U8SDK.getInstance().getContext()});
		} catch (Exception e) {

			e.printStackTrace();
		}
		return null;
	}
	
	
	private void loadComponentInfo(){
		String xmlStr = SDKTools.getAssetConfigs(U8SDK.getInstance().getContext(), "plugin_config.xml");
		
		Log.e("The plugin Str:", xmlStr);
		
		XmlPullParser parser = Xml.newPullParser();

		try {
			parser.setInput(new StringReader(xmlStr));
			
			int eventType = parser.getEventType();
			while(eventType != XmlPullParser.END_DOCUMENT){
				
				switch(eventType){
				case XmlPullParser.START_TAG:
					String tag = parser.getName();
					if("plugin".equals(tag)){
						String name = parser.getAttributeValue(0);
						int type = Integer.parseInt(parser.getAttributeValue(1));
						this.supportedComponents.put(type, name);
						Log.e("u8_plugin", "Curr Supported Plugin: "+type+"; name:"+name);
					}
				}
				eventType = parser.next();
			}
			
		} catch (XmlPullParserException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}
</span>

大家可以看到initComponent方法裡面,就是判斷當前外掛是否支援,如果支援,則從supportedComponents 裡面根據當前外掛型別取到對應外掛實現類的完整類名。通過Class.forName().newInstance()進行初始化。那麼,我們怎麼知道支援哪些外掛呢?怎麼得到當前支援的各個外掛的實現類呢?大家也許已經看到了,這個管理類中,也有一個初始化init方法。在init方法中,呼叫了loadComponentInfo方法來載入當前支援的外掛資訊。大家可以看到它是從assets目錄下的plugin_config.xml配置來讀取的。關於這個plugin_config.xml怎麼生成的,我們後面說打包工具的時候,會詳細講到。這個檔案不是我們手動寫的,而是打包工具在生成各個渠道包的時候動態生成的。

為了在SDK抽象層和SDK實現層傳遞資料,我們定義一個監聽器:

<span style="font-family:KaiTi_GB2312;font-size:18px;color:#333333;">package com.u8.sdk;

public interface IU8SDKListener {

	public void onResult(int code, String msg);
	
	public void onLoginResult(LoginResult result);
}
</span>

這個監聽器,我們定義了兩個介面,一個是onResult,是SDK實現層傳遞的狀態資訊,比如SDK初始化成功,SDK初始化失敗,SDK登陸成功,登陸失敗等資訊。而onLoginResult()這個介面,就是之前說到的,登陸成功之後,SDK實現層需要呼叫該介面,將封裝好的登陸結果,返回給SDK抽象層。這個LoginResult裡面,就包含sid資訊。

為了能夠將我們剛剛說的這些外掛,外掛包裝類,外掛管理類,和事件監聽介面整合到一起,我們最後定義一個總的單例類,也是我們整個抽象層的核心紐帶:

<span style="font-family:KaiTi_GB2312;font-size:18px;color:#333333;">package com.u8.sdk;

import com.u8.sdk.components.U8Pay;
import com.u8.sdk.components.U8User;

import android.app.Activity;
import android.content.Intent;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;

public class U8SDK{

	public static final int TYPE_LOGIN = 1;
	public static final int TYPE_PAY = 2;
	
	private static U8SDK instance;
	
	private Activity context;
	private Handler mainThreadHandler;	
	
	private SDKConfigData developInfo;
	
	private IU8SDKListener listener;
	private IActivityListener activityCallback;
	
	private U8SDK(){
		mainThreadHandler = new Handler(Looper.getMainLooper());
	}
	
	public static U8SDK getInstance(){
		if(instance == null){
			instance = new U8SDK();
		}
		return instance;
	}
	
	public SDKConfigData getSDKParams(){
		return developInfo;
	}
	
	public int getCurrChannel(){
		if(this.developInfo == null || !this.developInfo.contains("U8_Channel")){
			return 0;
		}
		
		return this.developInfo.getInt("U8_Channel");
	}
	
	public void setSDKListener(IU8SDKListener listener){
		this.listener = listener;
	}
	
	public void setActivityCallback(IActivityListener callback){
		this.activityCallback = callback;
	}
	
	public void init(Activity context){
		this.context = context;
		ComponentFactory.getInstance().init(context);
		developInfo = ComponentFactory.getInstance().getSDKConfigData();	
		
		U8User.getInstance().init();
		U8Pay.getInstance().init();
	}
	
	public void runOnMainThread(Runnable runnable){
		if(mainThreadHandler != null){
			mainThreadHandler.post(runnable);
			return;
		}
		
		if(context != null){
			context.runOnUiThread(runnable);
		}
	}	
	
	public Activity getContext(){
		return this.context;
	}
	
	public void onResult(int code, String msg){
		Log.e("U8SDK Action Result:", "code:"+code+";msg:"+msg);
		if(listener != null){
			listener.onResult(code, msg);
		}
	}
	
	public void onLoginResult(LoginResult result){
		if(listener != null){
			listener.onLoginResult(result);
		}
	}

	public void onActivityResult(int requestCode, int resultCode, Intent data) {
		if(this.activityCallback != null){
			this.activityCallback.onActivityResult(requestCode, resultCode, data);
		}
	}
	
	public void onBackPressed(){
		if(this.activityCallback != null){
			this.activityCallback.onBackPressed();
		}
	}


	public void onPause() {
		if(this.activityCallback != null){
			this.activityCallback.onPause();
		}
	}


	public void onResume() {
		
		if(this.activityCallback != null){
			this.activityCallback.onResume();
		}
		
	}


	public void onNewIntent(Intent newIntent) {
		if(this.activityCallback != null){
			this.activityCallback.onNewIntent(newIntent);
		}
		
	}


	public void onStop() {
		if(this.activityCallback != null){
			this.activityCallback.onStop();
		}
		
	}


	public void onDestroy() {
		if(this.activityCallback != null){
			this.activityCallback.onDestroy();
		}
		
	}


	public void onRestart() {
		if(this.activityCallback != null){
			this.activityCallback.onRestart();
		}
		
	}
	
	
}
</span>

大家可以看到,我們這裡,通過U8SDK這個單例,將所有的東西進行了連線和整合。在init方法裡面,我們init外掛管理類,也init所有的外掛包裝類。然後,對事件監聽器也進行一個簡單的包裝。使得SDK實現層的呼叫簡單方便,遊戲層的呼叫也簡單方便。

最後,因為有的SDK,需要在Activity的系統事件中做一些處理操作,而Activity是在遊戲接入我們這個抽象層時,傳遞進來的,所以,我們在抽象層定義了一個Activity事件監聽器:

<span style="font-family:KaiTi_GB2312;font-size:18px;color:#333333;">package com.u8.sdk;

import android.content.Intent;

public interface IActivityListener {

	public void onPause();
	public void onResume();
	public void onRestart();
	public void onBackPressed();	
	public void onNewIntent(Intent newIntent);
	public void onStop();
	public void onDestroy();
	public void onActivityResult(int requestCode, int resultCode, Intent data);
}
</span>

這樣,整個SDK接入的抽象層就差不多了。還有一些細節,我們可以後面邊開發,邊迭代,邊完善。後面我們將用例項來看看,遊戲層怎麼呼叫這個抽象層SDK,以及具體的SDK接入怎麼來實現這個抽象層。

本系列課程視訊教程已經錄製完畢,但是收費的。想要看視訊的童鞋請訪問:

本文作者:小黑

相關推薦

快速高效接入SDK——SDK接入抽象設計

題記:很多做遊戲開發的人,估計都或多或少地接過渠道SDK,什麼UC,當樂,91,小米,360......據統計國內市場當前不下於100家渠道,還包括一些沒有SDK的小渠道。每個渠道SDK接入的方法呢,多是大同小異。但是,正是這些小異,又讓SDK的接入,產生了無窮無盡的

快速高效接入SDK——總體思路和架構

題記:很多做遊戲開發的人,估計都或多或少地接過渠道SDK,什麼UC,當樂,91,小米,360......據統計國內市場當前不下於100家渠道,還包括一些沒有SDK的小渠道。每個渠道SDK接入的方法呢,多是大同小異。但是,正是這些小異,又讓SDK的接入,產生了無窮無盡的變數。所以,接入SDK之前,如果你沒有經驗

快速高效接入SDK——打包工具的實現(反編譯資源動態整合打渠道包)

1、打包工具的輸入,就是需要打包的apk包,也叫母包。遊戲裡面引入sdk抽象層的jar包,呼叫抽象層的介面。完成接入,然後打成apk。 2、打包工具會首先用apktool -d 對母包進行反編譯。反編譯到該渠道對應的臨時工作目錄中 3、重新命名包名。我們知道在AndroidManifet.xml中的pack

高速高效接入SDK——Unity統一接入渠道SDK(Android篇)

方法名 nco 簡單 sdk ui主線程 unit onpause 提示 schema U8SDK的設計之初,就是為了可以支持各種遊戲引擎開發的遊戲,而不不過Android的原生平臺。眼下一大半的手遊,都是採用Unity3D和Cocos2dx開發,那麽這裏,我們就先來一

國內實時通訊SDK測評,快速選用實時通訊服務

實時通訊(簡稱為RTC)是指能夠即時傳送和接收文字、音訊和視訊等的業務。近幾年的迅速發展,實時通訊的功能日益豐富,逐漸包括即時訊息、檔案共享、語音呼叫、直播互動、視訊會議等多種功能,需求涉及到多種不同行業包括媒體娛樂、電信、銀行、金融服務和保險(BFSI)、公共部門和教育,零

如何快速通過一個cmd命令啟動Oracle的兩個相關服務

安裝目錄 startup ice 11.2 start 服務 文件 我們 font 你安裝好了Oracle數據庫之後。 它都會默認開機自啟服務。 而我們為了節省電腦資源就把它給調為手動。 我們調為手動之後以後要用到Oracle數據庫就必須再去服務裏面一個一個去啟動。 這樣是

ZBrush快速搭建木橋技巧與方法介紹

ZBrush®作為一款優秀的3D圖形繪制軟件,幾乎可以雕刻任何你能想象到的人和物,只要你想得到,沒有它做不到的。木橋或者說腳手架在日常生活中扮演著很重要的角色,甚至在外面到處都可以看到它們,今天我們就來使用ZBrush雕一雕木橋吧。 這裏要運用ZBrush的Mirror and We

CAD轉換為JPG高清大圖?一鍵快速簡單操作

點擊 如何快速 轉換 迅捷 解決 樣式 現在 質量 下載 在這個快速化的時代,一切都講究速度,那我們如何快速的解決問題呢?很簡單的例子,現在幾乎都用得到CAD相關轉換的問題,那如何快速的將CAD圖紙文件轉換為JPG圖片樣式呢?不需要下載任何軟件,下面將進行演示方法。步驟一:

一分鐘快速建立起MySQL/Mariadb 主從狀態檢測腳本(shell)

watermark 簡單 -h fff adb dev 教你 spin sla 腳本主要實現了網絡檢測和簡單的主從狀態檢測,發現狀態異常即發送郵件報警,在手機上安裝一個易信可實現實時聯動,及時獲取服務器狀態信息,腳本雖簡單卻實用。 #!/bin/bash ##author:

錄屏軟體哪個好?大神快速錄製高清視訊

  現在錄遊戲視訊的自媒體很多,像之前大火的蛋蛋解說還有B站上熱門的吃雞遊戲UP主大忽悠等等~這些自媒體的視訊素材就是來自於錄製的遊戲視訊~其實錄制高清的視訊,用對工具就行了,不光是可以錄製電腦上的遊戲視訊,手機上也可以的。大家可以試試下面說的這款螢幕錄影工具,裡面除了有常規的錄製功能外,還有挺多好用的快

GsonFormat為快速高效生成實體類

  一、Android Studio快速新增Gson 具體操作:        1、File->Project Structure:  2、app->Dependencies->"

使用 Python 10分鐘 快速搭建一個部落格

10個優秀的程式設計師裡,有9個人都有寫部落格的習慣。這是非常好的習慣,它使得知識得以提煉,轉輸出為輸入,在提升自己的同時,還能利用網際網路易傳播的特性,將知識分享給每一個熱愛學習的人。               &n

神奇回車鍵快速錄入Excel表格資料,開啟高效率工作模式!

作為一名辦公職員,我們經常會用到excel。當有大量資料需要錄入Excel表格時,我們會發現既需要雙手操作鍵盤,又需要一隻手控制滑鼠點選單元格,導致工作效率真是出奇的低。所以,今天給大家分享一下關於Excel快速錄入資料的技巧。 方法一: 開啟"檔案",點選"選項"—"高階",更改"按

《從零構建前後分離的web專案》:前端完善 - 手把手快速構建網站佈局

添磚加瓦 - 手把手教你快速構建網站佈局 專案地址 本章原始碼地址 文章地址 本文為方便講述重構去除了 Element、vux 庫,用了最近比較火的 bulma 輕量、快捷、易讀。 專案截圖 Layout and Components Layout

快速部署一個區塊鏈網路

本文基於華為雲區塊鏈服務,介紹如何部署一個區塊鏈網路。 準備工作 在開始使用華為雲區塊鏈服務之前,我們需要先完成相應的環境準備工作,依次為:建立叢集、繫結彈性IP、建立檔案儲存。 注意: 建立叢集的時候可一併完成繫結彈性IP。您需要給叢集中每個虛機分別繫結一個彈性IP。 建立叢集

快速學會 Python 函式基礎知識

一、函式基礎 簡單地說,一個函式就是一組Python語句的組合,它們可以在程式中執行一次或多次執行。Python中的函式在其他語言中也叫做過程或子例程,那麼這些被包裝起來的語句通過一個函式名稱來呼叫。 有了函式,我們可以在很大程度上減少複製及貼上程式碼的次數了(相信很多人在剛開始時都有這樣的

從0到1,快速掌握“KOL網紅營銷”(下)

早在幾年前,“KOL”還是一種新興的營銷方式。而如今,“KOL”已成為品牌主、廣告主爭相佈局海外市場的重要砝碼。 這篇文章,我將為大家講述,如何聯絡網紅以及制定合作的策略。 第一步:如何聯絡網紅 首先你的頭像,簡介,以及你平常更新的內容都需要注意,這些都是這些網紅評判的標準。

從0到1,快速掌握“KOL網紅營銷”(上)

隨著社交平臺的廣泛普及,KOL(網紅)身上隱藏的巨大流量潛力逐漸被髮掘,與明星相比更具親和力,產品的推廣更自然,號召力也絲毫不遜色,那麼如何通過社交媒體“網紅”打造爆款成為眾多賣家的一大痛點。 網紅營銷在亞馬遜賣家當中一直都是熱門引流渠道,許多賣家在決定做站外引流的時候,第一時間就會想到

EOS抵押了不能用?ET錢包快速贖回

CPU資源是通過抵押EOS代幣獲得的,可以由其他賬戶為你抵押,也可以自己抵押,當我們需要使用EOS代幣時,也可以將抵押的EOS進行贖回。 如果是由他人為你抵押,他可以選擇抵押的資源是否可以由你贖回,所獲得的EOS也歸於你;或者這部分資源只能由他贖回,所獲得的EOS也歸於他。 簡單來說

UI設計初學者必看,這款設計神器快速入門

網路時代,網頁和手機App已經深入到人們生活的方方面面。這也使得App介面設計越來越受青年求職者們的青睞,並紛紛投入這個行業。但是,作為UI設計初學者,究竟如何才能快速的入門?當今市場上,是否有那麼一款高效實用的設計工具,幫助他們快速入門,提高工作效率的同時,迅速提升專業技能