'2017/11/19'에 해당되는 글 1건

728x90

Android Studio 3.0 에서 PHP PDO(PHP Data Object) 기반 함수와 연동하여 회원가입 및 로그인처리하는 예제를 구현하고 입문자를 위해서 상세한 설명과 더불어 일부 코드를 첨부한다.

 

1. Android Studio New Module 추가하는 방법

더보기

아래 그림 순서대로 진행하면 된다.

 

 

 

 

 

앱 build.gradle 을 수정할 준비를 한다.

 

 

2. 앱 build.gradle 수정사항

더보기

 apply plugin: 'com.android.application'

android {
    compileSdkVersion 23

    defaultConfig {
        applicationId "com.tistory.link2me.pdologin"
        minSdkVersion 16
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

    }

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

}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:23.4.0'
    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
    compile 'com.android.support:recyclerview-v7:24.2.0' // ListView 개선 버전
    compile "com.android.support:cardview-v7:24.2.0"
    compile 'com.squareup.okhttp3:okhttp:3.9.0'
    compile 'com.squareup.picasso:picasso:2.5.2' // 이미지 라이브러리
    compile 'com.google.code.gson:gson:2.8.2'   
}

 

처음에 세팅된 값은 compileSdkVersion 26, targetSdkVersion 26 으로 되어 있는걸 23으로 낮추면 에러메시지가 몇개 나온다.

이때 수정해줘야 할 사항은

AndroidManifest.xml 파일에서 roundicon 나오는 행을 삭제한다.

그리고 res 폴더에서 아래 그림과 같은 부분만 남기도 나머지 폴더는 과감하게 삭제해버린다.

 

위와 같이 하면 코드를 작성할 준비는 완료된 것이라고 봐도 된다.

compileSdkVersion 26, targetSdkVersion 26 그대로 두고 코드를 작성해도 된다.

추가하는 라이브러리를 하나도 사용하지 않고 코드를 작성해보려고 한다.

 

컴파일을 하다보니 에러가 발생하면서 컴파일이 안된다.

Error:Execution failed for task ':pdologin:transformDexArchiveWithExternalLibsDexMergerForDebug'.
> java.lang.RuntimeException: com.android.builder.dexing.DexArchiveMergerException: Unable to merge dex

그래서 implementation 하는 라이브러리 버전이 일치되지 않아서 생기는 문제인거 같아서 수정했더니 정상적으로 컴파일이 된다.

compileSdkVersion 24, targetSdkVersion 24 로 버전 일치 시킴

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:24.2.0'
    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
    implementation 'com.android.support:recyclerview-v7:24.2.0' // ListView 개선 버전
    implementation "com.android.support:cardview-v7:24.2.0"
    implementation 'com.squareup.okhttp3:okhttp:3.9.0'
    implementation 'com.squareup.picasso:picasso:2.5.2' // 이미지 라이브러리
    implementation 'com.google.code.gson:gson:2.8.2'
} 

 

 

3. 로그인 과정 설계

- 아이디, 패스워드를 입력하고 자동로그인을 체크하면 로그인이 처리되고 MainActivity 로 넘어간다.

- 자동로그인 체크를 해두면 아이디, 패스워드를 묻지 않고 자동으로 로그인되어 MainActivity 로 넘어간다.

 

<여기서 고려할 사항>

자동로그인 체크를 하면 로그인창 화면이 표시되지 않아야 한다.

즉, 앱을 실행하자마자 바로 MainActivity 로 넘어가도록 처리하려면 별도의 Activity가 하나 필요하다는 의미다.

 

 

4. Activtiy 추가과정

더보기

Intro Activity 추가

 

위 그림에서 3번은 생략하면 된다.

테스트삼아 선택을 해봤는데 필요하지 않은 기능이다.

 

Login Activity 추가

위 그림과 같은 과정으로 추가를 한다.

 

 

 

