728x90

해피투게더에 송가인이 나와서 핫하다는 해피투게더 예능(2019.8.1일자)를 보고나서 검색을 했고 미스트롯을 다운로드 받아서 봤다.

차분하게 볼 정도로 마음의 여유와 시간은 없기 때문에 송가인 중심으로, 정미애 중심으로 찾아서 노래를 들었다.

내 기준에는 송가인보다 정미애 노래가 더 좋은데... 이런 생각이 들었지만, 대중들은 송가인에 열풍하고 있다기에 왜 그럴까 더 궁금해서 계속 송가인의 노래를 들었고, AGAIN 에 회원가입을 했다.

팬심이 열성적인 그런 마음으로 가입하고 활동을 하는 건 아니다.

나한테 노래는 빠순이 처럼 열성적인 관심은 전혀 없다. 딸래미가 김준수 팬심으로 콘서트에도 가고 그러지만 난 그런 열정은 없다.

지금 당장 배우는 프로그램 실력 향상에 투자할 시간이 부족하다. 스트레스 해소하기 위해 힐링 차원으로 예능을 보는 정도다.


정통 트롯트를 듣다보니 고교시절에 트롯트를 따라 불러봤던 기억도 나고, 나훈아의 대동강편지, 고향역, 남진의 빈잔, 윤민호의 연상의 여인 노래를 다시금 불러보기도 한다.

난 잘 부르는 것은 아니지만 연습으로 계속 따라부르던 노래는 잘 부른다고 인정받는 곡은 몇 곡된다.

장르가 트롯트에 한정되어 있다. 아내는 "당신 목소리는 트롯트에 최적화된 목소리야. 가수를 했어도 성공했을 거야"라는 덕담을 한다.

하지만 딱 거기까지다. 노래를 열심히 부르고 싶은 마음이 없기 때문이다.

집에 1960년대 앰프 등 HiFi 오디오가 있는 것은, 음악 듣는 시간을 많이 할애하기 보단 한곡을 들어도 고음질로 듣고 좋은 소리를 듣기 위해서다.


뽕따러가세 예능을 계속 보면서 송가인이 기본기가 탄탄하고, 트롯트 뿐만 아니라 다른 장르 노래도 본인 노래로 소화해서 잘 부른다는 걸 알게되었다.

단 한가지 아쉬운 점은 정통 트롯트 장르를 고집해서 인지는 몰라도 국민 누구나가 공감할 수 있는 대중적인 노래를 선곡하지 못하는 거 같다.

30~40대에겐 송가인보다 홍진영의 인지도가 훨씬 더 높다. 송가인에 대해 잘 모를 수도 있다.

전쟁을 겪고 경제적으로 궁핍한 시대를 살았던 세대들에게는 더 공감될지 모르지만, 우리나라가 경제적으로 여유가 있게 된 요즈음 세대들에겐 공감이 되지 않을 것이다.

가난해서 마음이 우울하고, 대학 학비를 벌어서 다녀야 했던 그시절에는 트롯트 장르 노래가 더 마음에 와 닿았다. 회사에 입사하고, 경제적 여유가 좀 나아진 지금은 트롯트 보다는 다른 장르 노래를 더 선호한다.


송가인에 홀릭해서 송가인이 나오는 예능을 많이 찾아봤다. 그러면서 전지적 참견시점 이라는 방송도 보게 되었고, 놀면 뭐하니 방송도 알게되었다.

유재석의 뽕포유 프로젝트는 재미를 주기 위한 면도 있지만, 트롯트라는 열악한 장르를 대중들에게 알리는 역할이 더 큰 거 같다.

"합정역 5번 출구", "사랑의 재개발" 이란 노래는, 유재석의 파워로 트로트 장르를 알리는 데는 매우 큰 역할을 했다고 본다.

놀면 뭐하니라는 방송을 통해서 박현우, 정경천 등의 작곡가의 이름석자를 알게되었고, 세미트롯이 전 연령층에는 훨씬 더 공감이 간다는 걸 다시금 느낀다.


송가인은 탄탄한 기본기가 있으니까 정통 트롯트만 고집하지 말고, 세미트롯트 장르도 택해서 많은 연령층이 따라 부를 수 있도록 하는 것이 필요하지 않을까 싶다. 송가인의 엄마아리랑 보다는 합정역 5번 출구가 더 따라부르기 좋아 부르고 싶어진다. 엄마 아리랑은 나쁘지 않고 듣기 좋은 곡 정도다. 팬심이 엄청 많은 사람들에겐 다를지 모르지만.

정미애는 이선희의 모창가수라는 틀을 벗어나서 본인만의 색깔을 갖춰야 성공할 수 있다고 본다.


https://www.msn.com/ko-kr/news/national/%EB%AA%87%EB%8B%AC-%EC%A0%84%EA%B9%8C%EC%A7%84-%EC%9B%94%EC%84%B8%EB%B0%A9%EC%84%9C-%EB%B9%84%EB%85%80-%EB%A7%8C%EB%93%A4%EC%96%B4-%ED%8C%94%EC%95%98%EB%8A%94%EB%8D%B0%E2%80%A6-%EC%9D%B4%EA%B1%B4-%EA%B8%B0%EC%A0%81%EC%9D%B4%EC%97%90%EC%9A%94-%EA%B8%B0%EC%A0%81/ar-AAARsWJ


블로그 이미지

Link2Me

,
728x90

Firebase 에서 구글 인증으로 로그인 처리하는 방법에 대한 사항이다.


1. https://console.firebase.google.com/ 에 접속한다.

  

   프로젝트가 없으면 추가하고 있으면 선택한다.


2. 앱을 설정하는 일련의 과정은 그림으로 대신한다.


3. 앱 build.gradle 파일 내용

apply plugin: 'com.android.application'

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.2"

    defaultConfig {
        applicationId "com.link2me.android.googleauth"
        minSdkVersion 21
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }

}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    implementation 'com.google.firebase:firebase-analytics:17.2.0'
    implementation 'com.google.firebase:firebase-auth:19.1.0'  // Firebase 인증에 대한 항목을 추가
    implementation 'com.google.android.gms:play-services-auth:17.0.0'
}

apply plugin: 'com.google.gms.google-services'


4. XML Layout 작성

<RelativeLayout  xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center" >

    <!-- 구글 로그인 버튼 -->
    <com.google.android.gms.common.SignInButton
        android:id="@+id/sign_in_button"
        android:layout_width="254dp"
        android:layout_height="50sp">
    </com.google.android.gms.common.SignInButton>

    <ImageView
        android:id="@+id/sign_out_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_below="@+id/sign_in_button"
        android:background="@drawable/google_signout"/>

    <TextView
        android:id="@+id/mStatusTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_below="@+id/sign_in_button"
        android:layout_marginTop="99dp"
        android:text="TextView" />

</RelativeLayout >


5. 사용자 인증 코드 작성

https://firebase.google.com/docs/auth/android/google-signin?authuser=0 에서 필요한 코드를 복사하여 붙여넣기 한다.

import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import com.google.android.gms.auth.api.signin.GoogleSignIn;
import com.google.android.gms.auth.api.signin.GoogleSignInAccount;
import com.google.android.gms.auth.api.signin.GoogleSignInClient;
import com.google.android.gms.auth.api.signin.GoogleSignInOptions;
import com.google.android.gms.common.api.ApiException;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.auth.AuthCredential;
import com.google.firebase.auth.AuthResult;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.auth.GoogleAuthProvider;

public class GoogleSignInActivity extends AppCompatActivity implements View.OnClickListener {
    private static final String TAG = "GoogleActivity";
    private static final int RC_SIGN_IN = 9001;
    private FirebaseAuth mAuth;
    private GoogleSignInClient mGoogleSignInClient;

