Android - 從零開始的AWS Lambda
文章目錄
- Android - 從零開始的AWS Lambda
- 實現了啥
- STEP - 1 創建AWS IAM用戶並授予管理員權限
- 1. 進入[[AWS IAM console](https://console.aws.amazon.com/iam/) - 用戶 - 添加用戶]
- 2. 添加用戶
- 3. 創建組
- 4. (可選)輸入可跟蹤用戶信息的鍵值對
- 5. 返回[[AWS IAM console](https://console.aws.amazon.com/iam/) - 用戶]
- 6. 選擇[安全證書 - 創建訪問密鑰]
- STEP - 2 創建AWS IAM執行角色
- 1. 打開[[AWS IAM console](https://console.aws.amazon.com/iam/) - 用戶 - 角色]
- 2. 確保當前角色擁有**CloudWatch Logs**日誌寫入的**權限**
- 3. 角色名稱鍵入`lambda-android-role`
- STEP - 3 創建函數
- STEP - 4 創建Amazon Cognito 身份池
- STEP - 5 創建Android程序
和傳統的應用程序不同,用Lambda函數,結合其他微服務框架一起使用,可以把一個原本很複雜、冗餘的應用分解成一個個小的部分。每個框架之間只需要專注於做自己的事情,相互之間是松耦合的。這增加了開發程序的可能性、靈活性。
但是,要從頭自己開始學習還是有點點困難的。所以這次記錄了一下從頭開始學習Lambda函+Cognito與Android配合使用的過程。
“AWS Lambda 是一項計算服務,可使您無需預配置或管理服務器即可運行代碼。”
所以,我們只需要關注於代碼本身,由AWS Lambda 管理提供內存、CPU、網絡和其他資源均衡的計算機羣。
實現了啥
創建一個簡單的 Android 移動應用程序,通過在本地應用程序中輸入字符串並提交,從 Amazon Cognito 身份池檢索 AWS 憑證,並使用包含請求數據的事件,調用 Lambda 函數處理請求,記錄Cloud Watch日誌並向前端返回響應,返回字符串。
如果這些字連在一起就看不懂了:其實就是輸入兩個字符串,提交之後觸發了Lambda函數;觸發成功,返回toast,由Lambda函數記錄調用的日誌。
STEP - 1 創建AWS IAM用戶並授予管理員權限
AWS建議 不要使用 AWS 賬戶根用戶執行任務,而是應爲需要管理員訪問權限的每個人創建新的 IAM 用戶。
所以,擁有IAM用戶是訪問AWS服務的先決條件。
想要通過控制檯創建用戶,可以下載AWS CLI。
1. 進入[AWS IAM console - 用戶 - 添加用戶]
2. 添加用戶
a. 用戶名即之後用於訪問服務的用戶名
b. 因爲之後要通過CLI訪問,所以務必要選擇編程訪問。選擇AWS控制檯訪問的話,之後可以通過 IAM 控制檯控制面板中找到賬戶的登錄 URL進行登錄。
https://
account-ID-or-alias
.signin.aws.amazon.com/console
3. 創建組
篩選策略中,選擇AdministratorAccess
,即賦予組用戶管理員權限。
4. (可選)輸入可跟蹤用戶信息的鍵值對
本次實驗暫時用不到。接下來的步驟確認信息無誤後,擁有管理員權限的IAM用戶即可生成。
5. 返回[AWS IAM console - 用戶]
查看創建好的用戶,記錄用戶ARN,用於之後身份驗證服務。arn是AWS資源的唯一標識。
6. 選擇[安全證書 - 創建訪問密鑰]
把密鑰下載下來,之後登錄AWS CLI需要用。
STEP - 2 創建AWS IAM執行角色
要讓AWS Lambda對我們的資源進行訪問、操作,以實現具體功能,就要授予其相對應的權限。
IAM 角色就是信任的實體授予權限的安全方法。實體可以是其他賬戶的IAM用戶、AWS的服務、EC2上的程序代碼等。
1. 打開[AWS IAM console - 用戶 - 角色]
選擇創建角色,使用案例選擇Lambda。
2. 確保當前角色擁有CloudWatch Logs日誌寫入的權限
這裏選擇AWSLambdaBasicExecutionRole
。
3. 角色名稱鍵入lambda-android-role
這樣,擁有basic權限的角色就配置好了。
STEP - 3 創建函數
接下來的操作會用到shell,編寫一個簡單的觸發器,在本地打包創建成lambda函數並提交。
-
PS😋也可以選擇在AWS Lambda的網頁端中,通過現成的模板或創作設置想要的函數。 也支持在本地寫完Lambda函數,打包之後上傳。
在這裏,我選擇用WSL 2(Windows Subsystem for Linux 2) 進行腳本編寫創建,如果使用Linux/MacOS直接調用默認shell就可以了。
其實後來我看開發指南,Windows自帶的Power Shell也是可以的,被我弄複雜了;但是Power Shell長得醜,算了不虧。
方法有很多,只要是shell能編寫程序就OK。
1. 創建index.js文件
Lambda支持很多語言,如:javascript, java, python, c# … 這裏按照官方給的教程,我用的是node.js,很簡潔。
- 函數第一個參數event是來自調用程序(即我們的APP上的操作)信息對象。
- 第二個參數contexts是包含有關調用、函數和執行環境的對象,這裏將會獲取日誌流的名稱,然後將其返回到調用方。
- 第三個參數callback 是一個回調函數,可以用它對調用程序(APP)進行響應(response)。
這裏我們簡單定義了,調用程序中的函數,在console中記錄事件並返回給客戶端一個字符串的操作。
exports.handler = function(event, context, callback) {
console.log("Received event: ", event);
var data = {
"greetings": "Hello, " + event.firstName + " " + event.lastName + "."
};
callback(null, data);
}
2. 打包文件
把文件打包成壓縮包,並下載aws命令行。
zip function.zip index.js
sudo apt install awscli
如果是第一次使用AWS CLI,那麼就要設置用戶。
aws config
按照指令,輸入STEP - 1中下載好[用戶]-[安全證書]中的key,以及服務的地域等信息。
3. 使用 create-function
命令創建 Lambda 函數
- handler的名字之所以叫index.handler是因爲這是index.js文件中的handler函數。(好像是這樣的)
- lambda role arn可以直接在AWS CLI中查看,也可以在IAM role中查看。
- 這裏創建的函數名稱叫
AndroidBackendLambdaFunction
。
aws lambda create-function --function-name [lambda函數名] \ --zip-file
fileb://[目錄路徑]/function.zip --handler index.handler --runtime nodejs12.x \
--role [lambda role arn]
同時,可以在AWS Lambda網頁端查看到函數了。
STEP - 4 創建Amazon Cognito 身份池
Amazon Cognito 提供用戶池和身份池。
用戶池是爲應用程序提供註冊和登錄選項的用戶目錄,身份池提供 AWS 憑證以向用戶授予對其他 AWS 服務的訪問權限。
這裏用身份池,並授予其執行Lambda AndroidBackendLambdaFunction
函數的權限。
1. 打開 Amazon Cognito 控制檯
2. 新建身份池
身份池名爲 JavaFunctionAndroidEventHandlerPool
,並棄用未經驗證的訪問權限(即允許遊客登錄應用程序,觸發該函數)。
在未經身份驗證(unauthenticated identities)的角色中,輸入
{
"Version":"2012-10-17",
"Statement":[
{
"Effect":"Allow",
"Action":[
"mobileanalytics:PutEvents",
"cognito-sync:*"
],
"Resource":[
"*"
]
},
{
"Effect":"Allow",
"Action":[
"lambda:invokefunction"
],
"Resource":[
"arn:aws:lambda:us-east-1:[account-id]:function:AndroidBackendLambdaFunction"
]
}
]
}
Resource對應的就是AndroidBackendLambdaFunction的arn。[account-id]即IAM用戶的id。
創建成功後,系統會提示保存身份池id等信息,記錄下來,下一步將會用到.
STEP - 5 創建Android程序
做了這麼多,終於可以開始創建應用了,其實應用在這個demo裏面就顯得比較簡單。
值得注意的是,Lambda函數最好在API 28以上運行。在新建APP的時候,注意API版本。
如果在創建工程之後,同步(sync)失敗,(可能報app compact xxx29.+之類的錯),有以下幾種解決方法:
- 檢查project structure - Android SDK版本和build.gradle(module: app)中的sdk版本是否不一致.
- 檢查project structure -Update,是否版本過低. 建議升級較新的Android Studio版本. 我就是因爲版本比較舊,所以不能用29的包.
1. 創建一個Hello World工程
-
創建的工程名爲
AndroidEventGenerator
-
在 build.gradle (
Module:app
) 文件的dependencies
部分中添加以下內容
implementation 'com.amazonaws:aws-android-sdk-core:2.2.+'
implementation 'com.amazonaws:aws-android-sdk-lambda:2.2.+'
implementation "commons-logging:commons-logging:1.2"
- 在
AndroidManifest.xml
中,添加連接 Internet的權限
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.androideventgenerator">
...
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
</manifest>
2. 寫代碼
- 爲了Lambda函數能夠正確響應,創建和之前index.js中實現相同方法、參數的
RequestClass
和ResponseClass
package com.example.androideventgenerator;
public class RequestClass {
String firstName;
String lastName;
public RequestClass(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public RequestClass() {
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
package com.example.androideventgenerator;
public class ResponseClass {
String greetings;
public ResponseClass(String greetings) {
this.greetings = greetings;
}
public ResponseClass() {
}
public String getGreetings() {
return greetings;
}
public void setGreetings(String greetings) {
this.greetings = greetings;
}
}
-
MyInterface
用annotation
@LambdaFunction
對接口函數進行映射,注意這個映射的函數名應該一致,參數是request。
package com.example.androideventgenerator;
import com.amazonaws.mobileconnectors.lambdainvoker.LambdaFunction;
public interface MyInterface {
/**
* Invoke the Lambda function "AndroidBackendLambdaFunction".
* The function name is the method name.
*/
@LambdaFunction
ResponseClass AndroidBackendLambdaFunction(RequestClass request);
}
-
MainActivity
代碼要注意的地方我寫在註釋裏。。好長啊
package com.example.androideventgenerator;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import com.amazonaws.auth.CognitoCachingCredentialsProvider;
import com.amazonaws.mobileconnectors.lambdainvoker.LambdaFunctionException;
import com.amazonaws.mobileconnectors.lambdainvoker.LambdaInvokerFactory;
import com.amazonaws.regions.Regions;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
EditText editText1, editText2;
Button button;
RequestClass request;
MyInterface myInterface;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 創建提供Cognito服務的對象實例,這句在創建身份池的時候AWS已經提供了
CognitoCachingCredentialsProvider cognitoProvider = new CognitoCachingCredentialsProvider(
//把XXXXXXXXX改成創建的身份池id
this.getApplicationContext(), "us-east-1:XXXXXXXXX", Regions.US_EAST_1);
// 創建調用Lambda函數的工廠代理對象
//第三個參數是 a AWS credentials provider
LambdaInvokerFactory factory = new LambdaInvokerFactory(this.getApplicationContext(),
Regions.US_EAST_1, cognitoProvider);
//用工廠和默認的binder(第二個參數)創建一個Lambda對象實例
myInterface = factory.build(MyInterface.class);
editText1 = findViewById(R.id.editText);
editText2 = findViewById(R.id.editText2);
button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String firstName = editText1.getText().toString();
String LastName = editText2.getText().toString();
request = new RequestClass(firstName, LastName);
// The Lambda function invocation results in a network call.
// Make sure it is not called from the main thread.
//執行一個異步的請求
new myAsyncTask().execute(request);
}
});
}
//爲了防止內存泄漏,不要把AsyncTask放在主線程中
class myAsyncTask extends AsyncTask<RequestClass, Void, ResponseClass> {
@Override
protected ResponseClass doInBackground(RequestClass... params) {
// invoke "echo" method. In case it fails, it will throw a
// LambdaFunctionException.
try {
//******調用Lambda函數,返回request********//
return myInterface.AndroidBackendLambdaFunction(params[0]);
} catch (LambdaFunctionException lfe) {
Log.e("Tag", "Failed to invoke echo", lfe);
return null;
}
}
@Override
protected void onPostExecute(ResponseClass result) {
if (result == null) {
return;
}
/* 執行完onBackgoundTask方法後,執行onPostExecute方法 !
** 如果onBackgoundTask return回來的結果不爲空,即Lambda函數調用成功的話!
** 就do a toast !
*/
Toast.makeText(MainActivity.this, result.getGreetings(), Toast.LENGTH_LONG).show();
}
}
}
3. 結果
雖然我的佈局有點醜,但是可以看出,點擊SUBMIT之後,輸入的字符串在應用內被響應了。
同時,在AWS CloudWatch或AWS Lambda監控的CloudWatch中,都可以查看到Lambda函數記錄的日誌內容。
補充:調用函數的收費時間等信息也是可以看到的。
可以看出,我們只寫了Lambda功能代碼,沒有部署過物理資源,而實際調用物理資源的時間也微乎其微。