이제 AndroidManifest.xml 에서 실행순서를 로그인 처리과정 순서를 고려하여 변경해야 한다.

 <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.tistory.link2me.pdologin">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.CALL_PHONE" />
    <uses-permission android:name="android.permission.READ_CONTACTS" />
    <uses-permission android:name="android.permission.WRITE_CONTACTS" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

 

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".Intro">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".Login"></activity>
        <activity android:name=".MainActivity"></activity>
    </application>

</manifest>

 

5. Layout 구성

Layout 파일은 https://www.androidhive.info/2012/01/android-login-and-registration-with-php-mysql-and-sqlite 에서 받은 소스파일을 활용하여 필요한 사항을 수정하여 작성할 것이다.

activity_login.xml 파일 내용을 수정하고 나니 미리보기 화면이 android...actionbaroverlaylayout 라고 표시되며 보이지가 않는다.

검색해 보니 res/values/styles.xml 내용중에서 parent 부분을 아래 것으로 교체하면 정상적으로 보인다.

<style name="AppTheme" parent="Base.Theme.AppCompat.Light.DarkActionBar">
</style>

 

activity_login.xml

더보기

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/bg_login"
    android:gravity="center"
    android:orientation="vertical"
    android:padding="10dp" >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:orientation="vertical"
        android:paddingLeft="20dp"
        android:paddingRight="20dp" >

        <EditText
            android:id="@+id/login_id_edit"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="10dp"
            android:background="@color/white"
            android:hint="@string/hint_email"
            android:inputType="textEmailAddress"
            android:padding="10dp"
            android:singleLine="true"
            android:textColor="@color/input_login"
            android:textColorHint="@color/input_login_hint" />

        <EditText
            android:id="@+id/login_pw_edit"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="10dp"
            android:background="@color/white"
            android:hint="@string/hint_password"
            android:inputType="textPassword"
            android:padding="10dp"
            android:singleLine="true"
            android:textColor="@color/input_login"
            android:textColorHint="@color/input_login_hint" />

        <Button
            android:id="@+id/login_btn"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dip"
            android:background="@color/btn_login_bg"
            android:text="@string/btn_login"
            android:textColor="@color/btn_login" />

        <CheckBox
            android:id="@+id/autologin"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginTop="5dip"
            android:text="자동 로그인"
            android:textColor="#000000"
            android:textSize="14sp"
            android:textStyle="bold" />

        <!-- Link to Login Screen -->
        <Button
            android:id="@+id/btnRegisterScreen"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="30dip"
            android:background="@null"
            android:text="@string/btn_link_to_register"
            android:textAllCaps="false"
            android:textColor="@color/white"
            android:textSize="15dp" />
    </LinearLayout>

</LinearLayout>

 

6. Java 코드 작성

다른 코드는 생략하고 Login.java 코드만 적는다.

더보기

package com.tistory.link2me.pdologin;

import android.Manifest;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.telephony.TelephonyManager;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.Toast;

import com.tistory.link2me.common.BackPressHandler;
import com.tistory.link2me.common.PHPComm;

import java.net.HttpURLConnection;
import java.util.ArrayList;
import java.util.List;

public class Login extends AppCompatActivity {
    String getDeviceID; // 스마트기기의 장치 고유값
    EditText etId;
    EditText etPw;

    String userID;
    String userPW;
    CheckBox autologin;
    Boolean loginChecked;
    String idx;
    public SharedPreferences settings;
    private BackPressHandler backPressHandler;

    Context mContext;

    // 멀티 퍼미션 지정
    private String[] permissions = {
            Manifest.permission.READ_PHONE_STATE,
            Manifest.permission.CALL_PHONE, // 전화걸기 및 관리
            Manifest.permission.WRITE_CONTACTS, // 주소록 액세스 권한
            Manifest.permission.WRITE_EXTERNAL_STORAGE // 기기, 사진, 미디어, 파일 엑세스 권한
    };
    private static final int MULTIPLE_PERMISSIONS = 101;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        mContext = this.getBaseContext();
        backPressHandler = new BackPressHandler(this); // 뒤로 가기 버튼 이벤트

        if (Build.VERSION.SDK_INT >= 23) { // 안드로이드 6.0 이상일 경우 퍼미션 체크
            checkPermissions();
        }