    private TextView mStatusTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_google);

        // Button listeners
        findViewById(R.id.sign_in_button).setOnClickListener(this);
        findViewById(R.id.sign_out_button).setOnClickListener(this);
        mStatusTextView = findViewById(R.id.mStatusTextView);

        // Configure Google Sign In
        GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
                .requestIdToken(getString(R.string.default_web_client_id))
                .requestEmail()
                .build();

        mGoogleSignInClient = GoogleSignIn.getClient(this, gso);

        // Initialize Firebase Auth
        mAuth = FirebaseAuth.getInstance();
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.sign_in_button:
                signIn(); // 구글 로그인 버튼 클릭시
                break;
            case R.id.sign_out_button:
                signOut();
                break;
        }
    }

    @Override
    public void onStart() {
        super.onStart();
        // Check if user is signed in (non-null) and update UI accordingly.
        FirebaseUser currentUser = mAuth.getCurrentUser();
        updateUI(currentUser);
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        // Result returned from launching the Intent from GoogleSignInApi.getSignInIntent(...);
        if (requestCode == RC_SIGN_IN) {
            Task<GoogleSignInAccount> task = GoogleSignIn.getSignedInAccountFromIntent(data);
            try {
                // Google Sign In was successful, authenticate with Firebase
                GoogleSignInAccount account = task.getResult(ApiException.class);
                firebaseAuthWithGoogle(account);
            } catch (ApiException e) {
                Log.w(TAG, "Google sign in failed", e);
                updateUI(null);
            }
        }
    }

    private void firebaseAuthWithGoogle(GoogleSignInAccount acct) {
        Log.d(TAG, "firebaseAuthWithGoogle:" + acct.getId());

        AuthCredential credential = GoogleAuthProvider.getCredential(acct.getIdToken(), null);
        mAuth.signInWithCredential(credential)
                .addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
                    @Override
                    public void onComplete(@NonNull Task<AuthResult> task) {
                        if (task.isSuccessful()) {
                            // Sign in success, update UI with the signed-in user's information
                            Log.e(TAG, "signInWithCredential:success");
                            FirebaseUser user = mAuth.getCurrentUser();
                            updateUI(user);
                            Log.e(TAG,"user getEmail : "+user.getEmail());
                            Log.e(TAG,"user getDisplayName : "+user.getDisplayName());
                            Log.e(TAG,"user getUid : "+user.getUid());
                        } else {
                            // If sign in fails, display a message to the user.
                            Log.w(TAG, "signInWithCredential:failure", task.getException());
                            Toast.makeText(GoogleSignInActivity.this, "Authentication Failed.", Toast.LENGTH_SHORT).show();
                            updateUI(null);
                        }

                        // [START_EXCLUDE]
                        //hideProgressDialog();
                        // [END_EXCLUDE]
                    }
                });
    }

    private void signIn() {
        Intent signInIntent = mGoogleSignInClient.getSignInIntent();
        startActivityForResult(signInIntent, RC_SIGN_IN);
    }

    private void signOut() {
        mAuth.signOut(); // Firebase sign out

        // Google sign out
        mGoogleSignInClient.signOut().addOnCompleteListener(this,
                new OnCompleteListener<Void>() {
                    @Override
                    public void onComplete(@NonNull Task<Void> task) {
                        updateUI(null);
                    }
                });
    }

    private void revokeAccess() {
        // Firebase sign out
        mAuth.signOut();

        // Google revoke access
        mGoogleSignInClient.revokeAccess().addOnCompleteListener(this,
                new OnCompleteListener<Void>() {
                    @Override
                    public void onComplete(@NonNull Task<Void> task) {
                        updateUI(null);
                    }
                });
    }

    private void updateUI(FirebaseUser user) {
        //hideProgressDialog();
        if (user != null) {
            mStatusTextView.setText(user.getEmail() + " : " + user.getDisplayName());

            findViewById(R.id.sign_in_button).setVisibility(View.GONE);
            findViewById(R.id.sign_out_button).setVisibility(View.VISIBLE);
        } else {
            mStatusTextView.setText("LogOut");

            findViewById(R.id.sign_in_button).setVisibility(View.VISIBLE);
            findViewById(R.id.sign_out_button).setVisibility(View.GONE);
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //FirebaseAuth.getInstance().signOut();
    }
}


6. 로그인 후 Firebase 에 회원정보 저장 결과





블로그 이미지

Link2Me

,

Flutter란

하이브리드앱 2019. 12. 27. 07:27
728x90

구글 검색으로 Flutter 에 대해 찾아서 요약 정리를 해 본 것이다.

인프런 사이트에서 25% 할인을 미끼로 하길래 Flutter 강좌들은 수강생 평이 좋아서 결제를 했는데 무작정 결제를 한 것이 후회된다. 좀 더 정보 수집을 하고 나서 할 껄~~~

수많은 하이브리드앱이 출시되고 시장에 자리잡기 위한 노력을 하지만, Flutter가 개발자의 헛된 투자가 되지 말아야 할텐데 하는 마음이 든다.

아직 초보인지라 개발 익히려고 투자한 노력 대비 효과가 부족할 거 같아 먼저 iOS Swift  배우고 나서 시작해 볼 생각이다. Android도 코틀린 배워야 해서 항상 배워야 하는 것이 힘들다.

안드로이드 C/C++ 프로그래밍 책을 교보문고에 가서 사오기는 했는데, 언어가 코틀린으로 되어 있다.

다른 NDK 책들은 오래전에 출시되어 Eclipse 기반으로 설명이 되어 있는거 같더라.


플랫폼 시장에서는 어떤 기업이, 혹은 어떤 프레임워크가 최종 승자가 될지 아무도 알 수 없다.
크로스 플랫폼 기술은 하나의 코드를 여러 플랫폼에서 사용할 수 있게 해주는 기술을 말한다.
유명한 크로스 플랫폼으로 Xamarin, React Native, Flutter를 꼽을 수 있다.
Xamarin은 UI 개발 편의성이 좋지 않다는 점이 약점이다.
Flutter는 Google에서 개발한 크로스 플랫폼 모바일 앱 개발 프레임워크이다.
Flutter는 기존에 존재하는 Android와 iOS의 위젯을 사용하지 않고, 자체 Skia 엔진을 이용해 UI를 ‘그리는’ 방식을 사용한다.
Google이 제공하는 위젯을 통해, iOS에서 Cupertino 디자인을, Android에서 Material 디자인을 쉽게 구현할 수 있다.
Android Studio 에서 개발이 가능하고, 개발한 코드를 수정과 동시에 적용하여 살펴볼 수 있는 Hot Reload 시스템도 적용되어 있다.
그외 IntelliJ, VS Code를 사용하여 빌드가 가능하다.
기존 React Native 혹은 Hybrid App의 경우 브릿지를 통한 통신이 불가피했다. 하지만 Flutter는 직접 컴파일되서 Render를 직접 하기때문에 성능이 빠르다.
안드로이드(Java, Kotlin)와 iOS(Object-C, Swift)에서 동작하는 앱을 하나의 코드 베이스로 구현할 수 있는 것은 대단히 큰 장점이다.
아직 플러그인들은 부족한 편이고, 구글에서 만든 언어인 Dart를 사용하는 것은 Flutter의 가장 큰 단점이다.

Flutter가 아무리 자기 스스로를 크로스 플랫폼 앱 개발 툴킷이라고 부르지만, 결국 개발이 조금만 진전되면 그때부터는 안드로이드와 iOS 부분에 자잘하게 손이 많이 간다.
뭐 자세히 알지는 못하더라도 일단 어떤 패키지를 사용할때 어떤 플랫폼에는 어떤 문제 문제가 있다던지, 문제가 생겼을 때 해결책이 다르다던지 하는 것들이 상상 이상으로 많다.
구글팀의 대응이 상당히 느리다는게 큰 문제이다. 비록 깃헙을 통해 오픈소스로 이루어져 있고, 외부 개발자들의 피드백을 열심히 대응하고 있는것은 확실하다.


Android Jetpack
Jetpack은 개발자가 고품질 앱을 손쉽게 개발할 수 있게 돕는 라이브러리, 도구, 가이드 모음이다.
Jetpack은 플랫폼 API와는 별도로 제공되는 androidx.* 패키지 라이브러리로 구성된다.
Android Jetpack 구성요소는 개별적으로 채택할 수 있고 생산성을 향상하는 Kotlin 언어 기능을 활용하면서 함께 작동하도록 빌드된 라이브러리 컬렉션이다.

'하이브리드앱' 카테고리의 다른 글

[jQuery mobile] UI Layout 구성  (0) 2018.11.23
PhoneGap 안드로이드 APK 정렬하기  (0) 2018.11.23
PhoneGap CLI 설치 과정  (0) 2018.11.22
블로그 이미지

Link2Me

,

C++ MFC 환경

C#/Visual Studio 2019. 12. 23. 17:03
728x90

