mirror of https://github.com/grpc/grpc.git
Merge pull request #14193 from ericgribkoff/android_cpp_helloworld
C++ on Android: Hello World client and serverpull/14583/merge
commit
f9244afe4d
25 changed files with 1027 additions and 0 deletions
@ -0,0 +1,9 @@ |
||||
*.iml |
||||
.gradle |
||||
/local.properties |
||||
/.idea/workspace.xml |
||||
/.idea/libraries |
||||
.DS_Store |
||||
/build |
||||
/captures |
||||
.externalNativeBuild |
@ -0,0 +1,24 @@ |
||||
gRPC on Android |
||||
============== |
||||
|
||||
Note: Building the protobuf dependency for Android requires |
||||
https://github.com/google/protobuf/pull/3878. This fix will be in the next |
||||
protobuf release, but until then must be manually patched in to |
||||
`third_party/protobuf` to build gRPC for Android. |
||||
|
||||
PREREQUISITES |
||||
------------- |
||||
|
||||
- Android SDK |
||||
- Android NDK |
||||
- `protoc` and `grpc_cpp_plugin` binaries on the host system |
||||
|
||||
INSTALL |
||||
------- |
||||
|
||||
The example application can be built via Android Studio or on the command line |
||||
using `gradle`: |
||||
|
||||
```sh |
||||
$ ./gradlew installDebug |
||||
``` |
@ -0,0 +1 @@ |
||||
/build |
@ -0,0 +1,123 @@ |
||||
cmake_minimum_required(VERSION 3.4.1) |
||||
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") |
||||
|
||||
set(helloworld_PROTOBUF_PROTOC_EXECUTABLE "/usr/local/bin/protoc" CACHE STRING "Protoc binary on host") |
||||
set(helloworld_GRPC_CPP_PLUGIN_EXECUTABLE "/usr/local/bin/grpc_cpp_plugin" CACHE STRING "gRPC CPP plugin binary on host") |
||||
|
||||
set(GRPC_SRC_DIR ../../../../) |
||||
|
||||
set(GRPC_BUILD_DIR ../grpc/outputs/${ANDROID_ABI}) |
||||
file(MAKE_DIRECTORY ${GRPC_BUILD_DIR}) |
||||
|
||||
add_subdirectory(${GRPC_SRC_DIR} ${GRPC_BUILD_DIR}) |
||||
|
||||
include_directories(${GRPC_SRC_DIR}/include) |
||||
|
||||
add_library(libgrpc STATIC IMPORTED) |
||||
set_target_properties(libgrpc PROPERTIES IMPORTED_LOCATION |
||||
${GRPC_BUILD_DIR}/libgrpc.a) |
||||
|
||||
add_library(libgrpc++ STATIC IMPORTED) |
||||
set_target_properties(libgrpc++ PROPERTIES IMPORTED_LOCATION |
||||
${GRPC_BUILD_DIR}/libgrpc++.a) |
||||
|
||||
add_library(libgpr STATIC IMPORTED) |
||||
set_target_properties(libgpr PROPERTIES IMPORTED_LOCATION |
||||
${GRPC_BUILD_DIR}/libgpr.a) |
||||
|
||||
add_library(libcares STATIC IMPORTED) |
||||
set_target_properties(libcares PROPERTIES IMPORTED_LOCATION |
||||
${GRPC_BUILD_DIR}/third_party/cares/cares/lib/libcares.a) |
||||
|
||||
add_library(libzlib STATIC IMPORTED) |
||||
set_target_properties(libzlib PROPERTIES IMPORTED_LOCATION |
||||
${GRPC_BUILD_DIR}/third_party/zlib/libz.a) |
||||
|
||||
add_library(libcrypto STATIC IMPORTED) |
||||
set_target_properties(libcrypto PROPERTIES IMPORTED_LOCATION |
||||
${GRPC_BUILD_DIR}/third_party/boringssl/crypto/libcrypto.a) |
||||
|
||||
add_library(libssl STATIC IMPORTED) |
||||
set_target_properties(libssl PROPERTIES IMPORTED_LOCATION |
||||
${GRPC_BUILD_DIR}/third_party/boringssl/ssl/libssl.a) |
||||
|
||||
set(GRPC_PROTO_GENS_DIR ${CMAKE_BINARY_DIR}/gens) |
||||
file(MAKE_DIRECTORY ${GRPC_PROTO_GENS_DIR}) |
||||
include_directories(${GRPC_PROTO_GENS_DIR}) |
||||
|
||||
function(android_protobuf_grpc_generate_cpp SRC_FILES HDR_FILES INCLUDE_ROOT) |
||||
if(NOT ARGN) |
||||
message(SEND_ERROR "Error: android_protobuf_grpc_generate_cpp() called without any proto files") |
||||
return() |
||||
endif() |
||||
|
||||
set(${SRC_FILES}) |
||||
set(${HDR_FILES}) |
||||
set(PROTOBUF_INCLUDE_PATH -I ${INCLUDE_ROOT}) |
||||
foreach(FIL ${ARGN}) |
||||
get_filename_component(ABS_FIL ${FIL} ABSOLUTE) |
||||
get_filename_component(FIL_WE ${FIL} NAME_WE) |
||||
file(RELATIVE_PATH REL_FIL ${CMAKE_CURRENT_SOURCE_DIR}/${INCLUDE_ROOT} ${ABS_FIL}) |
||||
get_filename_component(REL_DIR ${REL_FIL} DIRECTORY) |
||||
set(RELFIL_WE "${REL_DIR}/${FIL_WE}") |
||||
|
||||
list(APPEND ${SRC_FILES} "${GRPC_PROTO_GENS_DIR}/${RELFIL_WE}.pb.cc") |
||||
list(APPEND ${HDR_FILES} "${GRPC_PROTO_GENS_DIR}/${RELFIL_WE}.pb.h") |
||||
list(APPEND ${SRC_FILES} "${GRPC_PROTO_GENS_DIR}/${RELFIL_WE}.grpc.pb.cc") |
||||
list(APPEND ${HDR_FILES} "${GRPC_PROTO_GENS_DIR}/${RELFIL_WE}.grpc.pb.h") |
||||
|
||||
add_custom_command( |
||||
OUTPUT "${GRPC_PROTO_GENS_DIR}/${RELFIL_WE}.grpc.pb.cc" |
||||
"${GRPC_PROTO_GENS_DIR}/${RELFIL_WE}.grpc.pb.h" |
||||
"${GRPC_PROTO_GENS_DIR}/${RELFIL_WE}.pb.cc" |
||||
"${GRPC_PROTO_GENS_DIR}/${RELFIL_WE}.pb.h" |
||||
COMMAND ${helloworld_PROTOBUF_PROTOC_EXECUTABLE} |
||||
ARGS --grpc_out=${GRPC_PROTO_GENS_DIR} |
||||
--cpp_out=${GRPC_PROTO_GENS_DIR} |
||||
--plugin=protoc-gen-grpc=${helloworld_GRPC_CPP_PLUGIN_EXECUTABLE} |
||||
${PROTOBUF_INCLUDE_PATH} |
||||
${REL_FIL} |
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} |
||||
DEPENDS ${helloworld_PROTOBUF_PROTOC_EXECUTABLE} ${helloworld_GRPC_CPP_PLUGIN_EXECUTABLE} ${ABS_FIL} ) |
||||
endforeach() |
||||
|
||||
set_source_files_properties(${${SRC_FILES}} ${${HDR_FILES}} PROPERTIES GENERATED TRUE) |
||||
set(${SRC_FILES} ${${SRC_FILES}} PARENT_SCOPE) |
||||
set(${HDR_FILES} ${${HDR_FILES}} PARENT_SCOPE) |
||||
endfunction() |
||||
|
||||
set(PROTO_BASE_DIR ${GRPC_SRC_DIR}/examples/protos) |
||||
|
||||
android_protobuf_grpc_generate_cpp( |
||||
HELLOWORLD_PROTO_SRCS HELLOWORLD_PROTO_HDRS ${PROTO_BASE_DIR} ${PROTO_BASE_DIR}/helloworld.proto) |
||||
|
||||
add_library(helloworld_proto_lib |
||||
SHARED ${HELLOWORLD_PROTO_HDRS} ${HELLOWORLD_PROTO_SRCS}) |
||||
|
||||
target_link_libraries(helloworld_proto_lib |
||||
libprotobuf |
||||
libgrpc++ |
||||
android |
||||
log) |
||||
|
||||
find_library(log-lib |
||||
log) |
||||
|
||||
add_library(grpc-helloworld |
||||
SHARED src/main/cpp/grpc-helloworld.cc) |
||||
|
||||
target_include_directories(grpc-helloworld |
||||
PRIVATE ${HELLOWORLD_PROTO_HEADERS}) |
||||
|
||||
target_link_libraries(grpc-helloworld |
||||
libgrpc++ |
||||
libgrpc |
||||
libzlib |
||||
libcares |
||||
libssl |
||||
libcrypto |
||||
helloworld_proto_lib |
||||
libgpr |
||||
android |
||||
${log-lib}) |
@ -0,0 +1,53 @@ |
||||
apply plugin: 'com.android.application' |
||||
|
||||
android { |
||||
compileSdkVersion 26 |
||||
defaultConfig { |
||||
applicationId "io.grpc.android.cpp.helloworldexample" |
||||
minSdkVersion 14 |
||||
targetSdkVersion 26 |
||||
versionCode 1 |
||||
versionName "1.0" |
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" |
||||
externalNativeBuild { |
||||
cmake { |
||||
cppFlags "-std=c++14 -frtti -fexceptions" |
||||
arguments '-DANDROID_STL=c++_static' |
||||
arguments '-DRUN_HAVE_POSIX_REGEX=0' |
||||
arguments '-DRUN_HAVE_STD_REGEX=0' |
||||
arguments '-DRUN_HAVE_STEADY_CLOCK=0' |
||||
arguments '-Dprotobuf_BUILD_PROTOC_BINARIES=off' |
||||
arguments '-DgRPC_BUILD_CODEGEN=off' |
||||
// Set this to the path to the protoc binary on the host system (codegen is not |
||||
// cross-compiled to Android) |
||||
arguments '-Dhelloworld_PROTOBUF_PROTOC_EXECUTABLE=/usr/local/bin/protoc' |
||||
// Set this to the path to the gRPC C++ protoc plugin binary on the host system |
||||
// (codegen is not cross-compiled to Android) |
||||
arguments '-Dhelloworld_GRPC_CPP_PLUGIN_EXECUTABLE=/usr/local/bin/grpc_cpp_plugin' |
||||
} |
||||
} |
||||
ndk.abiFilters 'x86' |
||||
} |
||||
buildTypes { |
||||
debug { |
||||
minifyEnabled false |
||||
} |
||||
release { |
||||
minifyEnabled true |
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' |
||||
} |
||||
} |
||||
externalNativeBuild { |
||||
cmake { |
||||
path "CMakeLists.txt" |
||||
} |
||||
} |
||||
} |
||||
|
||||
dependencies { |
||||
implementation fileTree(dir: 'libs', include: ['*.jar']) |
||||
implementation 'com.android.support:appcompat-v7:26.1.0' |
||||
testImplementation 'junit:junit:4.12' |
||||
androidTestImplementation 'com.android.support.test:runner:1.0.1' |
||||
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' |
||||
} |
@ -0,0 +1,21 @@ |
||||
# Add project specific ProGuard rules here. |
||||
# You can control the set of applied configuration files using the |
||||
# proguardFiles setting in build.gradle. |
||||
# |
||||
# For more details, see |
||||
# http://developer.android.com/guide/developing/tools/proguard.html |
||||
|
||||
# If your project uses WebView with JS, uncomment the following |
||||
# and specify the fully qualified class name to the JavaScript interface |
||||
# class: |
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview { |
||||
# public *; |
||||
#} |
||||
|
||||
# Uncomment this to preserve the line number information for |
||||
# debugging stack traces. |
||||
#-keepattributes SourceFile,LineNumberTable |
||||
|
||||
# If you keep the line number information, uncomment this to |
||||
# hide the original source file name. |
||||
#-renamesourcefileattribute SourceFile |
@ -0,0 +1,22 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" |
||||
package="io.grpc.helloworldexample.cpp" > |
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" /> |
||||
|
||||
<application |
||||
android:allowBackup="false" |
||||
android:icon="@mipmap/ic_launcher" |
||||
android:label="@string/app_name" |
||||
android:theme="@style/Base.V7.Theme.AppCompat.Light" > |
||||
<activity |
||||
android:name=".HelloworldActivity" |
||||
android:label="@string/app_name" > |
||||
<intent-filter> |
||||
<action android:name="android.intent.action.MAIN" /> |
||||
<category android:name="android.intent.category.LAUNCHER" /> |
||||
</intent-filter> |
||||
</activity> |
||||
</application> |
||||
|
||||
</manifest> |
@ -0,0 +1,142 @@ |
||||
/*
|
||||
* |
||||
* Copyright 2018 gRPC authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
* |
||||
*/ |
||||
|
||||
#include <atomic> |
||||
|
||||
#include <grpc++/grpc++.h> |
||||
#include <jni.h> |
||||
|
||||
#include "helloworld.grpc.pb.h" |
||||
|
||||
using grpc::Channel; |
||||
using grpc::ClientContext; |
||||
using grpc::Server; |
||||
using grpc::ServerBuilder; |
||||
using grpc::ServerContext; |
||||
using grpc::Status; |
||||
using helloworld::Greeter; |
||||
using helloworld::HelloReply; |
||||
using helloworld::HelloRequest; |
||||
|
||||
std::atomic<bool> stop_server(false); |
||||
|
||||
// Logic and data behind the server's behavior.
|
||||
class GreeterServiceImpl final : public Greeter::Service { |
||||
Status SayHello(ServerContext* context, const HelloRequest* request, |
||||
HelloReply* reply) override { |
||||
std::string prefix("Hello "); |
||||
reply->set_message(prefix + request->name()); |
||||
return Status::OK; |
||||
} |
||||
}; |
||||
|
||||
void StartServer(JNIEnv* env, jobject obj, jmethodID is_cancelled_mid, |
||||
int port) { |
||||
const int host_port_buf_size = 1024; |
||||
char host_port[host_port_buf_size]; |
||||
snprintf(host_port, host_port_buf_size, "0.0.0.0:%d", port); |
||||
|
||||
GreeterServiceImpl service; |
||||
ServerBuilder builder; |
||||
// Listen on the given address without any authentication mechanism.
|
||||
builder.AddListeningPort(host_port, grpc::InsecureServerCredentials()); |
||||
// Register "service" as the instance through which we'll communicate with
|
||||
// clients. In this case it corresponds to an *synchronous* service.
|
||||
builder.RegisterService(&service); |
||||
// Finally assemble the server.
|
||||
std::unique_ptr<Server> server(builder.BuildAndStart()); |
||||
while (!stop_server.load()) { |
||||
// Check with the Java code to see if the user has requested the server stop or the app is no
|
||||
// longer in the foreground.
|
||||
jboolean is_cancelled = env->CallBooleanMethod(obj, is_cancelled_mid); |
||||
if (is_cancelled == JNI_TRUE) { |
||||
stop_server = true; |
||||
} |
||||
} |
||||
} |
||||
|
||||
class GreeterClient { |
||||
public: |
||||
GreeterClient(std::shared_ptr<Channel> channel) |
||||
: stub_(Greeter::NewStub(channel)) {} |
||||
|
||||
// Assembles the client's payload, sends it and presents the response back
|
||||
// from the server.
|
||||
std::string SayHello(const std::string& user) { |
||||
// Data we are sending to the server.
|
||||
HelloRequest request; |
||||
request.set_name(user); |
||||
|
||||
// Container for the data we expect from the server.
|
||||
HelloReply reply; |
||||
|
||||
// Context for the client. It could be used to convey extra information to
|
||||
// the server and/or tweak certain RPC behaviors.
|
||||
ClientContext context; |
||||
// The actual RPC.
|
||||
Status status = stub_->SayHello(&context, request, &reply); |
||||
|
||||
if (status.ok()) { |
||||
return reply.message(); |
||||
} else { |
||||
return status.error_message(); |
||||
} |
||||
} |
||||
|
||||
private: |
||||
std::unique_ptr<Greeter::Stub> stub_; |
||||
}; |
||||
|
||||
// Send an RPC and return the response. Invoked from Java code.
|
||||
extern "C" JNIEXPORT jstring JNICALL |
||||
Java_io_grpc_helloworldexample_cpp_HelloworldActivity_sayHello( |
||||
JNIEnv* env, jobject obj_unused, jstring host_raw, jint port_raw, |
||||
jstring message_raw) { |
||||
const char* host_chars = env->GetStringUTFChars(host_raw, (jboolean*)0); |
||||
std::string host(host_chars, env->GetStringUTFLength(host_raw)); |
||||
|
||||
int port = static_cast<int>(port_raw); |
||||
|
||||
const char* message_chars = env->GetStringUTFChars(message_raw, (jboolean*)0); |
||||
std::string message(message_chars, env->GetStringUTFLength(message_raw)); |
||||
|
||||
const int host_port_buf_size = 1024; |
||||
char host_port[host_port_buf_size]; |
||||
snprintf(host_port, host_port_buf_size, "%s:%d", host.c_str(), port); |
||||
|
||||
GreeterClient greeter( |
||||
grpc::CreateChannel(host_port, grpc::InsecureChannelCredentials())); |
||||
std::string reply = greeter.SayHello(message); |
||||
|
||||
return env->NewStringUTF(reply.c_str()); |
||||
} |
||||
|
||||
// Start the server. Invoked from Java code.
|
||||
extern "C" JNIEXPORT void JNICALL |
||||
Java_io_grpc_helloworldexample_cpp_HelloworldActivity_startServer( |
||||
JNIEnv* env, jobject obj_this, jint port_raw) { |
||||
int port = static_cast<int>(port_raw); |
||||
|
||||
jclass cls = env->GetObjectClass(obj_this); |
||||
jmethodID is_cancelled_mid = |
||||
env->GetMethodID(cls, "isRunServerTaskCancelled", "()Z"); |
||||
|
||||
stop_server = false; |
||||
|
||||
StartServer(env, obj_this, is_cancelled_mid, port); |
||||
} |
@ -0,0 +1,167 @@ |
||||
/* |
||||
* Copyright 2018, gRPC Authors All rights reserved. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package io.grpc.helloworldexample.cpp; |
||||
|
||||
import android.content.Context; |
||||
import android.os.AsyncTask; |
||||
import android.os.Bundle; |
||||
import android.support.v7.app.AppCompatActivity; |
||||
import android.text.TextUtils; |
||||
import android.text.method.ScrollingMovementMethod; |
||||
import android.view.View; |
||||
import android.view.inputmethod.InputMethodManager; |
||||
import android.widget.Button; |
||||
import android.widget.EditText; |
||||
import android.widget.TextView; |
||||
import android.widget.Toast; |
||||
import java.lang.ref.WeakReference; |
||||
|
||||
public class HelloworldActivity extends AppCompatActivity { |
||||
|
||||
static { |
||||
System.loadLibrary("grpc-helloworld"); |
||||
} |
||||
|
||||
private Button sendButton; |
||||
private Button serverButton; |
||||
private EditText hostEdit; |
||||
private EditText portEdit; |
||||
private EditText messageEdit; |
||||
private EditText serverPortEdit; |
||||
private TextView resultText; |
||||
private GrpcTask grpcTask; |
||||
private RunServerTask runServerTask; |
||||
|
||||
@Override |
||||
protected void onCreate(Bundle savedInstanceState) { |
||||
super.onCreate(savedInstanceState); |
||||
setContentView(R.layout.activity_helloworld); |
||||
sendButton = (Button) findViewById(R.id.send_button); |
||||
serverButton = (Button) findViewById(R.id.server_button); |
||||
hostEdit = (EditText) findViewById(R.id.host_edit_text); |
||||
portEdit = (EditText) findViewById(R.id.port_edit_text); |
||||
messageEdit = (EditText) findViewById(R.id.message_edit_text); |
||||
serverPortEdit = (EditText) findViewById(R.id.server_port_edit_text); |
||||
resultText = (TextView) findViewById(R.id.grpc_response_text); |
||||
resultText.setMovementMethod(new ScrollingMovementMethod()); |
||||
} |
||||
|
||||
@Override |
||||
protected void onPause() { |
||||
super.onPause(); |
||||
if (runServerTask != null) { |
||||
runServerTask.cancel(true); |
||||
runServerTask = null; |
||||
serverButton.setText("Start gRPC Server"); |
||||
} |
||||
if (grpcTask != null) { |
||||
grpcTask.cancel(true); |
||||
grpcTask = null; |
||||
} |
||||
} |
||||
|
||||
public void sendMessage(View view) { |
||||
((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE)) |
||||
.hideSoftInputFromWindow(hostEdit.getWindowToken(), 0); |
||||
sendButton.setEnabled(false); |
||||
resultText.setText(""); |
||||
grpcTask = new GrpcTask(this); |
||||
grpcTask.executeOnExecutor( |
||||
AsyncTask.THREAD_POOL_EXECUTOR, |
||||
hostEdit.getText().toString(), |
||||
messageEdit.getText().toString(), |
||||
portEdit.getText().toString()); |
||||
} |
||||
|
||||
public void startOrStopServer(View view) { |
||||
if (runServerTask != null) { |
||||
runServerTask.cancel(true); |
||||
runServerTask = null; |
||||
serverButton.setText("Start gRPC Server"); |
||||
Toast.makeText(this, "Server stopped", Toast.LENGTH_SHORT).show(); |
||||
} else { |
||||
runServerTask = new RunServerTask(this); |
||||
String portStr = serverPortEdit.getText().toString(); |
||||
int port = TextUtils.isEmpty(portStr) ? 50051 : Integer.valueOf(portStr); |
||||
runServerTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, port); |
||||
serverButton.setText("Stop gRPC Server"); |
||||
Toast.makeText(this, "Server started on port " + port, Toast.LENGTH_SHORT).show(); |
||||
} |
||||
} |
||||
|
||||
private static class RunServerTask extends AsyncTask<Integer, Void, Void> { |
||||
private final WeakReference<HelloworldActivity> activityReference; |
||||
|
||||
private RunServerTask(HelloworldActivity activity) { |
||||
this.activityReference = new WeakReference<HelloworldActivity>(activity); |
||||
} |
||||
|
||||
@Override |
||||
protected Void doInBackground(Integer... params) { |
||||
int port = params[0]; |
||||
HelloworldActivity activity = activityReference.get(); |
||||
if (activity != null) { |
||||
activity.startServer(port); |
||||
} |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
private static class GrpcTask extends AsyncTask<String, Void, String> { |
||||
private final WeakReference<HelloworldActivity> activityReference; |
||||
|
||||
private GrpcTask(HelloworldActivity activity) { |
||||
this.activityReference = new WeakReference<HelloworldActivity>(activity); |
||||
} |
||||
|
||||
@Override |
||||
protected String doInBackground(String... params) { |
||||
String host = params[0]; |
||||
String message = params[1]; |
||||
String portStr = params[2]; |
||||
int port = TextUtils.isEmpty(portStr) ? 50051 : Integer.valueOf(portStr); |
||||
return sayHello(host, port, message); |
||||
} |
||||
|
||||
@Override |
||||
protected void onPostExecute(String result) { |
||||
HelloworldActivity activity = activityReference.get(); |
||||
if (activity == null || isCancelled()) { |
||||
return; |
||||
} |
||||
TextView resultText = (TextView) activity.findViewById(R.id.grpc_response_text); |
||||
Button sendButton = (Button) activity.findViewById(R.id.send_button); |
||||
resultText.setText(result); |
||||
sendButton.setEnabled(true); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Invoked by native code to stop server when RunServerTask has been canceled, either by user |
||||
* request or upon app moving to background. |
||||
*/ |
||||
public boolean isRunServerTaskCancelled() { |
||||
if (runServerTask != null) { |
||||
return runServerTask.isCancelled(); |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
public static native String sayHello(String host, int port, String message); |
||||
|
||||
public native void startServer(int port); |
||||
} |
@ -0,0 +1,86 @@ |
||||
<LinearLayout 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" |
||||
tools:context=".HelloworldActivity" |
||||
android:orientation="vertical" > |
||||
|
||||
<TextView |
||||
android:layout_width="match_parent" |
||||
android:layout_height="wrap_content" |
||||
android:paddingTop="12dp" |
||||
android:paddingBottom="12dp" |
||||
android:textSize="16sp" |
||||
android:text="gRPC Client Configuration" |
||||
android:textStyle="bold" /> |
||||
|
||||
<LinearLayout |
||||
android:layout_width="match_parent" |
||||
android:layout_height="wrap_content" |
||||
android:orientation="horizontal"> |
||||
<EditText |
||||
android:id="@+id/host_edit_text" |
||||
android:layout_weight="2" |
||||
android:layout_width="0dp" |
||||
android:layout_height="wrap_content" |
||||
android:hint="Enter Host" /> |
||||
<EditText |
||||
android:id="@+id/port_edit_text" |
||||
android:layout_weight="1" |
||||
android:layout_width="0dp" |
||||
android:layout_height="wrap_content" |
||||
android:inputType="numberDecimal" |
||||
android:hint="Enter Port" /> |
||||
</LinearLayout> |
||||
|
||||
|
||||
<EditText |
||||
android:id="@+id/message_edit_text" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="wrap_content" |
||||
android:hint="Enter message to send" /> |
||||
|
||||
<Button |
||||
android:id="@+id/send_button" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="wrap_content" |
||||
android:onClick="sendMessage" |
||||
android:text="Send gRPC Request" /> |
||||
|
||||
<TextView |
||||
android:layout_width="match_parent" |
||||
android:layout_height="wrap_content" |
||||
android:paddingTop="12dp" |
||||
android:paddingBottom="12dp" |
||||
android:textSize="16sp" |
||||
android:text="Response:" /> |
||||
|
||||
<TextView |
||||
android:id="@+id/grpc_response_text" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="wrap_content" |
||||
android:scrollbars = "vertical" |
||||
android:textSize="16sp" /> |
||||
|
||||
<TextView |
||||
android:layout_width="match_parent" |
||||
android:layout_height="wrap_content" |
||||
android:paddingTop="12dp" |
||||
android:paddingBottom="12dp" |
||||
android:textSize="16sp" |
||||
android:text="gRPC Server Configuration" |
||||
android:textStyle="bold" /> |
||||
|
||||
<EditText |
||||
android:id="@+id/server_port_edit_text" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="wrap_content" |
||||
android:hint="Server port" /> |
||||
|
||||
<Button |
||||
android:id="@+id/server_button" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="wrap_content" |
||||
android:onClick="startOrStopServer" |
||||
android:text="Start gRPC Server" /> |
||||
|
||||
</LinearLayout> |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 4.7 KiB |
After Width: | Height: | Size: 7.5 KiB |
@ -0,0 +1,3 @@ |
||||
<resources> |
||||
<string name="app_name">GrpcHelloworldCppExample</string> |
||||
</resources> |
@ -0,0 +1,24 @@ |
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules. |
||||
|
||||
buildscript { |
||||
repositories { |
||||
google() |
||||
jcenter() |
||||
} |
||||
dependencies { |
||||
classpath 'com.android.tools.build:gradle:3.0.1' |
||||
// NOTE: Do not place your application dependencies here; they belong |
||||
// in the individual module build.gradle files |
||||
} |
||||
} |
||||
|
||||
allprojects { |
||||
repositories { |
||||
google() |
||||
jcenter() |
||||
} |
||||
} |
||||
|
||||
task clean(type: Delete) { |
||||
delete rootProject.buildDir |
||||
} |
@ -0,0 +1,17 @@ |
||||
# Project-wide Gradle settings. |
||||
|
||||
# IDE (e.g. Android Studio) users: |
||||
# Gradle settings configured through the IDE *will override* |
||||
# any settings specified in this file. |
||||
|
||||
# For more details on how to configure your build environment visit |
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html |
||||
|
||||
# Specifies the JVM arguments used for the daemon process. |
||||
# The setting is particularly useful for tweaking memory settings. |
||||
org.gradle.jvmargs=-Xmx1536m |
||||
|
||||
# When configured, Gradle will run in incubating parallel mode. |
||||
# This option should only be used with decoupled projects. More details, visit |
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects |
||||
# org.gradle.parallel=true |
Binary file not shown.
@ -0,0 +1,6 @@ |
||||
#Thu Jan 25 11:45:30 PST 2018 |
||||
distributionBase=GRADLE_USER_HOME |
||||
distributionPath=wrapper/dists |
||||
zipStoreBase=GRADLE_USER_HOME |
||||
zipStorePath=wrapper/dists |
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip |
@ -0,0 +1,160 @@ |
||||
#!/usr/bin/env bash |
||||
|
||||
############################################################################## |
||||
## |
||||
## Gradle start up script for UN*X |
||||
## |
||||
############################################################################## |
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. |
||||
DEFAULT_JVM_OPTS="" |
||||
|
||||
APP_NAME="Gradle" |
||||
APP_BASE_NAME=`basename "$0"` |
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value. |
||||
MAX_FD="maximum" |
||||
|
||||
warn ( ) { |
||||
echo "$*" |
||||
} |
||||
|
||||
die ( ) { |
||||
echo |
||||
echo "$*" |
||||
echo |
||||
exit 1 |
||||
} |
||||
|
||||
# OS specific support (must be 'true' or 'false'). |
||||
cygwin=false |
||||
msys=false |
||||
darwin=false |
||||
case "`uname`" in |
||||
CYGWIN* ) |
||||
cygwin=true |
||||
;; |
||||
Darwin* ) |
||||
darwin=true |
||||
;; |
||||
MINGW* ) |
||||
msys=true |
||||
;; |
||||
esac |
||||
|
||||
# Attempt to set APP_HOME |
||||
# Resolve links: $0 may be a link |
||||
PRG="$0" |
||||
# Need this for relative symlinks. |
||||
while [ -h "$PRG" ] ; do |
||||
ls=`ls -ld "$PRG"` |
||||
link=`expr "$ls" : '.*-> \(.*\)$'` |
||||
if expr "$link" : '/.*' > /dev/null; then |
||||
PRG="$link" |
||||
else |
||||
PRG=`dirname "$PRG"`"/$link" |
||||
fi |
||||
done |
||||
SAVED="`pwd`" |
||||
cd "`dirname \"$PRG\"`/" >/dev/null |
||||
APP_HOME="`pwd -P`" |
||||
cd "$SAVED" >/dev/null |
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar |
||||
|
||||
# Determine the Java command to use to start the JVM. |
||||
if [ -n "$JAVA_HOME" ] ; then |
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then |
||||
# IBM's JDK on AIX uses strange locations for the executables |
||||
JAVACMD="$JAVA_HOME/jre/sh/java" |
||||
else |
||||
JAVACMD="$JAVA_HOME/bin/java" |
||||
fi |
||||
if [ ! -x "$JAVACMD" ] ; then |
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME |
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the |
||||
location of your Java installation." |
||||
fi |
||||
else |
||||
JAVACMD="java" |
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. |
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the |
||||
location of your Java installation." |
||||
fi |
||||
|
||||
# Increase the maximum file descriptors if we can. |
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then |
||||
MAX_FD_LIMIT=`ulimit -H -n` |
||||
if [ $? -eq 0 ] ; then |
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then |
||||
MAX_FD="$MAX_FD_LIMIT" |
||||
fi |
||||
ulimit -n $MAX_FD |
||||
if [ $? -ne 0 ] ; then |
||||
warn "Could not set maximum file descriptor limit: $MAX_FD" |
||||
fi |
||||
else |
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" |
||||
fi |
||||
fi |
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock |
||||
if $darwin; then |
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" |
||||
fi |
||||
|
||||
# For Cygwin, switch paths to Windows format before running java |
||||
if $cygwin ; then |
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"` |
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` |
||||
JAVACMD=`cygpath --unix "$JAVACMD"` |
||||
|
||||
# We build the pattern for arguments to be converted via cygpath |
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` |
||||
SEP="" |
||||
for dir in $ROOTDIRSRAW ; do |
||||
ROOTDIRS="$ROOTDIRS$SEP$dir" |
||||
SEP="|" |
||||
done |
||||
OURCYGPATTERN="(^($ROOTDIRS))" |
||||
# Add a user-defined pattern to the cygpath arguments |
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then |
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" |
||||
fi |
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh |
||||
i=0 |
||||
for arg in "$@" ; do |
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` |
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option |
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition |
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` |
||||
else |
||||
eval `echo args$i`="\"$arg\"" |
||||
fi |
||||
i=$((i+1)) |
||||
done |
||||
case $i in |
||||
(0) set -- ;; |
||||
(1) set -- "$args0" ;; |
||||
(2) set -- "$args0" "$args1" ;; |
||||
(3) set -- "$args0" "$args1" "$args2" ;; |
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;; |
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; |
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; |
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; |
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; |
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; |
||||
esac |
||||
fi |
||||
|
||||
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules |
||||
function splitJvmOpts() { |
||||
JVM_OPTS=("$@") |
||||
} |
||||
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS |
||||
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" |
||||
|
||||
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" |
@ -0,0 +1,90 @@ |
||||
@if "%DEBUG%" == "" @echo off |
||||
@rem ########################################################################## |
||||
@rem |
||||
@rem Gradle startup script for Windows |
||||
@rem |
||||
@rem ########################################################################## |
||||
|
||||
@rem Set local scope for the variables with windows NT shell |
||||
if "%OS%"=="Windows_NT" setlocal |
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. |
||||
set DEFAULT_JVM_OPTS= |
||||
|
||||
set DIRNAME=%~dp0 |
||||
if "%DIRNAME%" == "" set DIRNAME=. |
||||
set APP_BASE_NAME=%~n0 |
||||
set APP_HOME=%DIRNAME% |
||||
|
||||
@rem Find java.exe |
||||
if defined JAVA_HOME goto findJavaFromJavaHome |
||||
|
||||
set JAVA_EXE=java.exe |
||||
%JAVA_EXE% -version >NUL 2>&1 |
||||
if "%ERRORLEVEL%" == "0" goto init |
||||
|
||||
echo. |
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. |
||||
echo. |
||||
echo Please set the JAVA_HOME variable in your environment to match the |
||||
echo location of your Java installation. |
||||
|
||||
goto fail |
||||
|
||||
:findJavaFromJavaHome |
||||
set JAVA_HOME=%JAVA_HOME:"=% |
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe |
||||
|
||||
if exist "%JAVA_EXE%" goto init |
||||
|
||||
echo. |
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% |
||||
echo. |
||||
echo Please set the JAVA_HOME variable in your environment to match the |
||||
echo location of your Java installation. |
||||
|
||||
goto fail |
||||
|
||||
:init |
||||
@rem Get command-line arguments, handling Windowz variants |
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args |
||||
if "%@eval[2+2]" == "4" goto 4NT_args |
||||
|
||||
:win9xME_args |
||||
@rem Slurp the command line arguments. |
||||
set CMD_LINE_ARGS= |
||||
set _SKIP=2 |
||||
|
||||
:win9xME_args_slurp |
||||
if "x%~1" == "x" goto execute |
||||
|
||||
set CMD_LINE_ARGS=%* |
||||
goto execute |
||||
|
||||
:4NT_args |
||||
@rem Get arguments from the 4NT Shell from JP Software |
||||
set CMD_LINE_ARGS=%$ |
||||
|
||||
:execute |
||||
@rem Setup the command line |
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar |
||||
|
||||
@rem Execute Gradle |
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% |
||||
|
||||
:end |
||||
@rem End local scope for the variables with windows NT shell |
||||
if "%ERRORLEVEL%"=="0" goto mainEnd |
||||
|
||||
:fail |
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of |
||||
rem the _cmd.exe /c_ return code! |
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 |
||||
exit /b 1 |
||||
|
||||
:mainEnd |
||||
if "%OS%"=="Windows_NT" endlocal |
||||
|
||||
:omega |
@ -0,0 +1 @@ |
||||
include ':app' |
Loading…
Reference in new issue