        // 네트워크 연결상태 체크
        if(NetworkConnection() == false){
            NotConnected_showAlert();
        }

        etId = (EditText) findViewById(R.id.login_id_edit);
        etPw = (EditText) findViewById(R.id.login_pw_edit);
        autologin = (CheckBox) findViewById(R.id.autologin);

        settings = getSharedPreferences("settings", Activity.MODE_PRIVATE);
        loginChecked = settings.getBoolean("LoginChecked", false);
        if (loginChecked) {
            etId.setText(settings.getString("userID", ""));
            etPw.setText(settings.getString("userPW", ""));
            autologin.setChecked(true);
        }

        if(!settings.getString("userID", "").equals("")) etPw.requestFocus();

        Button submit = (Button) findViewById(R.id.login_btn);
        submit.setOnClickListener(new Button.OnClickListener(){
            @Override
            public void onClick(View view) {
                userID = etId.getText().toString().trim();
                userPW = etPw.getText().toString().trim();

                if(userID != null && !userID.isEmpty() && userPW != null && !userPW.isEmpty()){
                    login(userID, userPW);
                }
            }
        });

    }

    private void login(String loginID, String loginPW){
        // 단말기의 ID 정보를 얻기 위해서는 READ_PHONE_STATE 권한이 필요
        TelephonyManager mTelephony = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
        if (ActivityCompat.checkSelfPermission(mContext, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) {

        }
        if (mTelephony.getDeviceId() != null) {
            getDeviceID = mTelephony.getDeviceId();  // 스마트폰 기기 정보
        } else {
            getDeviceID = Settings.Secure.getString(getApplicationContext().getContentResolver(), Settings.Secure.ANDROID_ID);
        }

        // 전달할 인자들
        Uri.Builder builder = new Uri.Builder()
                .appendQueryParameter("userID", loginID)
                .appendQueryParameter("userPW", loginPW)
                .appendQueryParameter("deviceID", getDeviceID);
        String urlParameters = builder.build().getEncodedQuery();
        new getJSONData().execute(Value.IPADDRESS + "/loginChk.php", urlParameters);
    }

    private class getJSONData extends AsyncTask<String, Void, String> {
        ProgressDialog pdLoading = new ProgressDialog(Login.this);
        HttpURLConnection conn;

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            //this method will be running on UI thread
            pdLoading.setMessage("\tValidating user...");
            pdLoading.setCancelable(false);
            pdLoading.show();
        }

        @Override
        protected String doInBackground(String... params) {
            try {
                return PHPComm.getJson(params[0],params[1]);
            } catch (Exception e) {
                return new String("Exception: " + e.getMessage());
            }
        }

        protected void onPostExecute(String result){
            pdLoading.dismiss();
            showJSONResult(result);
        }
    }

    protected void showJSONResult(String result) {
        if(Integer.parseInt(result) > 0){ // 로그인 정보 일치하면 idx 값 받음
            idx = result;
            AutoLoginChk(); // 정보 저장
            Toast.makeText(Login.this,"로그인 성공", Toast.LENGTH_SHORT).show();
            startActivity(new Intent(getApplication(), MainActivity.class));
            finish(); // 현재 Activity 를 없애줌
        } else if(result.equalsIgnoreCase("-1")){ // 등록된 단말기와 불일치
            deviceDismatch_showAlert();
        } else if (result.equalsIgnoreCase("0")) {
            showAlert();
        } else {
            Toast.makeText(Login.this, "서버로부터 정보가 잘못 전송되었습니다", Toast.LENGTH_SHORT).show();
        }
    }

    private boolean checkPermissions() {
        int result;
        List<String> permissionList = new ArrayList<>();
        for (String pm : permissions) {
            result = ContextCompat.checkSelfPermission(this, pm);
            if (result != PackageManager.PERMISSION_GRANTED) {
                permissionList.add(pm);
            }
        }
        if (!permissionList.isEmpty()) {
            ActivityCompat.requestPermissions(this, permissionList.toArray(new String[permissionList.size()]), MULTIPLE_PERMISSIONS);
            return false;
        }
        return true;
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        switch (requestCode) {
            case MULTIPLE_PERMISSIONS: {
                if (grantResults.length > 0) {
                    for (int i = 0; i < permissions.length; i++) {
                        if (permissions[i].equals(this.permissions[i])) {
                            if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
                                showToast_PermissionDeny();
                            }
                        }
                    }
                } else {
                    showToast_PermissionDeny();
                }
                return;
            }
        }

    }

    private void showToast_PermissionDeny() {
        Toast.makeText(this, "권한 요청에 동의 해주셔야 이용 가능합니다. 설정에서 권한 허용 하시기 바랍니다.", Toast.LENGTH_SHORT).show();
        finish();
    }

    public void onStop(){
        // 어플리케이션이 화면에서 사라질때
        super.onStop();
        //AutoLoginChk();
    }

    private void AutoLoginChk(){
        // 자동 로그인이 체크되어 있고, 로그인에 성공했으면 폰에 자동로그인 정보 저장
        if (autologin.isChecked()) {
            settings = getSharedPreferences("settings",Activity.MODE_PRIVATE);
            SharedPreferences.Editor editor = settings.edit();

            editor.putString("userID", userID);
            editor.putString("userPW", userPW);
            editor.putBoolean("LoginChecked", true);
            editor.putString("idx", idx);

            editor.commit();
        } else {
            // 자동 로그인 체크가 해제되면 폰에 저장된 정보 모두 삭제
            settings = getSharedPreferences("settings",    Activity.MODE_PRIVATE);
            SharedPreferences.Editor editor = settings.edit();
            editor.clear(); // 모든 정보 삭제
            editor.commit();
        }
    }

    public void deviceDismatch_showAlert(){
        AlertDialog.Builder builder = new AlertDialog.Builder(Login.this);
        builder.setTitle("등록단말 불일치");
        builder.setMessage("최초 등록된 단말기가 아닙니다.\n" + "관리자에게 문의하여 단말기 변경신청을 하시기 바랍니다.")
                .setCancelable(false)
                .setPositiveButton("OK", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        dialog.dismiss();
                    }
                });
        AlertDialog alert = builder.create();
        alert.show();
    }

    public void showAlert(){
        AlertDialog.Builder builder = new AlertDialog.Builder(Login.this);
        builder.setTitle("로그인 에러");
        builder.setMessage("로그인 정보가 일치하지 않습니다.")
                .setCancelable(false)
                .setPositiveButton("OK", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        dialog.dismiss();
                    }
                });
        AlertDialog alert = builder.create();
        alert.show();
    }

    private void NotConnected_showAlert() {
        AlertDialog.Builder builder = new AlertDialog.Builder(Login.this);
        builder.setTitle("네트워크 연결 오류");
        builder.setMessage("사용 가능한 무선네트워크가 없습니다.\n" + "먼저 무선네트워크 연결상태를 확인해 주세요.")
                .setCancelable(false)
                .setPositiveButton("확인", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        finish(); // exit
                        //application 프로세스를 강제 종료
                        android.os.Process.killProcess(android.os.Process.myPid() );
                    }
                });
        AlertDialog alert = builder.create();
        alert.show();

    }

    private boolean NetworkConnection() {
        int[] networkTypes = ;
        try {
            ConnectivityManager manager = (ConnectivityManager) getSystemService (Context.CONNECTIVITY_SERVICE);
            for (int networkType : networkTypes) {
                NetworkInfo activeNetwork = manager.getActiveNetworkInfo();
                if(activeNetwork != null && activeNetwork.getType() == networkType){
                    return true;
                }
            }
        } catch (Exception e) {
            return false;
        }
        return false;
    }

    @Override
    public void onBackPressed() {
        backPressHandler.onBackPressed();
    }

}

 