마이크로소프트 파운데이션 클래스 라이브러리(Microsoft Foundation Class Library)는 C++용 프로그램 라이브러리이다.
블로그 글을 찾아서 읽다보니
C++ 공부하는 분들 제발 MFC책부터 펼치지 말란다.
VS2008에서 사용하는 C++ 표준이라고 해봐야 옛날 고전 문법 정도가 전부다.
C++ 개발자지만, 나쁘게 이야기하면 MFC밖에 안 다뤄봤다.
C++ 개발자라 하기엔 뭔가 부족한 느낌이다.
C#으로 UI를 만들고 C++로 엔진 구현하는게 추세다.
MFC는 공식적으로 1999년에 MS가 지원을 끊었다.


C++ MFC를 사용하기 위한 기본 설치 방법이다.





MySQL 과 C++ 연동하는 걸 해보려고 검색했더니 MFC 자료가 나오길래 설치하는 방법을 적어두기는 한다.

검색결과 이건 제대로 된 방향이 아니라고 하니까 일단 C++ 문법 지식 제대로 익히는데 주력해야 하나보다.

블로그 이미지

Link2Me

,

ScrollView

안드로이드/Layout 2019. 12. 20. 21:57
728x90

Android Layout 을 작성하다보면 종종 ScrollView를 사용하는 경우가 있다. ScrollView는 길어지는 높이로 인해 화면에서 일부 View가 잘리는 현상을 상하 스크롤을 통해 해소할 수 있는 View이다.

 

주의할점: 스크롤뷰에는 단 하나의 자식 View만 포함되어야 한다!!
그래서, 여러개의 View를 넣으려면 스크롤뷰 안에 LinearLayout(
리니어레이아웃) 또는 RelativeLayout 등의 ViewGroup을 자식 View로 지정하고 그 안에 다양한 View를 넣는 구조를 택하여 해결할 수 있다.

 

<ScrollView
    android:id="@+id/scrollview"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity ="center"
    android:fillViewport="true"
    android:layout_marginTop="0dp"
    android:background="#000000">
 
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">
 
        <TextView
            android:id="@+id/info"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10dp"
            android:gravity="left"
            android:textColor="#FFFFFF"/>
 
        <TextView
            android:id="@+id/consoleText"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10dp"
            android:ellipsize="start"
            android:gravity="left"
            android:textColor="#FFFFFF"
            android:textStyle="bold" />
    </LinearLayout>
</ScrollView> 

 

아래 코드는 콘솔 앱에 사용된 코드 일부이다.

 

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    Context mContext;
    TextView mTitleTextView;
    TextView mDumpTextView;
    ScrollView mScrollView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mContext = MainActivity.this;
        initView();
    }
    private void initView() {
        mTitleTextView = (TextView) findViewById(R.id.info);
        mDumpTextView = (TextView) findViewById(R.id.consoleText);
        mScrollView = findViewById(R.id.scrollview);
        mScrollView.setVerticalScrollBarEnabled(true); // 수직방향 스크롤바 사용 가능하도록 설정
        mDumpTextView.setMovementMethod(new ScrollingMovementMethod());
        mDumpTextView.setTextSize(Constants.mTextFontSize);
        mDumpTextView.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                mDumpTextView.setTextIsSelectable(true); // 복사 가능
                mDumpTextView.setCursorVisible(true);
                SpannableString highlightString = new SpannableString(mDumpTextView.getText());
                highlightString.setSpan(new BackgroundColorSpan(ContextCompat.getColor(mContext, R.color.mediumspringgreen))
                        , 0, mDumpTextView.getText().length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
                return false;
            }
        });
        mDumpTextView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mDumpTextView.setTextIsSelectable(true); // 복사 가능
                mDumpTextView.setCursorVisible(false);
            }
        });
    }
    private void updateReceivedData(String data) {
        final String message = data;
        mDumpTextView.setTextColor(Color.WHITE);
        mDumpTextView.append(message);
        mDumpTextView.setTextIsSelectable(false); // 텍스트 클립보드 복사
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            mDumpTextView.setElegantTextHeight(true);
        }
        mDumpTextView.setImeOptions(EditorInfo.IME_FLAG_NO_ENTER_ACTION);
        mDumpTextView.setInputType(InputType.TYPE_TEXT_FLAG_MULTI_LINE);
        mDumpTextView.setSingleLine(false);
        refreshView();
    }
    private void refreshView(){
        mScrollView.postDelayed(new Runnable() {
            @Override
            public void run() {
                mScrollView.fullScroll(ScrollView.FOCUS_DOWN);
            }
        },100);
        mScrollView.smoothScrollTo(0, mDumpTextView.getBottom());
    }

 

'안드로이드 > Layout' 카테고리의 다른 글

CardView Layout 예제  (0) 2020.04.12
Meterial Design 로그인 Layout 예제  (0) 2020.03.21
Dynamic Layouts  (0) 2019.01.06
Android Fragment 기본 예제  (0) 2018.09.11
FloatingActionButton(FAB)  (0) 2018.08.15
블로그 이미지

Link2Me

,
728x90

Visual Studio 에서 C++ 포인터 주소 등 주소 할당에 대해 실행할 때 마다 메모리 주소가 다르게 변경된다.

메모리 주소를 고정시키는 방법이다.





블로그 이미지

Link2Me

,

C++ stack 영역

C++/C++ 문법 2019. 12. 17. 09:01
728x90

C 및 C++ 에서 변수가 어떻게 저장되는지를 예제를 통해서 알아보자.

[그림 출처] : http://myblog.opendocs.co.kr/archives/1301


전역변수 및 상수는 data 영역을 사용한다.

지역변수 앞에 static을 붙이면 전역변수가 된다.

Heap 메모리 : 힙 영역에 저장되고 지속성이 보장되나 프로그래머에의해 꼼꼼한 관리(메모리 해제)가 필요함.

stack은 LIFO(Last In First Out) 방식의 시퀀스이다.
지역변수, 매개변수는 stack 영역을 사용한다.


#include <iostream>
using namespace std;

void sub(int x) {
    int k = 2;
    cout << "x = " << x << ", x 주소 : " << (uintptr_t)&x << endl;
    cout << "k = " << k << ", k 주소 : " << (uintptr_t)&k << endl;
}

int main() {
    int a = 3;
    cout << "a = " << a << ", a 주소 : " << (uintptr_t)&a << endl;
    sub(a);
}

실행결과

맨 처음에 a 주소가 할당되면서 주소값이 가장 크다는 걸 알 수 있고,

k주소값은 맨 나중에 할당되면서 주소값이 적다는 걸 알 수 있다.

여기서 int a = 3 을 static int a = 3 으로 변경해서 주소값이 어떻게 바뀌는지 확인해보라.

그리고 static int k = 2; 으로 변경해서 주소값을 확인해 보라.



sub 함수를 호출할 때 사용하는 주소가 어떻게 다른지 확인해보자.

#include <iostream>
using namespace std;

void sub(int x) { // int x 는 매개변수
    cout << "x = " << x << ", x 주소 : " << (uintptr_t)&x << endl;
}

int main() {
    sub(1);
    sub(3);
    sub(5);
}

실행결과 → 매개변수는 모두 동일한 메모리 주소를 사용

함수가 매개변수 주소를 할당하고, 반납하고, 다시 같은 주소에 값을 할당하고, 반납한다는 걸 알 수 있다.


재귀함수 호출을 해 봄으로써 변수가 어떻게 저장되는지 확인해보자.

#include <iostream>
using namespace std;

void sub(int x) {
    cout << "x = " << x << ", x 주소 : " << (uintptr_t)&x << endl;
    if(x > 1) sub(x - 1);
}

int main() {
    sub(1);
    sub(3);
    sub(5);
}

실행결과

주소가 어떻게 할당되는지 확인해 보면,

sub(1)을 실행시 매개변수 1에 대한 주소값을 사용하고, 주소를 반납한다.

sub(3)을 실행시 매개변수 3에 대한 주소값이 sub(1) 실행시의 주소값과 같음을 확인할 수 있다.

재귀함수 sub(2)의 주소는 처음할당된 주소값보다 작음을 알 수 있다.

재귀함수 sub(1)의 주소는 더 적은 값임을 알 수 있다.

sub(5)을 실행시 매개변수 5에 대한 주소값, 재귀함수 sub(4), sub(3), sub(2), sub(1)에 대한 주소값을 확인해보면,

맨 처음에 할당된 주소값이 가장 크고, 위로 갈 수록 주소값이 점점 적어지는 걸 확인할 수 있다.



예제1. 출처 : https://www.softwaretestinghelp.com/stack-in-cpp/


#include<iostream>
using namespace std;

#define MAX 1000 //max size for stack

class Stack {
    int top;
public:
    int myStack[MAX]; //stack array

    Stack() { top = -1; }
    bool push(int x);
    int pop();
    bool isEmpty();
};

//pushes element on to the stack
bool Stack::push(int item) {
    if (top >= (MAX - 1)) {
        cout << "Stack Overflow!!!";
        return false;
    }
    else {
        myStack[++top] = item;
        cout << item << endl;
        return true;
    }
}

//removes or pops elements out of the stack
int Stack::pop() {
    if (top < 0) {
        cout << "Stack Underflow!!";
        return 0;
    }
    else {
        int item = myStack[top--];
        return item;
    }
}

//check if stack is empty
bool Stack::isEmpty() {
    return (top < 0);
}

// main program to demonstrate stack functions
int main() {
    class Stack stack;
    cout << "The Stack Push " << endl;
    stack.push(10); // 스택에 등록
    stack.push(20);
    stack.push(30);

    cout << endl << "The Stack Pop : " << endl;
    while (!stack.isEmpty()) { // 스택이 비어 있지 않는 동안
        cout << stack.pop() << endl;
    }
    return 0;
}


예제2.

#include <iostream>
#include <vector>
#include <stack>
using namespace std;

int main() {

    stack<int> st;  // default 는 deque 컨테이너 사용
    //stack<int, vector<int>> st;  vector 컨테이너를 이용하여 stack 컨테이너 생성

    st.push(10);    // stack에 등록
    st.push(20);
    st.push(30);

    cout << st.top() << endl;   // stack 제일 위의 요소 가져오기
    st.pop();  // 스택 제일 위의 요소 제거
    cout << st.top() << endl;
    st.pop();
    cout << st.top() << endl;
    st.pop();

    if (st.empty())  // stack이 비었는지 확인
        cout << "stack에 데이터 없음" << endl;

    return 0;
}
 

실행해보면, stack 에 저장된 순서가 10, 20, 30 으로 맨 아래부터 저장되어 맨 위에 30이 저장되어 있음을 확인할 수 있다.

블로그 이미지

Link2Me

,
728x90

C++ 에서 변수 앞에 const를 붙여 상수화가 되어, 그 값을 변경하지 못하게 한다.

변경하지 않아야 하는 변수에 엉뚱한 값을 할당하여, 에러가 발생할 경우 찾아내기가 쉽지 않다.

const 선언은 코드의 안정성을 높이기 위한 것이다.
https://www.youtube.com/watch?v=pFo84VEZCAM 에 const 동영상 강좌 설명이 참 잘 되어 있다.

C언어의 문법과 C++ 문법이 동일한 것은 C언어 강좌를 들어서 이해하는 것이 좋을 거 같다. 유튜브의 다양한 강좌를 들어보고 가장 마음에 들고 좋은 건 링크를 걸어서 적어둔다.


const 변수타입 변수명 = 초기화 값; // 선언시 초기화 값을 넣어야 된다. 값을 변경할 수 없다.


ex) const double PI = 3.14139;


const 변수타입 *변수명; // 변수명(포인터)이 가리키는 주소의 값을 변경할 수 없다.

#include <iostream>
using namespace std;
int main() {
    int* pta;
    int a = 10;
    pta = &a;
    *pta = 5;

    cout << "*pta : " << *pta << endl;
    cout << "a : " << a << endl;

    const int* ptb; // 변수명이 가리키는 주소의 값을 상수화 → 포인터가 가리키는 값을 상수화
    int b = 20;
    ptb = &b;
    //*ptb = 12; // 컴파일 에러. ptb가 가리키는 주소의 값이 상수화되어 변경할 수 없다.
    b = 30; // 값은 변경할 수 있다.


   cout << "*ptb :" << *ptb << endl;


    int c = 15;
    ptb = &c; // 포인터 변수에 다른 주소를 할당할 수는 있다.
    cout << "*ptb :" << *ptb << endl;
}
 


변수타입 * const 변수명 = 초기화값; // 포인터 변수를 상수화한다.

#include <iostream>
using namespace std;
int main() {
    int a;
    int* const pta = &a;
    int b;
    *pta = 10; // pta 가 가리키는 주소의 값을 변경할 수 있다.
    pta = &b; // 컴파일 에러. pta (포인터)가 가리키는 주소가 상수화되어 변경할 수 없다.
} 


const 변수타입 * const 변수명 = 초기화 값; // 변수명이 가리키는 주소와 그 주소의 값도 변경할 수 없다.

#include <iostream>
using namespace std;
int main() {

    int a;
    const int* const pta = &a;
    int b;
    a = 15; // 직접 값을 변경할 수 있다.
    *pta = 10; // 컴파일 에러. pta 가 가리키는 주소의 값이 상수화되어 변경할 수 없다.
    pta = &b; // 컴파일 에러. pta 가 가리키는 주소가 상수화되어 변경할 수 없다.

}



메소드의 상수화

#include <iostream>
using namespace std;

class Account {
private:
    int money; // 멤버 변수
public:
    Account() : money(0) {}
    Account(int money) : money(money) {}

    void Deposit(const int d) { // 매개변수 상수화
        money += d;
        cout << d << "원을 예금" << endl;
    }

    void Withdraw(const int d) { // 매개변수 상수화
        if (money >= d) {
            money -= d;
            cout << d << "원을 인출" << endl;
        }
    }

    int viewMoney() const { // 메소드의 상수화
        // money++; // 컴파일 에러. 멤버 변수 값을 변경할 수 없다.
        return money;
    }
};

int main() {

    Account account(1000);

    cout << "잔고 : " << account.viewMoney() << "원" <<endl << endl;

    account.Deposit(1000);

    cout << "잔고 : " << account.viewMoney() << "원" << endl << endl;

    account.Withdraw(500);

    cout << "잔고 : " << account.viewMoney() << "원" << endl << endl;
   
}
 


'C++ > C++ 문법' 카테고리의 다른 글

C++ stack 영역  (0) 2019.12.17
C++ STL Vector(벡터)  (0) 2019.12.01
C++ 포인터 이해  (0) 2019.11.30
C++ 클래스 상속  (0) 2019.11.29
C++ 이동 생성자(move constructor), 이동 대입 연산자(Move assignment operator)  (2) 2019.11.28
블로그 이미지

Link2Me

,
728x90

안드로이드 바인딩 서비스 개요

startService() 메소드 대신 bindService() 메소드를 통해 시작되는 서비스를 서비스 바인딩이라 한다.

바인딩 서비스는 Activity가 클라이언트 역할을 하고, 서비스가 서버 역할을 한다.

startService() 메소드 대신 bindService() 메소드를 통해 시작되는 서비스를 서비스 바인딩이라 한다.

이 서비스는 Activity가 클라이언트 역할을 하고, 서비스가 서버 역할을 한다.



출처: https://link2me.tistory.com/1343 [소소한 일상 및 업무TIP 다루기]

BindService는 startService()를 통해 시작되는 UnBound Service와는 다르게
Activity / Fragment와 서비스간에 데이터를 주고 받을 수 있으며, 프로세스간의 통신에도 사용된다.

백그라운드에서 무한히 실행되지 않도록 Activity 종료시 자동으로 서비스를 종료시킬 수 있도록 구현해야 한다.

서비스 바인딩은 연결된 Activity가 사라지면 서비스도 소멸된다.
하나의 서비스에 다수의 Activity 연결이 가능하다.
서비스에 연결된 Activity(컴포넌트)가 하나도 남아있지 않으면 서비스는 종료된다.

서비스 바인딩 및 구현 동작

연결을 유지하고 데이터를 전송 받기 위한 ServiceConnection() 객체와 IBinder 인터페이스 객체가 필요하다.

ServiceConnection() 는 Service를 호출하는 Activity 에 만들어야 하고, IBinder 는 Service 에서 생성한 후 리턴해야 한다.


startService()를 호출하여 서비스를 시작하고 이를 통해 서비스가 무한히 실행되도록 할 수 있으며, bindService()를 호출하면 클라이언트(Activity)가 해당 서비스에 바인딩되도록 할 수 있다는 의미다.


서비스가 시작되고 바인딩되도록 허용한 다음, 서비스가 실제로 시작되면 시스템은 Activity가 모두 바인딩을 해제해도 서비스를 소멸시키지 않는다. 그 대신 서비스를 직접 확실히 중단해야 한다. 그러려면 stopSelf() 또는 stopService()를 호출하면 된다.