Intro.java 코드는 Login.java 코드를 활용하면 얼마든지 만들 수 있다.

코드의 핵심로직은 다음과 같다.

// 자동 로그인 체크 검사
settings = getSharedPreferences("settings", Activity.MODE_PRIVATE);
loginChecked = settings.getBoolean("LoginChecked", false);
if (loginChecked) {
    userID = settings.getString("userID", "");
    userPW = settings.getString("userPW", "");
    if (userID != null && !userID.isEmpty() && userPW != null && !userPW.isEmpty()) {
        login(userID, userPW);
    } else {
        startActivity(new Intent(getApplication(), Login.class));
        finish();
    }
} else {
    startActivity(new Intent(getApplication(), Login.class));
    finish();
}

 

protected void showJSONResult(String result) {
    if(Integer.parseInt(result) > 0){ // 로그인 정보 일치하면 idx 값 받음
        SharedPreferences.Editor editor = settings.edit();
        editor.putString("idx", result);
        Toast.makeText(Intro.this,"로그인 성공", Toast.LENGTH_SHORT).show();
        startActivity(new Intent(getApplication(), MainActivity.class));
        finish(); // 현재 Activity 를 없애줌
    } else {
        startActivity(new Intent(getApplication(), Login.class));
        finish();
    }
}

 