보통은 onBind() 또는 onStartCommand() 중 한 가지만 구현하지만, 둘 모두 구현해야 할 때도 있다.
클라이언트는 bindService()를 호출하여 서비스에 바인딩된다.
이때 반드시 서비스와의 연결을 모니터링하는 ServiceConnection의 구현을 제공해야 한다.
Android 시스템이 클라이언트와 서비스 사이에 연결을 설정하면 ServiceConnection에서 onServiceConnected()을 호출한다.
onServiceConnected() 메서드에는 IBinder 인수가 포함되고 클라이언트는 이를 사용하여 바인딩된 서비스와 통신한다.
바인딩된 서비스를 구현할 때 가장 중요한 부분은 onBind() 콜백 메서드가 반환하는 인터페이스를 정의하는 것이다.

바인딩 서비스 예제

안드로이드 서비스 예제를 통해 개념을 익히고 활용할 수 있도록 예제를 테스트하고 적어둔다.

https://link2me.tistory.com/1343 에 기본 서비스 예제가 있으며, 비교하여 달라진 점을 확인해 볼 수 있다.

bindService를 추가하면 서비스 종료시에 반드시 unbindService(mConnection) 처리를 해줘야만 제대로 종료가 됨을 확인할 수 있다.


layout.xml

<?xml version="1.0" encoding="utf-8"?>
<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"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/btn_servicestart"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center_vertical"
        android:layout_gravity="center"
        android:text="서비스 시작"
        android:textSize="14sp"
        android:textAllCaps="false"
        android:layout_marginTop="30dp"/>

    <Button
        android:id="@+id/btn_servicestop"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="서비스 종료"
        android:layout_gravity="center"
        android:gravity="center"
        android:textSize="14sp"
        android:textAllCaps="false"
        android:layout_marginTop="20dp"/>

    <Button
        android:id="@+id/btn_bindservice"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="바인드 값"
        android:layout_gravity="center"
        android:gravity="center"
        android:textSize="14sp"
        android:textAllCaps="false"
        android:layout_marginTop="20dp"/>

</LinearLayout>



MyService.java

import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;

public class MyService extends Service {
    private static final String TAG = MyService.class.getSimpleName();
    private Context context;
    private int mCount = 0;
    private Thread mThread;
    public static boolean SERVICE_CONNECTED = false;

    public MyService() {
    }

    private IBinder mBinder = new MyBinder();
    public class MyBinder extends Binder {
        public MyService getService(){
            Log.e(TAG, "MyService MyBinder return.");
            return MyService.this;  // 서비스 객체를 리턴
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        Log.e(TAG, "MyService IBinder onBind");
        // Service 객체와 (화면단 Activity 사이에서) 데이터를 주고받을 때 사용하는 메서드
        // Activity에서 bindService() 를 실행하면 호출됨
        // 데이터를 전달할 필요가 없으면 return null;
        return mBinder; // 리턴한 mBinder 객체는 서비스와 클라이언트 사이의 인터페이스를 정의한다.
    }

    public int getCount(){
        return mCount;
    }

    @Override
    public void onCreate() {
        Log.e(TAG, "MyService Started");
        this.context = this;
        MyService.SERVICE_CONNECTED = true;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.e(TAG, "MyService onStartCommand startID === "+startId); // 계속 증가되는 값
        if(mThread == null){
            mThread = new Thread("My Thread"){
                @Override
                public void run() {
                    while (!Thread.currentThread().isInterrupted()){
                        try {
                            mCount++;
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                            this.interrupt();
                        }
                        Log.e("My Thread", "서비스 동작 중 " + mCount);
                    }
                }
            };
            mThread.start();
        }
        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        Log.e(TAG,"MyService onDestroy");
        super.onDestroy();
        if(mThread != null && mThread.isAlive() ){
            mThread.interrupt();
            mThread = null;
            mCount = 0;
        }
        SERVICE_CONNECTED = false;
    }
}


MainActivity.java

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

import java.util.Set;

public class MainActivity extends AppCompatActivity {
    private static final String TAG = MainActivity.class.getSimpleName();
    Context mContext;
    private Button btnServiceStart;
    private Button btnServiceStop;
    private Button btnBindService;

    private MyService myService;
    private boolean mBound = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mContext = MainActivity.this;

        btnServiceStart = findViewById(R.id.btn_servicestart);
        btnServiceStop = findViewById(R.id.btn_servicestop);
        btnBindService = findViewById(R.id.btn_bindservice);

        btnServiceStart.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(mContext, "서비스를 시작합니다.", Toast.LENGTH_SHORT).show();
                Intent intent = new Intent(mContext,MyService.class);
                startService(intent);
            }
        });

        btnServiceStop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(mContext, "서비스를 종료합니다.", Toast.LENGTH_SHORT).show();
                Log.e(TAG,"MainActivity ServiceStop Button Clicked.");
                if(mBound){
                    unbindService(mConnection);
                    mBound = false;
                }
                stopService(new Intent(mContext,MyService.class));
            }
        });

        btnBindService.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(mBound){
                    Toast.makeText(mContext, "카운팅 : " + myService.getCount(), Toast.LENGTH_SHORT).show();
                }
            }
        });
    }

    @Override
    protected void onDestroy() {
        Log.e(TAG,"MainActivity onDestroy");
        if(mBound){
            unbindService(mConnection);
            mBound = false;
        }
        stopService(new Intent(this,MyService.class));
        super.onDestroy();
    }

    @Override
    protected void onStart() {
        super.onStart();
        Intent intent = new Intent(this,MyService.class);
        bindService(intent,mConnection,BIND_AUTO_CREATE); //
startService 버튼 클릭시 시작
//        startService(MyService.class, mConnection,null); // Activity 시작시
startService 자동 실행
    }

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            MyService.MyBinder binder = (MyService.MyBinder) service;
            myService = binder.getService();
            mBound = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            // 예기치 않은 종료(안드로이드 OS에 의한 종료)

            mBound = false;
        }
    };

    private void startService(Class<?> service, ServiceConnection serviceConnection, Bundle extras) {
        // onStart 메소드에서 서비스 자동 실행 처리할 목적
        if (!MyService.SERVICE_CONNECTED) {
            Intent startService = new Intent(this, service);
            if (extras != null && !extras.isEmpty()) {
                Set<String> keys = extras.keySet();
                for (String key : keys) {
                    String extra = extras.getString(key);
                    startService.putExtra(key, extra);
                }
            }
            startService(startService);
        }
        Intent bindingIntent = new Intent(this, service);
        bindService(bindingIntent, serviceConnection, Context.BIND_AUTO_CREATE);
    }
}


Log 를 통해서 동작 순서에 대한 이해를 할 수 있다.

Activity가 실행되면서 자동으로 서비스를 실행하도록 한 경우의 Log 메시지다.


블로그 이미지

Link2Me

,
728x90

Interface 처리에 대한 사항을 정리차원에서 실 사용 예제에서 발췌하여 적어둔다.

3번 객체에 해당하는 부분은 여러 Class 에서 하나의 개발코드에 접근할 수 있도록 인터페이스 상속 처리를 했다.


1. 인터페이스 선언

public interface ISerialListener {
    void onReceive(int msg, int arg0, int arg1, String arg2, Object arg3);
}



2. SerialConnector 코드 발췌

public class SerialConnector {
    private Context mContext;
    private ISerialListener mListener; // 인터페이스 처리
    private Handler mHandler;
    private SerialMonitorThread mSerialThread;

    public SerialConnector(Context context, ISerialListener listener, Handler handler) {
        mContext = context;
        mListener = listener;
        mHandler = handler;
    }

    public void initialize() {
        List<UsbSerialDriver> availableDrivers = UsbSerialProber.getDefaultProber().findAllDrivers(sUsbManager);
        if (availableDrivers.isEmpty()) {
            mListener.onReceive(Constants.MSG_SERIAL_ERROR, 0, 0, "Error: There is no available device. \n", null);
            return;
        }

        sDriver = availableDrivers.get(0);
        if(sDriver == null) {
            mListener.onReceive(Constants.MSG_SERIAL_ERROR, 0, 0, "Error: Driver is Null \n", null);
            return;
        }

        try {
            sPort.open(mConnection);
            if(BaudRate.isEmpty()){ // 통신 속도 설정값 가져와서 세팅
                sPort.setParameters(9600, 8, 1, 0);  // baudrate:9600, dataBits:8, stopBits:1, parity:N
            } else {
                sPort.setParameters(Integer.parseInt(BaudRate), Integer.parseInt(DataBit), Integer.parseInt(StopBit), Integer.parseInt(Parity));
            }
        } catch (IOException e) {
            mListener.onReceive(Constants.MSG_SERIAL_ERROR, 0, 0, "Error: Cannot open port \n" + e.toString() + "\n", null);
        } finally {
        }

        startThread();
    }

    public void finalize() {
        try {
            sDriver = null;
            stopThread();

            sPort.close();
            sPort = null;
        } catch(Exception ex) {
            mListener.onReceive(Constants.MSG_SERIAL_ERROR, 0, 0, "Error: Cannot finalize serial connector \n" + ex.toString() + "\n", null);
        }
    }

    /*****************************************************
     *    private methods
     ******************************************************/
    // start thread
    private void startThread() {
        mListener.onReceive(Constants.MSG_SERIAL_ERROR, 0, 0, "Start serial monitoring thread \n", null);
        if(mSerialThread == null) {
            mSerialThread = new SerialMonitorThread();
            mSerialThread.start();
        }
    }
    // stop thread
    private void stopThread() {
        if(mSerialThread != null && mSerialThread.isAlive())
            mSerialThread.interrupt();
        if(mSerialThread != null) {
            mSerialThread = null;
        }
    }

    /*****************************************************
     *    Sub classes, Handler, Listener
     ******************************************************/
    public class SerialMonitorThread extends Thread {
        @Override
        public void run() {
            byte readBuffer[] = new byte[4096];

            while(!Thread.interrupted()) {
                if(sPort != null) {
                    Arrays.fill(readBuffer, (byte)0x00);

                    try {
                        // Read and Display to Terminal
                        int numBytesRead = sPort.read(readBuffer, 1000);
                        if(numBytesRead > 0) {

                            // Print message length
                            Message msg = mHandler.obtainMessage(Constants.MSG_READ_DATA_COUNT, numBytesRead, 0, new String(readBuffer));
                            mHandler.sendMessage(msg);

                        } // End of if(numBytesRead > 0)
                    } catch (IOException e) {
                        Message msg = mHandler.obtainMessage(Constants.MSG_SERIAL_ERROR, 0, 0, "Error # run: " + e.toString() + "\n");
                        mHandler.sendMessage(msg);
                    }
                }

                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    break;
                }

            }    // End of while() loop
            finalizeThread();
        }    // End of run()
    }    // End of SerialMonitorThread

}


3. 객체 구현

핵심사항만 발췌하여 유사한 코드 구현시 활용 차원으로 적어둔다.

public class AAA extends AppCompatActivity {
    private static final String TAG = "AAA";
    Context mContext;

    private ActivityHandler mHandler = null;
    private SerialListener mSerialListener = null;
    private SerialConnector mSerialConnector = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_tam_ss);
        mContext = AAA.this;
        initView();
    }

    private void initView() {
        connectUsb();
    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        connectUsb();
    }

    @Override
    protected void onDestroy() {
        releaseUsb();
        super.onDestroy();
    }

    private void connectUsb() {
        searchEndPoint();
        if (usbInterfaceFound != null) {
            setupUsbComm();
        }
    }

    private void releaseUsb() {
        textStatus.setText("releaseUsb()");
        mSerialConnector.finalize();
    }

    private boolean setupUsbComm() {
        boolean success = false;

        UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
        Boolean permitToRead = manager.hasPermission(deviceFound);

        if (permitToRead) {
            usbDeviceConnection = manager.openDevice(deviceFound);
            if (usbDeviceConnection != null) {
                // Initialize
                mSerialListener = new SerialListener();
                mHandler = new ActivityHandler();

                // Initialize Serial connector and starts Serial monitoring thread.
                mSerialConnector = new SerialConnector(mContext, mSerialListener, mHandler);
                mSerialConnector.initialize();
            }
        } else {
            manager.requestPermission(deviceFound, mPermissionIntent);
            textStatus.setText("Permission: " + permitToRead);
        }
        return success;
    }

    public class SerialListener implements ISerialListener {
        public void onReceive(int msg, int arg0, int arg1, String arg2, Object arg3) {
            switch(msg) {
                case Constants.MSG_DEVICD_INFO:
                    updateReceivedData(arg2);
                    break;
                case Constants.MSG_DEVICE_COUNT:
                    updateReceivedData(Integer.toString(arg0) + " device(s) found \n");
                    break;
                case Constants.MSG_READ_DATA_COUNT:
                    updateReceivedData(Integer.toString(arg0) + " buffer received \n");
                    break;
                case Constants.MSG_READ_DATA:
                    if(arg3 != null) {
                        updateReceivedData((String)arg3);
                    }
                    break;
                case Constants.MSG_SERIAL_ERROR:
                    updateReceivedData(arg2);
                    break;
                case Constants.MSG_FATAL_ERROR_FINISH_APP:
                    finish();
                    break;
            }
        }
    }

    public class ActivityHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch(msg.what) {
                case Constants.MSG_DEVICD_INFO:
                    updateReceivedData((String)msg.obj);
                    break;
                case Constants.MSG_DEVICE_COUNT:
                    updateReceivedData(Integer.toString(msg.arg1) + " device(s) found \n");
                    break;
                case Constants.MSG_READ_DATA_COUNT:
                    updateReceivedData(((String)msg.obj));
                    break;
                case Constants.MSG_READ_DATA:
                    if(msg.obj != null) {
                        updateReceivedData((String)msg.obj);
                    }
                    break;
                case Constants.MSG_INPUT_READ:
                    updateReceivedData((String)msg.obj);
                    mDumpTextView.setTextColor(Color.BLUE);
                    break;
                case Constants.MSG_SERIAL_ERROR:
                    updateReceivedData((String)msg.obj);
                    break;
            }
        }
    }
}


'안드로이드 > Interface' 카테고리의 다른 글

Java Interface 예제  (0) 2019.11.18
Android Interface AsyncTask 예제  (0) 2019.11.05
Java Interface Example  (0) 2019.09.05
Java 인터페이스(interface) 개요  (0) 2019.08.20
Android Interface 예제 ★★★  (0) 2018.08.22
블로그 이미지

Link2Me

,
728x90
전세계적으로 개발자가 자바 언어만큼 많이 사용하는 또다른 프로그램 개발 언어는 C언어와 C++언이이다.
C/C++언어는 자바와 달리 특정 플랫폼에 최적화시킬 수 있는 장점을 제공한다.
java만 사용하여 필요 기능과 성능을 모두 만족시키기는 힘들다. 안드로이드는 자바 기반의 언어다. 자바는 JVM(Java Virtual Machine)에서 동작하기 때문에 실제적인 디바이스에 접근이 불가능하다. 그래서 나온 것이 안드로이드 NDK이다.
NDK을 사용하여 디바이스에 접근 제어할 수 있는 C/C++ 함수들을 호출할 수 있도록 하여 안드로이드 개발자들이 좀 더 효율성 있게 개발할 수 있도록 도와주는 역할을 한다.

C, C++ 로 작성된 프로그램을 안드로이드에서 사용할 수 있도록 JDK에서 제공하는 것이 JNI(Java Native Interface) 이다.

Java에는 상응하는 것이 없는 수많은 C/C++ 라이브러리가 있다. 이러한 라이브러리를 이용하려면 NDK를 사용하는 것이 좋다.

안드로이드는 C/C++언어로 개발된 프로그램을 Java와 구분하기 위해 네이티브 코드라고 하며, 네이티브 코드로 작성되어 실행이 가능한 메소드를 일반 Java 메소드와 구분하여 네이티브 함수라 부른다.


안드로이드 스튜디오에서 C++ 코드를 import 하여 사용하기 위해 필요한 설정이다.

화면 캡처는 맥북에서 했다. 맥 윈도우 8.1 에서 CMake 인식이 안되어서 맥북 모드로 다시 확인하니까 맥북에서는 잘 인식되어 installed 했다.

최신 알파버전 Android Studio 4.0  을 다른 폴더에 설치하고 실행해서 보니 안보이던 cmake 선택창이 보여서 설치했더니, 이제는 Android Studio 3.5.2 에서도 잘 보인다. 흐미....



https://developer.android.com/ndk/guides?hl=ko 에 설명이 잘 되어 있다.

NDK(Native Development Kit)는 Android에서 C 및 C++ 코드를 사용할 수 있게 해주는 일련의 도구 모음으로, 네이티브 액티비티를 관리하고 센서 및 터치 입력과 같은 물리적 기기 구성요소에 액세스하는 데 사용할 수 있는 플랫폼 라이브러리를 제공한다.