7. 서버 PHP 코드 작성

loginChk.php

<?php
extract($_POST);

if (isset($_POST['userID']) && isset($_POST['userPW'])) {
    require_once '../config/config.php';
    $c = new MemberClass();

    // get the user by userID and password
    $rs = $c->LoginUserChk($userID,$userPW,$deviceID);
    $user = $c->getUser($userID, $userPW);

    if ($user != false && $rs > 0) {
        // use is found
        echo $user['idx'];
    } else if ($user != false && $rs == -1) {
        // 등록된 폰이 아닙니다. 관리자에게 문의하세요!
        echo -1;
    } else {
        // user is not found with the credentials
        echo 0;
    }
} else {
    echo -2;
}
?>

 

서버 코드 값을 좀 더 세분화하면 등록된 userID가 없는 경우도 안드로이드폰에 반환할 수 있고, 패스워드가 일치하지 않는 경우를 반환할 수도 있다.

이러한 것은 개발자의 몫이다.

 

서버 PHP 와 안드로이드간에 통신 개념부분은 http://link2me.tistory.com/1110 를 참조하면 도움된다.

Bootstrap 기반 로그인 폼 및 로그인 처리(PHP PDO Class 연동) : http://link2me.tistory.com/1402 참조

[부트스트랩] 로그인 폼 및 로그인 처리 (PHP PDO Class 연동)

출처: http://link2me.tistory.com/1402 [소소한 일상 및 업무TIP 다루기]
[부트스트랩] 로그인 폼 및 로그인 처리 (PHP PDO Class 연동)

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

 

 

 

 

다음에 시간이 되면 안드로이드 회원가입 처리부분을 해볼 생각이다.

 

회원가입이 필요한 분은 https://www.androidhive.info/2012/01/android-login-and-registration-with-php-mysql-and-sqlite 에 명시된 코드를 활용하여 만들어도 될 것이다.

이 코드는 서버에 저장된 DB 데이터를 안드로이드폰 SQLite DB에 저장하는 로직이 추가되어 있으므로 그대로 구현해도 되고, 서버에만 저장하고 저장된 결과를 간단하게 받아서 정상 가입여부만 알 수 있도록 register.php 코드를 수정해도 된다.

Bootstrap 기반 회원가입 폼 및 회원가입 처리(http://link2me.tistory.com/1403) 코드를 참조해서 xml 파일을 수정하고 작성하면 될 것이다.

서버와의 통신에 사용된 HttpURLConnection 부분은 http://link2me.tistory.com/1349 게시글과 첨부파일을 참조하면 된다.

Asynchronous Http Client 를 사용한 서버와의 통신은 http://link2me.tistory.com/1493 게시글을 참조하면 도움된다.

 

첨부파일은 코드 작성에 도움되도록 일부만 발췌하여 첨부한다.

android_Login.zip
다운로드

 

블로그 이미지

Link2Me

,