- 기기에서 최대한의 성능을 도출하여 짧은 지연 시간을 달성해야 하는 경우

- 게임 또는 물리학 시뮬레이션과 같은 연산 집약적인 애플리케이션을 실행하는 경우

- 본인 또는 다른 개발자의 C 또는 C++ 라이브러리를 재사용하는 경우


ㅇCMake: Gradle과 함께 작동하여 네이티브 라이브러리를 빌드하는 외부 빌드 도구다.

   (Android Studio’s default build tool for native libraries is CMake.)

   ndk-build만 사용하려는 경우에는 이 구성요소가 필요하지 않다.

  

ㅇLLDB: Android 스튜디오에서 네이티브 코드를 디버깅하는 데 사용하는 디버거다.



C++ 코드 사용 가능한 프로젝트 생성




설치된 폴더를 확인해보면, NDKTest 라는 프로젝트명이 생겼다.


소스 폴더에 java, res 폴더 외에 cpp 라는 폴더가 추가로 생긴 걸 확인할 수 있다.


cpp 폴더안에 native-lib.cpp 파일과 CMakeLists.txt 파일 기본 생성되었다.


모든 C++ 파일과 라이브러리는 cpp 폴더 안에 있어야 한다.


cpp 파일 작성법

default 로 생성된 파일을 분석해보자.

#include <jni.h>
#include <string>

extern "C" JNIEXPORT jstring JNICALL
Java_com_link2me_android_ndktest_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}


#include <jni.h> // 다수의 매크로 정의, 타입, 구조체, 함수를 포함하는 헤더 파일로 반드시 포함해야 한다.

JNI(Java Native Interface)를 통해 접근할 수 있는 C++ 함수명은 다음과 같은 형태여야 한다.

'JNIEXPORT <함수 타입> JNICALL Java_<패키지 명>_<클래스 명>_<함수명>(JNIEnv*, jobject, 파라미터) 이다.

Java_com_link2me_android_ndktest_MainActivity_stringFromJNI(JNIEnv *env, jobject) {

Java는 접두어이고,

package="com.link2me.android.ndktest" 패키지명에서 . 대신에 _ 로 바꾼다.

자바 클래스명 : MainActivity

함수명 : stringFromJNI

JNIEnv*, jobject는 네이티브에서 자바로 접근하는 포인트가 된다.


extern "C" JNIEXPORT jstring JNICALL
Java_com_link2me_android_ndktest_MainActivity_intFromJNI(
        JNIEnv *env, jobject b, jint a, jint b) {
    // 연산결과 = 연산식
    return env->NewStringUTF(연산결과);
}


public native int sum(int a, int b);

매개변수가 추가된 경우에는 네이티브 코드에 자주색처럼 추가해주면 된다.



MainActivity.java 파일 추가 사항을 살펴보자.

public class MainActivity extends AppCompatActivity {

    // 네이티브 함수를 사용하기 위하여 동적 라이브러리를 로딩
    // 동적라이브러리는 stringFromJNI메서드가 호출되기 전에 로딩되어야 하므로 static으로 초기화
    static {
        System.loadLibrary("native-lib"); // 라이브러리명은 소문자를 사용해야 한다.
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Example of a call to a native method
        TextView tv = findViewById(R.id.sample_text);
        tv.setText(stringFromJNI());
    }

    /**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */
    // java에서 사용할 네이티브 함수의 선언
    // native라는 키워드는 이 메서드가 java가 아닌 다른 언어로 작성된 것임을 암시
    public native String stringFromJNI();
}

자바 프로그램내 선언한 메소드에 native 라는 키워드를 추가하지 않는다면, 자바 컴파일러는 메소드들이 모두 자바 언어로 작성되어 있다고 가정한다.

native 선언은 메소드는 자바 언어가 아닌 C나 C++ 언어로 작성되어 있다는 사실을 알려준다.

자바 프로그램의 특성상 메소드의 선언은 C/C++ 언어와 달리 클래스의 어디에 위치해도 좋다.

네이티브 메소드는 커널에 의해 실행되기 때문에 자바 가상 머신에서 관리하지 않는다.

자바를 실행시키는 가상머신은 기본적으로 모든 문자열을 UTF-16 이라는 유니코드(Unicode)로 처리한다.


앱 build.gradle

cpp가 포함되지 않은 앱 build.gradle 에 비해 자주색으로 표시된 부분이 추가되어 있다.

apply plugin: 'com.android.application'

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.2"
    defaultConfig {
        applicationId "com.link2me.android.ndktest"
        minSdkVersion 21
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"
        externalNativeBuild {
            cmake {
                cppFlags ""
            }
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    externalNativeBuild {
        cmake {
            path "src/main/cpp/CMakeLists.txt"
            version "3.10.2"
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
}


참고자료

https://programmingfbf7290.tistory.com/entry/NDK2-%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%EC%8A%A4%ED%8A%9C%EB%94%94%EC%98%A4-NDK-JNI-%EA%B8%B0%EB%B3%B8-%EC%98%88%EC%A0%9C?category=666988


참고하면 좋은 책 : 안드로이드 C-C++ 프로그래밍

블로그 이미지

Link2Me

,
728x90

인코딩 문제인지 파이썬에서 개발했다고 하는 ANSI escape sequences 코드 적용된 장비 접속에서 회신이 제대로 오지 않는 문제로 골머리가 아프다.

PC/노트북용 Putty 에서는 정상적으로 처리되는데 Android 앱에서 구현한 코드로는 특정 명령어는 100% 회신이 오는데, 특정 명령어는 50% 성공율이 발생한다. 문제 해결을 위해서 C++ 유투브 강좌도 열심히 들어보고 있다.

Android NDK 를 이용한 방법으로 해결할 수 있지 않을까 생각과 더불어 NDK 처리 방법을 배우면 유용할 거 같아서다.

아직 문제 해결이 안된 상태인데 인코딩 문제인가 싶어서 관련 내용을 찾아보고 정리해둔다.


인코딩(encoding)은 문자셋을 컴퓨터가 이해할 수 있는 바이트와 매핑하는 규칙이다.
UTF-8은 모든 유니 코드 문자를 나타낼 수있는 멀티 바이트 인코딩이다.
ISO 8859-1은 첫 번째 256 유니 코드 문자를 나타낼 수있는 단일 바이트 인코딩이다.

ASCII(American Standard Code for Information Interchange)는 7비트로 엄격하게 제한되었으며, 표현 가능한 7개의 이진수로 비트 패턴을 사용함을 의미하는데, 10진수로는 0부터 127까지의 범위를 제공한다.
여기에는 32개의 보이지 않는 제어 문자가 있는데, 0부터 31까지의 범위에 있으며, 마지막 제어 문자는 DEL또는 delete이며, 이 제어문자는 127번에 할당되어 있다.
32번 부터 126번 까지는 공백 문자, 문장 부호, 라틴 문자, 숫자와 같은 눈에 보이는 문자로 구성되어 있다.
ASCII의 8번째 비트는 본디 오류 검출을 위한 패리티 비트로 활용했다.
오류 검사를 고려하지 않으면 0으로 그대로 둔다. ASCII에서 각 문자를 단일 바이트로 표현했음을 의미한다.
ㅇ 7 bit 코드 : 0x00 ~ 0x7F 까지 128개
ㅇ 첫 1bit 는 패러티 비트 : 에러 검출 목적

강세부호가 들어간 문자가 있는 다른 유럽어권의 문자를 ASCII 로 의사소통할 수 없어 ISO 8859 표준을 개발했다.
현재 ISO 8859 표준의 15가지 변형 표준이 있다.
ㅇ 8bit 코드 : 0x00 ~ 0xFF 까지 256개
ㅇ ASCII 에서 쓸 수 없는 문자들까지 포함
ㅇ 언어권에 따라 여러가지 변형
ㅇ 서유럽용 IOS-8859-1 을 가장 많이 씀


UTF-8

ㅇ 유니코드 인코딩 방식중 하나

ㅇ 영어는 1byte, 한글은 3byte


https://developer.android.com/training/articles/perf-jni#java 에 "자바 프로그래밍 언어는 UTF-16을 사용한다." 고 나온다. UTF-8 이 아니고???


The ANSI character set, also known as Windows-1252, has become a Microsoft proprietary character set;
it is a superset of ISO-8859-1 with the addition of 27 characters in locations that ISO designates for control codes.

ASCII 가  1byte 인코딩 방식이라고 하면, UTF-8 은 멀티방식 인코딩이지만 영문은 1byte 이므로 동일할 거 같고, ISO-8859-1 인코딩 방식이랑 차이가 없을거 같은 생각이 드는데 맞나??


아무튼 문제 해결을 위한 지식과 코드를 분석해보면서 해결책을 찾아야겠다.


Update : 2019.12.20

해결 결과 : 인코딩 문제가 아니라 흐름제어(Flow Control) 문제였다.


블로그 이미지

Link2Me

,
728x90

안드로이드 폰에서 SSH 어플을 사용하고자 한다면, 검색해보면 가장 추천하는 앱은 JuiceSSH 이고, open source project로 진행되는 ConnectBot 이 있다는 걸 알았다.  좀 더 실력이 키워야 분석 및 활용이 가능할 것 같다.


Java 언어로 개발된 SSH2 라이브러리는 JSCH(Java Secure Channel) 를 확인할 수 있다.

구글링하면 예제 많이 나오는데 제대로 동작되는 걸 해보려면 쉽지 않더라.

http://www.jcraft.com/jsch/ 에 가면 jsch-0.1.55.jar 파일을 다운로드할 수 있다.


https://github.com/jonghough/AndroidSSH 에서 소스 코드를 다운로드 받아서 어플을 실행해 볼 수 있다.


테스트 결과 LG G5 안드로이드 6.0 운영체제에서는 잘 동작을 했다.

그런데 삼성 갤럭시 S7 안드로이드 8.0, 삼성 갤럭시 S10 안드로이드 9.0 운영체제 폰에서는 동작이 제대로 안되더라.

코드가 오래전에 올려진 것이라서 위험권한을 추가하고 동작시켜도 동작이 안된다.

쓰레드 처리에 대한 학습을 좀 하고 나서 몇가지 사항을 수정해서 동작되도록 했다.


앱 build.gradle

android {
    compileSdkVersion 28


   // 중간 생략


dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])

    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    implementation 'gun0912.ted:tedpermission:2.0.0'
    implementation 'androidx.multidex:multidex:2.0.0' // minSdkVersion 이 21 이하인 경우 사용
    implementation 'com.jcraft:jsch:0.1.55'
    implementation 'org.bouncycastle:bcprov-jdk16:1.46'
}


MainActivity.java 수정사항

- 위험권한 설정 처리코드 추가 구현 필요


ShellController.java 수정사항

    public void writeToOutput(final String command) {
        if (mDataOutputStream != null) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        mDataOutputStream.writeBytes(command + "\r\n");
                        mDataOutputStream.flush();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();

        }
    }


   public void openShell(final Session session, Handler handler, EditText editText) throws JSchException, IOException {
        Log.e(TAG, "openShell start!");
        if (session == null) throw new NullPointerException("Session cannot be null!");
        if (!session.isConnected()) throw new IllegalStateException("Session must be connected.");
        final Handler myHandler = handler;
        final EditText myEditText = editText;
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    mChannel = session.openChannel("shell");
                    mChannel.connect();
                    try {
                        mBufferedReader = new BufferedReader(new InputStreamReader(mChannel.getInputStream()));
                        mDataOutputStream = new DataOutputStream(mChannel.getOutputStream());
                    } catch (IOException e) {
                        e.printStackTrace();
                    }

                } catch (JSchException e) {
                    e.printStackTrace();
                }

                try {
                    String line;
                    while (true) {
                        while ((line = mBufferedReader.readLine()) != null) {
                            final String result = line;
                            if (mSshText == null) mSshText = result;
                            Log.e(TAG, "result : " + result);
                            myHandler.post(new Runnable() {
                                public void run() {
                                    synchronized (myEditText) {
                                        ((SshEditText)myEditText).setPrompt(result); //set the prompt to be the current line, so eventually it will be the last line.
                                        myEditText.setText(myEditText.getText().toString() + "\r\n" + result + "\r\n"+fetchPrompt(result));
                                        Log.e(TAG, "LINE : " + result);
                                    }
                                }
                            });
                        }

                    }
                } catch (Exception e) {
                    Log.e(TAG, " Exception " + e.getMessage() + "." + e.getCause() + "," + e.getClass().toString());
                }
            }
        }).start();
    }
 


내가 테스트한 GitHub 소스는 칼라 코드 지원은 안된다.

SFTP 기능 구현은 테스트 하지 않았다.



참고하면 도움될 게시글
싱글톤(Singleton) 개념 이해 : https://link2me.tistory.com/1528
Java Thread 이해 및 Thread Life Cycle : https://link2me.tistory.com/1730
Java Thread 상태 제어 : https://link2me.tistory.com/1731
Java Thread 동기화 : https://link2me.tistory.com/1732


블로그 이미지

Link2Me

,
728x90

표준 템플릿 라이브러리라고 불리는 STL은 자료와 알고리즘을 효율적으로 관리할 수 있도록 C++에서 제공하는 표준 라이브러리다.

컨테이너(container)는 어떤 종류의 객체 컬렉션(collection)을 관리하는데 사용된다.


순차 컨테이너, sequence containers
순차 컨테이너는 모든 요소들을 선형적인 순차열로 저장하는 형태다.
원소들이 규칙에 따라 저장한 순서대로 배열이 된다.
벡터(vector), 데큐(deque), 리스트(list)가 있다.

연관 컨테이너, associative containers
연관 컨테이너는 키(key)와 값(value) 쌍처럼 관련된(연관된) 데이터를 하나의 쌍 형태로 저장한다.
키와 값을 이용하여 요소들에 대한 빠른 접근을 제공한다.
세트(set), 멀티세트(multiset), 맵(map), 멀티맵(multimap) 가 있다.

Vector

ㅇ C++에서 벡터는 배열의 기능을 확장한 것이다.
ㅇ 배열은 동적할당을 하면 그 크기를 바꿀 수 없지만, 벡터는 크기를 자유롭게 변경할 수 있다.
ㅇ Vector의 특성

    - 연속적인 공간에서 원소를 저장/관리

    - 단방향 원소 저장을 위한 container

    - push_back(element) : 벡터 요소를 마지막에 추가

    - pop_back(element) : 벡터의 마지막 요소를 삭제

    - size() : 벡터 내의 요소수를 반환

    - capacity() : 할당된 공간 크기를 리턴
    - at(index) : 지정 인덱스에 있는 요소 반환

    - empty() : 벡터가 비어 있으면 true 반환

    - clear() : 벡터의 모든 요소 삭제

    - container 에 보관된 원소에 접근할 때 반복자(iterator)를 사용한다.

      ▶ begin() : 첫번째 원소를 가리킨다.

      ▶ end() : 마지막 원소를 가리킨다.

      ▶ erase(iterator) : iterator가 가리키는 원소 제거


ㅇ vector의 선언

    - #include <vector> 로 헤더를 포함해야 한다.

    - std::vector<자료형> 이름; // using namespace std;를 사용하시면 std:: 생략이 가능

      vector<int> iv(10) : 0으로 초기화 된 10개의 원소를 가지는 vector iv를 생성

      vector<int> iv(10, 1) : 1로 초기화된 10개의 원소를 가지는 vector iv를 생성


예제1

#include <iostream>
#include <string>
#include <vector>

using namespace std;

int main() {
    vector<int> iv; // int 타입 벡터 선언 및 비어있는 벡터 생성

    // 벡터 데이터 생성
    for (int i = 0; i < 10; i++) {
        iv.push_back(i+1);
    }

    // for문을 이용한 데이터 출력
    for (int i = 0; i < iv.size(); i++) {
        cout << iv[i] << " ";
    }
    cout << endl;

    cout << "size : " << iv.size() << endl;

    iv.push_back(15); // 벡터 요소 추가
    iv.push_back(16); // 벡터 요소 추가

    // 반복자를 활용한 데이터 출력
    vector<int>::iterator iter;
    for (iter = iv.begin(); iter != iv.end(); iter++) {
        cout << *iter << " ";
    }
    cout << endl;

    cout << "size : " << iv.size() << endl;

    iv.pop_back(); // 벡터 마지막 요소 삭제

    // 반복자를 활용한 데이터 출력
    for (iter = iv.begin(); iter != iv.end(); iter++) {
        cout << *iter << " ";
    }
    cout << endl;

    return 0;
}

실행결과


블로그 이미지

Link2Me

,