728x90

FCM 테스트를 해보려고 Android Studio 에서 신규로 파일을 생성하는 방식으로 해봤다.

그러다보니 Eclipse 기반에서 구현한 로그인 코드가 에러가 발생해서 로그인이 안된다.

deprecated 된 것을 모두 수정해서 코드를 보완했다.


 === Login.java ===

 package com.tistory.link2me.addresschart;

import android.annotation.TargetApi;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
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.Secure;
import android.support.annotation.NonNull;
import android.support.v4.util.Pair;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.webkit.CookieManager;
import android.webkit.CookieSyncManager;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.Toast;

import com.google.firebase.iid.FirebaseInstanceId;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.List;
import java.util.Map;

public class Login extends Activity {

    String getDeviceID; // 스마트기기의 장치 고유값
    EditText etId;
    EditText etPw;

    String loginID;
    String loginPW;
    CheckBox autologin;
    Boolean loginChecked;
    public SharedPreferences settings;

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);

        // 네트워크 연결상태 체크
        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.autologinchk);

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

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

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

                if(loginID != null && !loginID.isEmpty() && loginPW != null && !loginPW.isEmpty()){
                    new AsyncLogin().execute(loginID,loginPW);
                }
            }
        });

    }

    private  class AsyncLogin 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 {
                URL url = new URL(Value.IPADDRESS + "/loginChk.php");
                conn = (HttpURLConnection) url.openConnection();
                // 세션 쿠키 전달
                String cookieString = CookieManager.getInstance().getCookie(Value.IPADDRESS);

                // 단말기의 ID 정보를 얻기 위해서는 READ_PHONE_STATE 권한이 필요
                TelephonyManager mTelephony = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
                if (mTelephony.getDeviceId() != null){
                    getDeviceID = mTelephony.getDeviceId();  // 스마트폰 기기 정보
                } else {
                    getDeviceID = Secure.getString(getApplicationContext().getContentResolver(), Secure.ANDROID_ID);
                }

                // 사용자 기기의 고유 토큰 정보를 획득
                String getToken = FirebaseInstanceId.getInstance().getToken();

                System.out.println("userID : " + loginID);
                System.out.println("Input PW : " + loginPW);
                System.out.println("DeviceID : " + getDeviceID);
                System.out.println("Login Token : " + getToken);

                if(conn != null){ // 연결되었으면
                    conn.setConnectTimeout(10000); // milliseconds 연결 타임아웃시간

                    //add request header
                    conn.setRequestMethod("POST");
                    conn.setRequestProperty("USER-AGENT", "Mozilla/5.0");
                    conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
                    conn.setRequestProperty("Accept-Language", "en-US,en;q=0.5");
                    if (cookieString != null) {
                        conn.setRequestProperty("Cookie", cookieString);
                        Log.e("PHP_getCookie", cookieString);
                    }

                    conn.setUseCaches(false);
                    conn.setDefaultUseCaches(false);
                    conn.setDoOutput(true); // POST 로 데이터를 넘겨주겠다는 옵션
                    conn.setDoInput(true); // 서버로부터 응답 헤더와 메시지를 읽어들이겠다는 옵션

                    // 전달할 인자들
                    Uri.Builder builder = new Uri.Builder()
                            .appendQueryParameter("loginID", params[0])
                            .appendQueryParameter("loginPW", params[1])
                            .appendQueryParameter("deviceID", getDeviceID)
                            .appendQueryParameter("tokenID", getToken);
                    String urlParameters = builder.build().getEncodedQuery();

                    DataOutputStream wr = new DataOutputStream(conn.getOutputStream());
                    wr.writeBytes(urlParameters);
                    wr.flush();
                    wr.close();

                    int responseCode = conn.getResponseCode();
                    System.out.println("GET Response Code : " + responseCode);
                    if(responseCode == HttpURLConnection.HTTP_OK){ // 연결 코드가 리턴되면
                        BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
                        StringBuilder sb = new StringBuilder();
                        String line;
                        while((line = reader.readLine())!= null){
                            sb.append(line);
                        }
                        System.out.println("line Value : " + sb.toString().trim());
                        return sb.toString().trim();
                    }else{
                        return("unsuccessful");
                    }
                }

            } catch(Exception e){
                return new String("Exception: " + e.getMessage());
            } finally {
                conn.disconnect();
            }
            return null;
        }

        protected void onPostExecute(String result){
            pdLoading.dismiss();
            if(result.equalsIgnoreCase("1")){ // 로그인 정보 일치
               Toast.makeText(Login.this,"로그인 성공", Toast.LENGTH_SHORT).show();
                startActivity(new Intent(getApplication(), MainActivity.class));
                finish(); // finish()를 호출해서 Activity를 없애줌

            } else if(result.equalsIgnoreCase("-1")){ // 등록된 단말기와 불일치
                deviceDismatch_showAlert();
            } else if (result.equalsIgnoreCase("0")) {
                showAlert();
            } else {
                Toast.makeText(Login.this, "서버로부터 정보가 잘못 전송되었습니다", Toast.LENGTH_SHORT).show();
            }
        }
    }

    public void onStop(){
        // 어플리케이션이 화면에서 사라질때
        super.onStop();
        // 자동 로그인이 체크되어 있고, 로그인에 성공했으면 폰에 자동로그인 정보 저장
        if (autologin.isChecked()) {
            settings = getSharedPreferences("settings",Activity.MODE_PRIVATE);
            SharedPreferences.Editor editor = settings.edit();

            editor.putString("loginID", loginID);
            editor.putString("loginPW", loginPW);
            editor.putBoolean("LoginChecked", true);

            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 = {ConnectivityManager.TYPE_MOBILE, ConnectivityManager.TYPE_WIFI};
        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;
    }

    // Back 버튼을 누르면 어플 종료여부 확인 처리
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if( keyCode == KeyEvent.KEYCODE_BACK ) {
            new AlertDialog.Builder(this).setIcon(android.R.drawable.ic_dialog_alert).setTitle("Quit").setMessage("어플을 종료하시겠습니까?").setPositiveButton("Yes", new DialogInterface.OnClickListener() {
                @Override
                public void onClick( DialogInterface dialog, int which) {
                    moveTaskToBack(true); // 본Activity finish후 다른 Activity가 뜨는 걸 방지.
                    finish();
                    //application 프로세스를 강제 종료
                    android.os.Process.killProcess(android.os.Process.myPid() );
                }
            }).setNegativeButton( "No", null ).show();

            return true;
        }

        return super.onKeyDown(keyCode, event);
    }

    @Override
    protected void onResume()
    {
        super.onResume();
        if (android.os.Build.VERSION.SDK_INT >= 21) flushCookies();
        else CookieSyncManager.getInstance().startSync();
    }

    @Override
    protected void onPause()
    {
        super.onPause();
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
            //noinspection deprecation
            CookieSyncManager.getInstance().stopSync(); // 동기화 종료
        }
    }

    @TargetApi(21)
    private void flushCookies() {
        // 롤리팝 이상에서는 CookieManager의 flush를 하도록 변경됨.
        CookieManager.getInstance().flush();
    }
}


서버 loginChk.php

- 서버에서는 $_POST 배열로 전달받은 값이면 로그인 처리 함수를 통해서 로그인 성공이면 1을 반환하고 틀리면 0을 반환하도록 코드를 구현하면 된다.


 <?php
if(!isset($_SESSION)) {
    session_start();
}

@extract($_POST); // POST 전송으로 전달받은 값 처리
if(isset($loginID) && !empty($loginID) && isset($loginPW) && !empty($loginPW)) {

    $deviceID = $deviceID ? $deviceID : '';

    if(isset($deviceID) && !empty($deviceID)){ // 모바일 접속이면
        require_once 'db.info.php';
        require_once 'phpclass/dbClass.php';
        $conn=new MySQLiDbClass();
        $dbconn = $conn->isConnectDb($DB); // 안드로이드폰에서는 반드시 객체로 생성해야 정상접속
        require_once 'phpclass/loginClass.php';
        $c=new LoginClass();

        $result = $c->MobileUserAuthCheck($loginID,$loginPW,$deviceID);
        if($result > 0 ) {
            session_save_path('./_tmp/session');

            $_SESSION['userID'] = $loginID;
            $_SESSION['userPW'] = md5($loginPW);
            $_SESSION['ip'] = $_SERVER['REMOTE_ADDR'];
            $_SESSION['ua'] = $_SERVER['HTTP_USER_AGENT'];

            echo $result; // 로그인 성공이면 1을 반환
        } else if($result == 0) {
            echo 0; // 로그인 정보 틀림
        } else {
            echo '-1'; // Phone mismatch
        }

    }

} else {
    echo("<meta http-equiv='Refresh' content='0; URL=loginForm.php'>");
}
?>


유사 게시글(POD 기반 로그인 처리) : http://link2me.tistory.com/1404

블로그 이미지

Link2Me

,
728x90

삼성폰은 쉽게 USB 디버깅을 설정할 수 있는데 LG G5 폰에서 USB 디버깅(개발자모드)를 활성화시키는 방법을 몰라서 고생을 좀 했다.


1. LG USB 통합드라이버를 홈페이지에서 받아서 설치한다.

    http://www.lge.co.kr/lgekor/download-center/downloadCenterList.do

    소프트웨어 다운로드

    LG United Mobile Driver(LG 360 캠 포함)


    ※ Firefox 브라우저에서 다운로드 했더니 죽어도 안되더라. 그래서 Explorer 로 받았더니 받아지네. 우라질 ^^


2. 폰에서 설정변경 방법

    설정 --> 시스템 --> 휴대폰 정보 --> 소프트웨어정보 --> 빌드번호

    빌드번호가 보일 것이다.

    빌드번호를 7번 눌러주면 개발자 모드가 활성화된다.


3. Android Studio 에서 인식이 안된다.

   왜 그런지 도저히 원인을 찾을 수가 없다.


2017.9.21일 추가사항

- 개발자 모드가 활성화되면 폰 인식을 시킬 수 있는 단계까지 준비가 된 거더라.

  최근에 삼성폰 외에 다른 폰에서도 기능이 제대로 동작되는지 확인하기 위해서

  LG폰을 구해서 꼭 테스트를 해야 되어서 테스트를 하다가 인식하도록 하는 걸 찾아냈다.

  삼성폰은 개발자 모드 활성화만 시키면 인식되는데, LG폰은 개발자 모드 ON 으로 꼭 한번 더 해줘야....

- 개발자 옵션으로 일단 들어간다.

  상단에 ON/OFF 하는 버튼이 보인다. 이걸 먼저 눌러야 한다.

  먼저 OFF 로 설정한 다음에 ON으로 다시 설정한다. 

- USB 디버깅을 ON 으로 변경하면 이제 변경이 된다.

  이렇게 하면 폰에서 인식이 된다.

블로그 이미지

Link2Me

,
728x90

안드로이드 Broadcast 를 테스트 해보고 정리해둔다.
개념도 제대로 모른채 구글링해서 나온 예제를 실행해보고 실행이 되는구나 하는 정도만으로는 응용이 불가능하다는 걸 테스트 해보면서 몸소 느끼고 있다. Android Studio 기반에서 테스트한 것을 적어둔다.

안드로이드 6.0 에서 SMS 권한이 없는데 허용할 것인지 문의하는 창도 뜨고 잘 동작된다.


BroadCast.zip


Android Studio 는 main 폴더에 핵심적인 내용이 들어가 있으므로 이것만 파일 첨부하면 이걸 이용해서 다시 작성하기가 쉽다.


BroadcastReceiver는 모든 어플에게 다 전해지는 것이다. (시스템 매니저가 알아서 보내는 것)


// IntentFilter 전달받는 여러 인텐트중 특정 인텐트만 고르겠다는 의도
IntentFilter filter = new IntentFilter(UsbManager.ACTION_USB_DEVICE_ATTACHED);
registerReceiver(mUsbAttachReceiver , filter); // 브로드캐스트리시버 등록


또는

IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_USB_DEVICE_ATTACHED);
registerReceiver(mUsbAttachReceiver, filter);  // 브로드캐스트리시버 등록


Broadcast Receiver(방송수신자)는 BroadcastReceiver 클래스를 상속받아 작성한다.

Broadcast 메시지가 수신되면 onReceive 메서드가 자동 호출된다.

public class Broadcastreceiver extends BroadcastReceiver {

    private Bundle bundle;
    private SmsMessage currentSMS;
    private String message;

    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
            Log.d("onReceive()","부팅완료");
            //Intent i = new Intent(context, ScreenService.class);
            //context.startService(i);
        }


        if (intent.getAction().equals("android.provider.Telephony.SMS_RECEIVED")) {
            Log.d("SMSBroadcastReceiver", "SMS 메시지가 수신되었습니다.");
            //Toast.makeText(context, "문자가 수신되었습니다", Toast.LENGTH_SHORT).show();
            bundle = intent.getExtras();
            if (bundle != null) {
                Object[] pdu_Objects = (Object[]) bundle.get("pdus");
                if (pdu_Objects != null) {
                    for (Object aObject : pdu_Objects) {
                        currentSMS = getIncomingMessage(aObject, bundle);
                        String senderNo = currentSMS.getDisplayOriginatingAddress();
                        message = currentSMS.getDisplayMessageBody();
                        Toast.makeText(context, "senderNum: " + senderNo + " :\n message: " + message, Toast.LENGTH_LONG).show();
                    }
                    this.abortBroadcast();
                    // End of loop
                }
            }
        } // bundle null



AndroidManifest.xml 을 사용하여 Broadcast Receiver 를 등록

- 앱이 실행중이 아니더라도 지정한 Broadcast 메시지를 수신(지정한 Action 발생)되면 Broadcast Receiver 가 자동 실행된다.


동적으로 Broadcast Receiver 를 등록하면, 앱이 실행중일 때만 Broadcast Receiver 가 수행되며, 동적으로 Broadcast Receiver 를 추가/제거가 가능하다.


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

    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.READ_SMS"/>
    <uses-permission android:name="android.permission.RECEIVE_SMS" />

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

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <receiver
            android:name ="com.tistory.link2me.sms_receive.Broadcastreceiver"
            android:enabled="true">
            <intent-filter android:priority="10000">
                <action android:name="android.intent.action.BOOT_COMPLETED"/>
                <action android:name="android.provider.Telephony.SMS_RECEIVED" />
            </intent-filter>
        </receiver>

    </application>

</manifest>


AndroidManifest.xml에 해당 리시버를 등록하고 intent-filter로 원하는 브로드캐스팅 받을 Action을 등록한다.

즉, intent-filter는 어떤 intent를 필터링해서 들을것인지 설정하는 것이다.

안드로이드 앱에서 알고 싶은 이벤트들이 있을수 있다. 가령
- 배터리 부족
- 네트워크 연결 / 중단
- SD카드 삽집 / 제거
- SMS 수신
- 카메라 버튼이 눌렸을때
- 폰의 날짜, 시간이 변동되었을때
- 비행기 모드 전환시
- 어플 설치/제거
- 재부팅/종료
- 매 분마다 수신


android:enabled="true" : 시스템이 알아서 Broadcastreceiver를 실행한다는 의미다. default 가 true 다.

android:exported="false" : 외부 어플리케이션에서는 사용할 수 없으며 같은 앱 또는 같은 UserId를 가진 놈만 호출할 수 있다는 뜻이다. (이걸 적어두면 메시지 수신이 제대로 안됨)
android:priority : 숫자가 높을수록 우선순위가 높으며, 우선순위가 높은 리시버부터 순차적으로 메세지가 전달된다.


 === Broadcastreceiver.java === 

 package com.tistory.link2me.sms_receive;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.telephony.SmsMessage;
import android.util.Log;
import android.widget.Toast;

public class Broadcastreceiver extends BroadcastReceiver {
    private Bundle bundle;
    private SmsMessage currentSMS;
    private String message;

    @Override
    public void onReceive(Context context, Intent intent) {

        if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
            Log.d("onReceive()","부팅완료");
            //Intent i = new Intent(context, ScreenService.class);
            //context.startService(i);
        }

        if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
            Log.d("onReceive()","스크린 ON");
        }

        if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
            Log.d("onReceive()","스크린 OFF");
        }

        if (intent.getAction().equals("android.provider.Telephony.SMS_RECEIVED")) {
            Log.d("SMSBroadcastReceiver", "SMS 메시지가 수신되었습니다.");
            Toast.makeText(context, "문자가 수신되었습니다", Toast.LENGTH_SHORT).show();
            bundle = intent.getExtras();
            if (bundle != null) {
                Object[] pdu_Objects = (Object[]) bundle.get("pdus");
                if (pdu_Objects != null) {
                    for (Object aObject : pdu_Objects) {
                        currentSMS = getIncomingMessage(aObject, bundle);
                        String senderNo = currentSMS.getDisplayOriginatingAddress();
                        message = currentSMS.getDisplayMessageBody();
                        Toast.makeText(context, "senderNum: " + senderNo + " :\n message: " + message, Toast.LENGTH_LONG).show();
                    }
                    this.abortBroadcast();
                    // End of loop
                }
            }
        } // bundle null
    }

    private SmsMessage getIncomingMessage(Object aObject, Bundle bundle) {
        SmsMessage currentSMS;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            String format = bundle.getString("format");
            currentSMS = SmsMessage.createFromPdu((byte[]) aObject, format);
        } else {
            currentSMS = SmsMessage.createFromPdu((byte[]) aObject);
        }
        return currentSMS;
    }
}


문자수신시각 정보 추가시

Date curDate = new Date(currentSMS.getTimestampMillis());
SimpleDateFormat sdf = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" );
String receivedDate = sdf.format( curDate );



=== MainActivity.java ===

 package com.tistory.link2me.sms_receive;

import android.Manifest;
import android.content.pm.PackageManager;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

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

        int permissionCheck = ContextCompat.checkSelfPermission(this, Manifest.permission.RECEIVE_SMS);
        if(permissionCheck == PackageManager.PERMISSION_GRANTED){
            Toast.makeText(this, "SMS 수신권한 있음", Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(this, "SMS 수신권한 없음", Toast.LENGTH_SHORT).show();
            if(ActivityCompat.shouldShowRequestPermissionRationale(this,Manifest.permission.RECEIVE_SMS)){
                Toast.makeText(this, "SMS 권한 설정이 필요함", Toast.LENGTH_SHORT).show();
            } else {
                // 권한이 할당되지 않았으면 해당 권한을 요청
                ActivityCompat.requestPermissions(this,new String[] {Manifest.permission.RECEIVE_SMS},1);
            }
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        if (requestCode == 1) {
            for (int i = 0; i < permissions.length; i++) {
                if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
                    Toast.makeText(this, permissions[i] + " 권한이 승인됨.", Toast.LENGTH_LONG).show();
                } else {
                    Toast.makeText(this, permissions[i] + " 권한이 거부됨.", Toast.LENGTH_LONG).show();
                }
            }
        }
    }

}



블로그 이미지

Link2Me

,
728x90

Eclipse 에서 만든 코드를 Android Studio 로 변환하고 나서 신규로 코드를 추가하면 문제가 생긴다.

FCM 을 이용한 메시지 발송시 메시지 수신하는 걸 해결하지 못한 상태다.

FCM 메시지 발송을 위한 TokenID 를 회원DB에 저장하는 방법을 Login.java 코드를 이용하여 구현했다.


Login.java 파일에서 로그인 정보를 입력하고 확인을 누르면,  POST 방식으로 서버에 있는 loginChk.php 파일로 전송되어 내용을 확인하고 정보를 기록하고 결과값을 반환한다.

서버에서 결과값은 echo 문을 다시 안드로드이드폰에서 받아서 그 결과값을 기준으로 로그인 성공, 로그인 실패, 인증된 단말인지 여부 체크 등을 한다.


사용자 기기 토큰 정보를 획득하는 코드는 http://blog.naver.com/PostView.nhn?blogId=cosmosjs&logNo=220739141098&categoryNo=0&parentCategoryNo=56&viewDate=&currentPage=1&postListTopCurrentPage=1&from=search 를 참조하면서 해보는 중이다.


=== Login.java ====

package com.tistory.link2me.addresschart;

import android.app.ActionBar;
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.net.ConnectivityManager;
import android.os.Bundle;
import android.provider.Settings.Secure;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.webkit.CookieManager;
import android.webkit.CookieSyncManager;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.Toast;

import com.google.firebase.iid.FirebaseInstanceId;

import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.cookie.Cookie;
import org.apache.http.impl.client.BasicResponseHandler;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;

import java.util.ArrayList;
import java.util.List;

public class Login extends Activity {

    String getDeviceID; // 스마트기기의 장치 고유값
    ProgressDialog dialog = null;
    EditText etId;
    EditText etPw;

    String loginID;
    String loginPW;
    CheckBox autologin;
    Boolean loginChecked;
    List<NameValuePair> params;
    public SharedPreferences settings;
    CookieManager cookieManager;

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.login);

        // ActionBar 제거하기
        ActionBar actionbar = getActionBar();
        actionbar.hide();

        // 네트워크 연결상태 체크
        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.autologinchk);

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

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

        CookieSyncManager.createInstance(this);
        cookieManager = CookieManager.getInstance();
        CookieSyncManager.getInstance().startSync();

        Button submit = (Button) findViewById(R.id.login_btn);
        submit.setOnClickListener(new Button.OnClickListener(){

            @Override
            public void onClick(View v) {
                dialog = ProgressDialog.show(Login.this, "", "Validating user...", true);
                 new Thread(new Runnable() {
                        public void run() {
                            login();
                        }

                      }).start();
            }

        });

    }

    void login() {
        try {
            loginID = etId.getText().toString().trim();
            loginPW = etPw.getText().toString().trim();

            // 단말기의 ID 정보를 얻기 위해서는 READ_PHONE_STATE 권한이 필요
            TelephonyManager mTelephony = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
            if (mTelephony.getDeviceId() != null){
                getDeviceID = mTelephony.getDeviceId();  // 스마트폰 기기 정보
            } else {
                getDeviceID = Secure.getString(getApplicationContext().getContentResolver(), Secure.ANDROID_ID);
            }

            // 사용자 기기의 고유 토큰 정보를 획득
            String getToken = FirebaseInstanceId.getInstance().getToken();

            String postURL = Value.IPADDRESS + "/loginChk.php";
            HttpPost post = new HttpPost(postURL);

            // 전달할 인자들
            params = new ArrayList<NameValuePair>();
            params.add(new BasicNameValuePair("loginID", loginID));
            params.add(new BasicNameValuePair("loginPW", loginPW));
            params.add(new BasicNameValuePair("deviceID", getDeviceID));
            params.add(new BasicNameValuePair("tokenID", getToken));

            UrlEncodedFormEntity ent = new UrlEncodedFormEntity(params,HTTP.UTF_8);
            post.setEntity(ent);

            HttpClient httpclient = new DefaultHttpClient();

            ResponseHandler<String> responseHandler = new BasicResponseHandler();
            final String responsePost = httpclient.execute(post, responseHandler);

            System.out.println("DeviceID : " + getDeviceID);
            System.out.println("Login Token : " + getToken);
            System.out.println("Response : " + responsePost);

            runOnUiThread(new Runnable() {
                public void run() {
                    dialog.dismiss();
                }
            });

            if(responsePost.equalsIgnoreCase("1")){ // 로그인 정보 일치
                runOnUiThread(new Runnable() {
                    public void run() {
                        Toast.makeText(Login.this,"Login Success", Toast.LENGTH_SHORT).show();
                    }
                });

                // 쿠키 정보 생성을 통한 PHP 세션 접속 유지 처리
                List<Cookie> cookies = ((DefaultHttpClient)httpclient).getCookieStore().getCookies();
                if (!cookies.isEmpty()) {
                    for (int i = 0; i < cookies.size(); i++) {
                        String cookieString = cookies.get(i).getName() + "="
                                    + cookies.get(i).getValue();
                        Log.e("PHP_setCookie", cookieString);
                        cookieManager.setCookie(Value.IPADDRESS, cookieString);
                    }
                }
                Thread.sleep(300);

                startActivity(new Intent(this.getApplicationContext(), MainActivity.class));
                finish(); // finish()를 호출해서 Activity를 없애줌

            } else if(responsePost.equalsIgnoreCase("-1")){ // 등록된 단말기와 불일치
                deviceDismatch_showAlert();
            } else {
                showAlert();
            }
        } catch(Exception e) {
            dialog.dismiss();
            System.out.println("Exception : " + e.getMessage());
        }

    }

    public void onStop(){
        // 어플리케이션이 화면에서 사라질때
        super.onStop();
        // 자동 로그인이 체크되어 있고, 로그인에 성공했으면 폰에 자동로그인 정보 저장
        if (autologin.isChecked()) {
            settings = getSharedPreferences("settings",Activity.MODE_PRIVATE);
            SharedPreferences.Editor editor = settings.edit();

             editor.putString("loginID", loginID);
             editor.putString("loginPW", loginPW);
             editor.putBoolean("LoginChecked", true);

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

    }

    public void deviceDismatch_showAlert(){
        Login.this.runOnUiThread(new Runnable() {
            public void run() {
                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) {
                           }
                       });
                AlertDialog alert = builder.create();
                alert.show();
            }
        });
    }

    public void showAlert(){
        Login.this.runOnUiThread(new Runnable() {
            public void run() {
                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) {
                           }
                       });
                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() {
        ConnectivityManager manager = (ConnectivityManager) getSystemService (Context.CONNECTIVITY_SERVICE);
        boolean isMobileAvailable = manager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE).isAvailable();
        boolean isMobileConnect = manager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE).isConnectedOrConnecting();
        boolean isWifiAvailable = manager.getNetworkInfo(ConnectivityManager.TYPE_WIFI).isAvailable();
        boolean isWifiConnect = manager.getNetworkInfo(ConnectivityManager.TYPE_WIFI).isConnectedOrConnecting();

        if ((isWifiAvailable && isWifiConnect) || (isMobileAvailable && isMobileConnect)){
            return true;
        }else{
            return false;
        }
    }

    // Back 버튼을 누르면 어플 종료여부 확인 처리
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if( keyCode == KeyEvent.KEYCODE_BACK ) {
            new AlertDialog.Builder(this).setIcon(android.R.drawable.ic_dialog_alert).setTitle("Quit").setMessage("어플을 종료하시겠습니까?").setPositiveButton("Yes", new DialogInterface.OnClickListener() {
                @Override
                public void onClick( DialogInterface dialog, int which) {
                    moveTaskToBack(true); // 본Activity finish후 다른 Activity가 뜨는 걸 방지.
                    finish();
                    //application 프로세스를 강제 종료
                    android.os.Process.killProcess(android.os.Process.myPid() );
                }
        }).setNegativeButton( "No", null ).show();

        return true;
      }

      return super.onKeyDown(keyCode, event);
     }

    @Override
    protected void onResume()
    {
        super.onResume();
        CookieSyncManager.getInstance().startSync();
    }

    @Override
    protected void onPause()
    {
        super.onPause();
        CookieSyncManager.getInstance().stopSync(); // 동기화 종료
    }
}
 


이제 MySQL DB 및 PHP 코드를 구현해야 한다.

MySQL DB에는 tokenID 칼럼을 추가한다. Varchar(200) 으로 길이를 설정해준다. 토콘의 길이가 상당히 길더라.

테스트하는 회원 테이블은 회원ID와 회원 Data 를 분리하여 저장하는 방식으로 만들어보고 있다.


테이블 구조

CREATE TABLE IF NOT EXISTS `member_id` (
  `uid` int(11) NOT NULL AUTO_INCREMENT,
  `site` int(11) NOT NULL DEFAULT '0',
  `id` varchar(50) NOT NULL DEFAULT '',
  `pw` varchar(50) NOT NULL DEFAULT '',
  `code` int(6) NOT NULL DEFAULT '0',
  `admin` int(2) NOT NULL DEFAULT '0',
  PRIMARY KEY (`uid`),
  UNIQUE KEY `id` (`id`),
  KEY `code` (`code`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;


CREATE TABLE IF NOT EXISTS `member_data` (
  `memberuid` int(11) NOT NULL,
  `site` int(11) NOT NULL DEFAULT '0',
  `auth` tinyint(4) NOT NULL DEFAULT '0',
  `level` int(11) NOT NULL DEFAULT '0',
  `admin` tinyint(4) NOT NULL DEFAULT '0',
  `email` varchar(50) NOT NULL DEFAULT '',
  `name` varchar(30) NOT NULL DEFAULT '',
  `nic` varchar(50) NOT NULL DEFAULT '',
  `grade` varchar(20) NOT NULL DEFAULT '',
  `photo` varchar(200) NOT NULL DEFAULT '',
  `sex` tinyint(4) NOT NULL DEFAULT '0',
  `officeNO` varchar(14) NOT NULL DEFAULT '',
  `mobileNO` varchar(14) NOT NULL DEFAULT '',
  `num_login` int(11) NOT NULL DEFAULT '0',
  `d_regis` varchar(14) NOT NULL DEFAULT '',
  `tokenID` varchar(200) DEFAULT NULL,
  `phoneID` varchar(60) DEFAULT NULL,
  PRIMARY KEY (`memberuid`),
  KEY `site` (`site`),
  KEY `auth` (`auth`),
  KEY `level` (`level`),
  KEY `admin` (`admin`),
  KEY `email` (`email`),
  KEY `name` (`name`),
  KEY `nic` (`nic`),
  KEY `sex` (`sex`),
  KEY `d_regis` (`d_regis`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8; 



=== loginChk.php ===

<?php
if(!isset($_SESSION)) {
    session_start();
}

@extract($_POST); // POST 전송으로 전달받은 값 처리
if(isset($loginID) && !empty($loginID) && isset($loginPW) && !empty($loginPW)) {

    $deviceID = $deviceID ? $deviceID : '';

    if(empty($deviceID)){
        require_once 'dbconnect.php'; // db접속
        require_once 'phpclass/loginClass.php';

        $c=new LoginClass();

        $row = $c->WebUserAuthCheck($loginID,$loginPW);
        if(is_array($row)) {
            if($row['code'] > 0) {
                $_SESSION['userID'] = $row['id'];
                $_SESSION['userPW'] = md5($loginPW);
                $_SESSION['code'] = $row['code'];
                $_SESSION['ip'] = $_SERVER['REMOTE_ADDR'];
                $_SESSION['ua'] = $_SERVER['HTTP_USER_AGENT'];

                echo("<meta http-equiv='Refresh' content='0; URL=m/list.php'>");
            } else {
                echo '권한 불가';
            }
        } else if($row == '0'){
            $msg ='정보가 올바르지 않습니다';
            echo "<script>alert('".$msg."');history.go(-1);</script>";
        } else {
            $msg ='정보가 올바르지 않습니다';
            echo "<script>alert('".$msg."');history.go(-1);</script>";
        }

    } else {
        require_once 'db.info.php';
        require_once 'phpclass/dbClass.php';
        $conn=new MySQLiDbClass();
        $dbconn = $conn->isConnectDb($DB); // 안드로이드폰에서는 반드시 객체로 생성해야 정상접속
        require_once 'phpclass/loginClass.php';
        $c=new LoginClass();

        $result = $c->MobileUserAuthCheck($loginID,$loginPW,$deviceID);
        if($result > 0 ) {
            session_save_path('./_tmp/session');

            $_SESSION['userID'] = $loginID;
            $_SESSION['userPW'] = md5($loginPW);
            $_SESSION['ip'] = $_SERVER['REMOTE_ADDR'];
            $_SESSION['ua'] = $_SERVER['HTTP_USER_AGENT'];

            // 토큰 등록 및 갱신
            if(isset($tokenID) && strlen($tokenID) > 63){
                $c->registerToken($tokenID,$loginID,$deviceID); // DB에 토큰 저장
                //$myfile = fopen("tokenfile.txt", "a") or die("파일 쓰기를 할 수 없습니다.");
                //fwrite($myfile, $tokenID);
                //fclose($myfile);
            }
            echo 1;
        } else if($result == 0) {
            echo 0; // 로그인 정보 틀림
        } else {
            echo '-1'; // Phone mismatch
        }
    }

} else {
    echo("<meta http-equiv='Refresh' content='0; URL=loginForm.php'>");
}
?>
 

 // 토큰 등록 및 갱신(2017-03-24) 함수 만들기 (LoginClass.php 파일내에 존재)
    function registerToken($token,$userID,$deviceID){
        global $dbconn;
        $rs = $this->getDeviceChk($userID,$deviceID);
        if($rs > 0 ){
            $gettokenID = $this->getTokenIDChk($rs); // 0 또는 토큰 반환
            if(($gettokenID == '0') || ($gettokenID !== $token)){ // 등록되어 있지 않으면 등록
                $sql ="UPDATE member_data SET tokenID='".$token."'";
                $sql.=" where memberuid='".$rs."'";
                if($result = mysqli_query($dbconn,$sql)){
                    return 1;
                } else {
                    return 0;
                }
            }
        } else { // 장치 ID 와 다르거나 없으면
            // 로그인 금지
        }
    }

    function getTokenIDChk($rs){
        global $dbconn;
        $sql ="select tokenID from member_data where memberuid='".$rs."'";
        if($result = mysqli_query($dbconn,$sql)){
            $row = mysqli_fetch_row($result);
            if($row[0] == NULL){
                return 0;
            } else {
                return $row[0];
            }
        } else {
            return 0;
        }
    }

    function getDeviceChk($userID,$deviceID){
        global $dbconn;
        // 사용자 기준 장치 정보 검사
        $sql ="select count(phoneID),memberuid from member_data";
        $sql.=" where memberuid=(select uid from member_id where id='".$userID."') and phoneID='".$deviceID."'";
        if($result = mysqli_query($dbconn,$sql)){
            $row = mysqli_fetch_row($result);
            if($row[0] == 1){
                return $row[1]; // 있으면 memberuid 반환
            } else {
                return $row[0];
            }
        } else {
            return 0;
        }
    }


토큰을 회원 DB에 저장하는 코드 구현로직이 어떻게 되는지만 참고하면 된다고 본다.


FCM 을 이용한 PUSH 메시지 전송을 성공하면 다시 기록해 두련다.

블로그 이미지

Link2Me

,
728x90

fragment란 activity안에 속해있는 화면으로 새로운 activity를 띄우는게 아니라,

하나의 activity에서 fragment만 바꿔가면서 보여주기 때문에

한 activity화면에서 여러개의 화면을 볼 수 있게 해주는 것이라고 할 수 있다.

 

https://developer.android.com/guide/components/fragments.html

 

본 예제는 두 개의 Fragment 를 생성하고 하나의 Fragment 만 Activity 상에 보이도록 할 것이다.
그리고, 액티비티의 버튼을 누르면 다른 Fragment 로 교체되도록 하는 내용에 대해서 적어둔다.

 

fragment_main.zip
다운로드

 

=== fragment01.xml ===

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/tv01"
        android:text="프레그먼트01 입니다"
        android:textSize="20dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <Button
        android:id="@+id/btn01"
        android:text="Fragment02 로 이동"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>

 

=== fragment02.xml ===

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:background="#ff0"
    android:gravity="center"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/tv02"
        android:text="프레그먼트02 입니다"
        android:textSize="20dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <Button
        android:id="@+id/btn02"
        android:text="Fragment 01로 이동"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>

 

=== activity_main.xml ===

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/parentView"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <fragment
        android:id="@+id/frag01"
        android:name="com.tistory.link2me.fragment.Fragment01"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</RelativeLayout>

 

=== Fragment01.java ===

package com.tistory.link2me.fragment;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;

public class Fragment01 extends Fragment {

    public View onCreateView(LayoutInflater inflater, ViewGroup parentView, Bundle savedInstanceState){
        // 인플레이트(inflate)를 한다는 것은 동작 가능한 view 객체로 생성한다는 의미
        ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.fragment01, parentView,false);

        Button btn = (Button) rootView.findViewById(R.id.btn01);
        btn.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View v) {
                MainActivity mainActivity = (MainActivity) getActivity();
                mainActivity.onFragmentChanged(0);
            }
        });

        return rootView;
    }
}

 

=== Fragment02.java ===

package com.tistory.link2me.fragment;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;

public class Fragment02 extends Fragment {
    public View onCreateView(LayoutInflater inflater, ViewGroup parentView, Bundle savedInstanceState){

        ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.fragment02, parentView,false);

        Button btn = (Button) rootView.findViewById(R.id.btn02);
        btn.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View v) {
                MainActivity mainActivity = (MainActivity) getActivity();
                mainActivity.onFragmentChanged(1);
            }
        });

        return rootView;
    }
}

 

=== MainActivity.java ===

package com.tistory.link2me.fragment;

import android.support.v4.app.Fragment;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

    Fragment01 fragment01;
    Fragment02 fragment02;

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

        // 초기 지정은 frag01 로

        fragment01 = (Fragment01) getSupportFragmentManager().findFragmentById(R.id.frag01);
        fragment02 = new Fragment02();
    }

    public void onFragmentChanged(int index){
        if(index == 0){
            getSupportFragmentManager().beginTransaction().replace(R.id.parentView,fragment02).commit();
        } else if(index == 1){
            getSupportFragmentManager().beginTransaction().replace(R.id.parentView,fragment01).commit();
        }
    }
}

 

 

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

Android Fragment 기본 예제  (0) 2018.09.11
FloatingActionButton(FAB)  (0) 2018.08.15
LinearLayout weight  (0) 2018.03.01
Android ViewFlipper(뷰플리퍼)  (0) 2017.05.02
안드로이드 Layout  (0) 2016.07.23
블로그 이미지

Link2Me

,
728x90

Android Studio 상에서 프로젝트 이름(package name)을 변경하고 싶을 때가 있다.


왼쪽 프로젝트 탐색기에서 Android로 변경되어 있는지 확인



java 폴더 선택하여 그 하위에 있는 변경할패키지명을 선택한 후
Shift + F6(Refactor → rename)를 누른다






androidMainfest.xml에서 변경되어 있는지 확인


build.gradle 파일에서 applicationId를 변경할 패키지명으로 변경되었는지 확인


이렇게 하고 컴파일을 하면 패키지명이 변경되어 컴파일 된다.

하지만 물리적인 이름까지 변경되지는 않는다.


물리적인 이름까지 변경해서 테스트를 해보니 에러가 발생하고 제대로 동작이 안되는 거 같다.

그래서 딱 위 그림까지면 변경하면 생성되는 apk 파일명도 변경된다.

블로그 이미지

Link2Me

,
728x90

Android Studio 에서 메뉴 만드는 방법이다.




메뉴를 추가한다.







일일이 입력하지 않고 특정 단축어만 입력하면 자동으로 팝업되면서 선택할 수 있게 나온다.

그러면 엔터키만 치면 그 다음에 입력할 사항만 입력하면 된다.



여기서 에러메시지를 보여주면서 자동으로 넘어가지 않는다.

Android Studio 버전 2.3 에서는 자동으로 완성시키더라.

계속 버전업을 하면서 편리하게 자동완성 기능을 추가하고 있는거 같다.


==== menu_item.xml ====

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
  <item
      android:id="@+id/item01"
      android:title="메뉴1"
      android:orderInCategory="2"
      android:icon="@android:drawable/ic_lock_idle_low_battery"
      app:showAsAction="always"
      />.

    <item
        android:id="@+id/item02"
        android:title="메뉴2"
        android:orderInCategory="3"
        android:icon="@android:drawable/btn_plus"
        app:showAsAction="always"
        app:actionLayout="@layout/custom_layout"
        />

    <item
        android:id="@+id/item03"
        android:title="메뉴3"
        android:orderInCategory="1"
        android:icon="@android:drawable/sym_def_app_icon"
        app:showAsAction="always"
        />
</menu>


==== custom_layout.xml ===

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <EditText
        android:id="@+id/custom_et01"
        android:inputType="text"
        android:imeOptions="actionDone"
        android:layout_width="100dp"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/custom_tv01"
        android:text="찾기"
        android:textSize="15dp"
        android:textColor="#22a"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>


==== activity_main.xml ===

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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="com.tistory.link2me.Menu.MainActivity">

    <TextView
        android:id="@+id/tv01"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:textSize="18dp"
        android:textColor="#a84"
        android:text="Hello World!" />

    <Button
        android:id="@+id/btn01"
        android:layout_centerHorizontal="true"
        android:layout_below="@id/tv01"
        android:layout_marginTop="20dp"
        android:text="ActionBar 아이콘 변화"
        android:onClick="btnClicked"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</RelativeLayout>


==== MainActivity.java ====

package com.tistory.link2me.Menu;

import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    TextView textView;
    EditText editText;
    ActionBar actionBar;

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

        actionBar = getSupportActionBar();
        actionBar.setSubtitle("서브타이틀");

        textView = (TextView) findViewById(R.id.tv01);
    }

    public void btnClicked(View v){
        actionBar.setLogo(android.R.drawable.sym_action_call);
        actionBar.setDisplayOptions(android.app.ActionBar.DISPLAY_SHOW_HOME|android.app.ActionBar.DISPLAY_USE_LOGO);
    }

    // 시스템 상에서 자동으로 호출하는 메소드 및 custom_layout 객체화
    public boolean onCreateOptionsMenu(Menu menu){
        getMenuInflater().inflate(R.menu.menu_item,menu);

        // custom_Layout 객체를 참조
        View findView = menu.findItem(R.id.item02).getActionView();
        if(findView != null){
            editText = (EditText) findView.findViewById(R.id.custom_et01);
            if(editText != null){
                editText.setOnEditorActionListener(findListener);
            }
        } else {
            Toast.makeText(getApplicationContext(), "액션뷰가 없네요", Toast.LENGTH_SHORT).show();
        }
        return true;
    }

    public boolean onOptionsItemSelected(MenuItem item){
        switch (item.getItemId()){
            case R.id.item01:
                Toast.makeText(this, "메뉴1", Toast.LENGTH_SHORT).show();
                break;

            case R.id.item02:
                Toast.makeText(this, "메뉴2", Toast.LENGTH_SHORT).show();
                break;

            case R.id.item03:
                Toast.makeText(this, "메뉴3", Toast.LENGTH_SHORT).show();
                break;
        }

        textView.setText(item.getTitle().toString());
        return super.onOptionsItemSelected(item);
    }

    private TextView.OnEditorActionListener findListener = new TextView.OnEditorActionListener() {
        @Override
        public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
            if(event == null || event.getAction() == KeyEvent.ACTION_UP){
                find();

                // 키패드 처리 객체 --> InputMethodManager 객체
                InputMethodManager inputMethodManger = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
                // 키패드 닫기
                inputMethodManger.hideSoftInputFromWindow(textView.getWindowToken(),0);
                textView.setText("메뉴2");
            }
            return true;
        }
    };

    private void find(){
      String findString = editText.getEditableText().toString();
        Toast.makeText(this, "검색어 : "+findString, Toast.LENGTH_SHORT).show();
    }
 
}





블로그 이미지

Link2Me

,
728x90

어플을 Android 5.1 버전에서는 문제없이 잘 동작했는데 6.0에서 테스트를 하니까 로그인 자체가 안된다.


해결방법으로 간단한 방법을 알아보자.



 

target SDK 가 23이상이면 안된다.

인스톨시 버전을 낮춰야 한다. 버전이 높으면 위와 같이 직접 수동으로 처리해주는 것이 최선의 방법인거 같다.

블로그 이미지

Link2Me

,
728x90

부트스트랩 기반 WYSIWYG editor 인 summernote 기능을 익혀 보는데 쉽지 않았다.

구글링을 통해 많은 예제를 참고하면서 테스트를 해봤다.

http://summernote.org/ 에서 summernote 파일을 다운로드 받아서 설치한다.


summernote 에디터에 이미지를 첨부하게 되면, 이미지가 지정된 폴더에 업로드되고 그 결과가 editor 화면에 보여지게 된다.

이미지는 하나를 업로드 하고 다시 선택해서 업로드를 해보니 업로드는 잘 되고 두개 모두 에디터에 보여진다.


설명보다 받아서 테스트해보고 내것으로 소화하면 될 거 같다.


==== summernote.php ====

<?php
require_once 'connect.php'; // db접속 성공
require_once 'phpclass/boardClass.php'; // 게시판 클래스
if(isset($_GET['uid']) || empty($_GET['uid'])){
    $_GET['uid'] = '';
}

$uid = $_GET['uid'] ? $_GET['uid'] : '';
$b = new boardClass();
$R = $b->rdata_array($uid); // 배열함수
if($R == 0){
    $R = array();
    $R['uid'] = '';
    $R['subject']= '';
    $R['content'] = "DB에서 값을 가져온다고 가정";
    //$R['content'] = '';
}

?>

<!DOCTYPE html>
<head>
<meta charset=UTF-8" />
<meta name="robots" content="noindex,nofollow"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no"/>
<meta http-equiv="X-UA Compatible" />
<link rel="stylesheet" href="plugin/bootstrap/css/bootstrap.min.css" />
<script src="http://code.jquery.com/jquery.min.js"></script>
<script src="plugin/bootstrap/js/bootstrap.min.js"></script>
<!-- include summernote css/js-->
<link href="plugin/summernote/summernote.css" rel="stylesheet">
<script src="plugin/summernote/summernote.js"></script>
<script src="plugin/summernote/lang/summernote-ko-KR.js"></script>
<script type="text/javascript">
$(document).ready(function() {
    $('.summernote').summernote({
        height: 200,
        minHeight: null,
        maxHeight: null,
        lang : 'ko-KR',
        callbacks: {
            onImageUpload : function(files, editor, welEditable) {
                console.log('image upload:', files);
                sendFile(files[0], editor, welEditable);
            },
        }
    });

    // summernote 에 값을 기록(set)
    // var markupStr = 'hello world';
    // $('.summernote').summernote('code', markupStr);

    // summernote 에디터에 이미지 업로드
    function sendFile(file,editor,welEditable) {
        data = new FormData();
        data.append("file", file);
        $.ajax({
            url: "saveimage.php", // image 저장 경로
            data: data,
            cache: false,
            contentType: false,
            enctype: 'multipart/form-data',
            processData: false,
            type: 'POST',
            success: function(data){
                var obj = JSON.parse(data);
                if (obj.success) {
                    var image = $('<img>').attr('src', '' + obj.save_url); // 에디터에 img 태그로 저장
                    $('.summernote').summernote("insertNode", image[0]); // summernote 에디터에 img 태그를 보여줌
                } else {
                    switch(parseInt(obj.error)) {
                        case 1: alert('업로드 용량 제한에 걸렸습니다.'); break;
                        case 2: alert('MAX_FILE_SIZE 보다 큰 파일은 업로드할 수 없습니다.'); break;
                        case 3: alert('파일이 일부분만 전송되었습니다.'); break;
                        case 4: alert('파일이 전송되지 않았습니다.'); break;
                        case 6: alert('임시 폴더가 없습니다.'); break;
                        case 7: alert('파일 쓰기 실패'); break;
                        case 8: alert('알수 없는 오류입니다.'); break;
                        case 100: alert('허용된 파일이 아닙니다.'); break;
                        case 101: alert('0 byte 파일은 업로드할 수 없습니다.'); break;
                    }
                }
            },
            error: function(jqXHR, textStatus, errorThrown) {
                console.log(textStatus+" "+errorThrown);
            }
        });
    }

    $("#savebtn").click(function(){
        if($("input[name='subject']").val() ==""){
            alert('제목을 입력하세요');
            $("input[name='subject']").focus();
            return false;
        }

        var contents = $('.summernote').summernote('code');
        //alert(contents.length);
        if(contents.length < 12){ // 소스보기로 보면 <p><br></p> 가 기본으로 있음
            window.alert('내용을 입력하세요');
            $('.summernote').summernote('focus');
            return false;
        }

        $("#writeForm").submit();
    });

});
</script>
</head>
<body>
<div class="rdata_write">
    <form name="writeForm" id="writeForm" action="write.php" method="post" enctype="multipart/form-data">
    <input type="hidden" name="uid" value="<?php echo $R['uid'];?>">
    <table class="table" width="1000">
        <tr>
            <td class="td1">No</td>
            <td class="td2"><?php if($R['uid']):?><?php echo $R['uid'];?><?php endif;?></td>
        </tr>

        <tr>
            <td class="td1">제목</td>
            <td class="td2"><input type="text" name="subject" value="<?php echo htmlspecialchars($R['subject']);?>" style="width:98%;" />
            </td>
        </tr>

        <tr>
            <td class="td1">내용</td>
            <td class="td2"><!-- summernote editor -->
                <textarea class="summernote" name="content" rows="10"><?php echo htmlspecialchars($R['content']);?></textarea>
            </td>
        </tr>

        <tr>
        <td colspan="2" align="center"><input type="button" id="savebtn" value="서버전송" class="btn btn-danger" /></td>
        </tr>

    </table>
    </form>
</div>
</body>
</html>


==== saveimage.php ====

<?php
$allowed = array("jpg", "png", "gif", "zip");

if(isset($_POST) and $_SERVER['REQUEST_METHOD'] == "POST"){
    if ($_FILES['file']['name']) {
        $tmpname = $_FILES['file']['tmp_name'];
        $realname = $_FILES['file']['name'];
        if (!$_FILES['file']['error']) { // 오류 없이 파일 업로드 성공
            if(! in_array(pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION), $allowed) ){
                @unlink($tmpname);
                echo json_encode(array('success' => false, 'error' => 100));
            } else if (!getimagesize($tmpname)) {
                @unlink($tmpname);
                echo json_encode(array('success'=> false, 'error' => 101));
            } else {
                $fileExt = getExt($realname);
                $filename = date("Ymd").md5(uniqid($tmpname)) .round(microtime(true)).'.'.$fileExt;
                $uploadFile = './upload/' . $filename; //change this directory
                if(move_uploaded_file($tmpname, $uploadFile)){
                    @chmod($uploadFile,0606);
                    echo json_encode(array('success' => true, 'save_url' => $uploadFile ));
                }
            }
        } else {
            echo json_encode(array('success'=> false, 'error' => $error));
        }
    }
}

// 확장자 추출 함수
function getExt($filename){
    $ext = substr(strrchr($filename,"."),1);
    $ext = strtolower($ext);
    return $ext;
}

?>


==== write.php ====

테스트 목적이라서 내용이 없고 파일 내용이 잘 넘어가는지 여부만 테스트를 했다.

write.php 에서 content 필드의 내용을 잘 정리하는 함수 테스트까지는 못한 상태이다.

print_r($_POST) 보다는 var_dump($_POST)가 정보를 더 많이 보여준다.


<?php
extract($_POST);
var_dump($_POST);
//print_r($_POST);
exit;

?>


테스트해보니 $_POST['content'] 의 값은 그대로 잘 저장된다.

<?php
if(!isset($_SESSION)) {
    session_start();
}

@extract($_POST);
//var_dump($_POST);exit;
$tableName = 'table_items';
if(isset($_SESSION['userid']) && !empty($_SESSION['userid'])) { // 세션정보가 있으면(로그인되어있으면)
    include_once $_SERVER['DOCUMENT_ROOT'].'/_core/config/config.php';
    include_once $_site['web_config'].'/dbconnect.php'; // db접속
    include_once $_site['web_class'].'/dbClass.php';
    $c=new MySQLiDbClass;

    $content = trim($content);
    if($uid){ // 수정
        $R = $c->getUidData($tableName,$uid);
        $QVAL = "ItemName='$ItemName',Price='$Price',Quantity='$Quantity',content='$content'";
        $c->getDbUpdate($tableName,$QVAL,'uid='.$R['uid']);
        echo json_encode(array('result'=>'2'));
    } else { // 신규
        $QKEY = "ItemName,Price,Quantity,content";
        $QVAL = "'$ItemName','$Price','$Quantity','$content'";
        $c->getDbInsert($tableName,$QKEY,$QVAL);
        echo json_encode(array('result'=>'1'));
    }
} else {
    echo json_encode(array('result'=>'0'));
}
?>


https://github.com/kimsQ/rb/issues/60 은 나중에 읽어봐야 할 거 같다.


summernote.zip


에디터 테스트한다고 고생해서 다른 분들은 수고를 줄이라고 파일 첨부합니다.

블로그에 정리해주신 분들의 자료가 완벽하게 정리된 것을 찾기가 쉽지 않아서 정말 많은 검색을 통해서 자료에 살을 붙이면서 테스트에 성공했습니다.

write.php 파일과의 처리는 ajax 처리는 아닙니다. ajax 처리로 변경하려면 다른 게시글을 참조하면 됩니다.

'Web 프로그램 > 테이블, 게시판, 검색' 카테고리의 다른 글

PHP 검색어 함수  (0) 2018.04.08
HTML 특수기호  (0) 2018.01.17
부트스트랩 테이블 만들기(수정)  (0) 2017.02.02
부트스트랩 테이블 만들기  (0) 2017.01.31
부트스트랩 테이블 속성  (0) 2017.01.31
블로그 이미지

Link2Me

,
728x90

상대경로 상에서 게시판(board)에서 게시글을 클릭하면 해당 게시글을 수정할 수 있는 write.php 파일로 이동하는 기능을 작성하다보니 경로 추출이 쉽지 않다.


성공한 결과값부터 적어둔다.

$('.tr1').click(function() {
    var idx=$(this).attr('data-uid');
    var modulepath =$("#ajax_loginpath").attr('data-module');
    var page = urlParam('p') ? urlParam('p') : 1;
    var moveURL = modulepath + 'bbs/board.php?m=write&idx='+idx+'&p='+page;
    $(location).attr('href', moveURL);

}).mouseover(function() {
    $(this).children('.td2').css({'backgroundColor':'#DCDCDC','cursor':'pointer'});
}).mouseout(function() {
    $(this).children('.td2').css({'backgroundColor':'#FFFFFF','cursor':'default'});
});


과정 설명

테이블에서 해당 게시글을 눌렀을때 별도의 팝업창을 띄우거나 해당 게시글을 수정할 수 있는 창을 만들기 위해서는 해당 게시글의 uid 번호를 알아야 한다.

이것은 테이블 작성시에

<tr class="tr1" data-uid="'.$row['uid'].'">

로 해당 uid 를 추출할 수 있게 해준다.


modulepath 는

<div id="ajax_loginpath" data-path="<?php echo $g['path_page'];?>" data-module="<?php echo $g['path_module'];?>"></div>

에서 추출할 수 있도록 작성한다.


해당 페이지 번호도 넘겨줘야 게시글 수정후 list.php 파일로 돌아올 때 화면이 변경되지 않는다.

그러기 위해서는 URL parameter 값을 받아서 넘겨줘야 한다.

이 URL parameter 값을 넘겨주는 함수는

http://handam.tistory.com/41 분이 작성한 함수가 있어서 편하게 해결할 수 있었다. 꾸뻑^^


function urlParam(name){
    var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(window.location.href);
    if (results==null){
       return null;
    }
    else{
       return results[1] || 0;
    }
}


변수에서 추출을 못할 경우에는 null 을 반환하는데

페이지가 없으면 1을 반환하도록 삼항연산자를 사용했다.

var page = urlParam('p') ? urlParam('p') : 1;


이제 이동하고 싶은 URL을 설정하면 된다.

var moveURL = modulepath + 'bbs/board.php?m=write&idx='+idx+'&p='+page;

$(location).attr('href', moveURL); // 해당 URL 로 이동

블로그 이미지

Link2Me

,
728x90

본 내용은 강의 수강 내용과 인터넷 검색해서 보강한 자료를 기록해둔다.

AsyncTask 는 Thread 보다 자주 사용하며, MySQL(PHP) 서버와의 통신에서도 사용한다.

import 추가는 Alt + Enter 를 누르라고 팝업 알림창이 나온다. 그러면 눌러주면 된다.

Android Studio 는 오프라인 강의를 들었는데, 단축키 기능이 너무 편리하다.

XML 코드 작성시에도 기본적인 것부터 작성할 수 있게 하고 명령어를 일부 넣어주면 자동으로 팝업되면서 자동완성 기능을 편하게 완성시킨다.


package com.tistory.link2me.app10;

import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.AsyncTask;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    Button btn_alert;
    Button btn_custom;
    Button btn_progress;
    TextView txt;
    ProgressDialog progressDialog;

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

        btn_alert = (Button) findViewById(R.id.btn01);
        btn_custom = (Button) findViewById(R.id.btn02);
        btn_progress = (Button) findViewById(R.id.btn03);
        txt = (TextView) findViewById(R.id.tv01);

        btn_alert.setOnClickListener(listener);
        btn_custom.setOnClickListener(listener);
        btn_progress.setOnClickListener(listener);
    }

    View.OnClickListener listener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            switch (v.getId()){
                case R.id.btn01:
                    new AlertDialog.Builder(MainActivity.this)
                            .setTitle("2017 프로야구 우승후보")
                            .setMessage("두산 베어스")
                            .setIcon(android.R.drawable.ic_dialog_alert)
                            .setPositiveButton("OK", new DialogInterface.OnClickListener() {
                                @Override
                                public void onClick(DialogInterface dialog, int which) {
                                    Toast.makeText(getBaseContext(), "OK", Toast.LENGTH_LONG).show();
                                    txt.setText("두산 베어즈");
                                }
                            })
                            .setNegativeButton("NO", new DialogInterface.OnClickListener() {
                                @Override
                                public void onClick(DialogInterface dialog, int which) {
                                    Toast.makeText(getBaseContext(), "NO", Toast.LENGTH_LONG).show();
                                    txt.setText("LG 트윈스");
                                }
                            })
                            .show();
                    break;

                case R.id.btn02:
                    LinearLayout linearLayout = (LinearLayout) View.inflate(MainActivity.this, R.layout.custom, null);
                    final EditText editText = (EditText) linearLayout.findViewById(R.id.et01);

                    new AlertDialog.Builder(MainActivity.this)
                            .setTitle("2017 프로야구 우승 후보")
                            .setView(linearLayout)
                            .setPositiveButton("OK", new DialogInterface.OnClickListener() {
                                @Override
                                public void onClick(DialogInterface dialog, int which) {
                                    Toast.makeText(getBaseContext(), "OK", Toast.LENGTH_LONG).show();
                                    String winner = editText.getText().toString();
                                    txt.setText(winner);
                                }
                            })
                            .setNegativeButton("CANCEL", new DialogInterface.OnClickListener() {
                                @Override
                                public void onClick(DialogInterface dialog, int which) {
                                    Toast.makeText(MainActivity.this, "CANCEL", Toast.LENGTH_SHORT).show();
                                }
                            })
                            .show();
                    break;

                case R.id.btn03:
                    DownloadTask task = new DownloadTask(MainActivity.this);
                    task.execute(100);
                    break;
            }
        }
    };

    private class DownloadTask extends AsyncTask<Integer, String, Integer> {

        ProgressDialog progressDialog;
        private Context mContext;

        public DownloadTask(Context context){
            mContext =context;
        }

        @Override
        protected void onPreExecute() {
            progressDialog = new ProgressDialog(mContext);
            progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
            progressDialog.setIcon(android.R.drawable.ic_dialog_info);
            progressDialog.setTitle("Download");
            progressDialog.setMessage("다운로드 중...");
            progressDialog.show(); // show dialog

            super.onPreExecute();
        }

        @Override
        protected Integer doInBackground(Integer... params) {
            //doInBackground 함수는 excute() 실행시  실행됨
            final int taskCnt = params[0]; // 최대 몇인지 설정하는 변수
            publishProgress("max", Integer.toString(taskCnt));
            try {
                for (int i = 0; i < taskCnt; i++) {
                    publishProgress("progress", Integer.toString(i), "번호 " + Integer.toString(i) + "번 수행중");
                    Thread.sleep(100);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            return taskCnt; // onPostExecute()함수의 인수가 됨
        }

        @Override
        protected void onProgressUpdate(String... progress) {
            //onProgressUpdate() 함수는 publishProgress() 함수로 넘겨준 데이터들을 받아옴
            if (progress[0].equals("progress")) {
                progressDialog.setProgress(Integer.parseInt(progress[1]));
                progressDialog.setMessage(progress[2]);
            }
            else if (progress[0].equals("max")) {
                progressDialog.setMax(Integer.parseInt(progress[1]));
            }
        }

        @Override
        protected void onPostExecute(Integer result) {
            //onPostExecute() 함수는 doInBackground() 함수가 종료되면 실행됨
            progressDialog.dismiss();
            super.onPostExecute(result);
        }
    }
}


첨부한 파일은 Android Studio 에서 프로젝트 생성하고 첨부한 파일을 참조해서 작성하면 도움이 된다.

app_main.zip



블로그 이미지

Link2Me

,
728x90

Android Studio 에서 xml 파일을 추가하는 방법

오랫만에 접속하면 이런 방법마저 잊어버리게 되더라. 그래서 적어둔다.




<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">

<ImageButton
android:id="@+id/ib01"
android:src="@android:drawable/sym_def_app_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />

<EditText
android:id="@+id/et01"
android:hint="예상 우승 후보팀을 입력하세요"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />

</LinearLayout>

이 XML 파일을 View 객체로 만들기 위해서는 LayoutInflator를 이용해야 한다.

안드로이드에서 inflate 를 사용하면 xml 에 씌여져 있는 view 의 정의를 실제 view 객체로 만드는 역할을 한다.

자바 프로그램 코드상에서는 레이아웃 인플레이터에 직접 접근하지 못하고

getLayoutInflater() 메소드를 이용하거나, getSystemService(String) 메소드를 호출해 반환값으로 LayoutInflater 객체를 받아야 한다.

사용자의 화면에 보여지는 것들은 Activity 위에 있는 View다.


Layout Inflater
- 레이아웃 인플레이터는 레이아웃 xml 파일에 상응하는 뷰 객체를 반환받는 데 사용한다
- 자바 프로그램 코드상에서는 레이아웃 인플레이터에 직접 접근하지 못하고 getLayoutInflater() 메소드를 이용하거나
getSystemService(String) 메소드를 호출해 반환값으로 LayoutInflater 객체를 받아야 한다.
- 보통 자바 코드에서 View, ViewGroup 을 사용하거나, Adapter의 getview() 또는 Dialog, Popup 구현시
배경화면이 될 Layout을 만들어 놓고 View의 형태로 반환 받아 Acitivity에서 실행 하게 된다.


inflate(int resource, ViewGroup root, boolean attachToRoot)
- 레이아웃 XML파일을 View객체로 만들기 위해서는 LayoutInflater내의 inflater 메서드를 사용
- resource: view를 만들고 싶은 레이아웃 파일의 id (
inflate할 대상의 xml 리소스)

ex) R.layout.custom

- 두 번째 인수는 생성된 뷰의 루트로 사용할 뷰 객체. 리소스 내에 루트가 따로 있다면 null.


inflate 를 사용하기 위해서는 우선 inflater 를 얻어와야 한다.
LayoutInflater inflater = (LayoutInflater) getSystemService( Context.LAYOUT_INFLATER_SERVICE );
LinearLayout linearLayout = (LinearLayout) inflater.inflate( R.layout.custom, null);
setContentView( linearLayout ); // 가져온 View 를 화면에 그린다.


/* We get the inflator in the constructor */
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

--> getSystemService(Context.LAYOUT_INFLATER_SERVICE) = LayoutInflater 객체를 반환받는다.


// 인플레이트를 얻어오는 다른 방법
LinearLayout linearLayout = (LinearLayout) View.inflate(MainActivity.this, R.layout.custom, null);
// Inflate된 View에서 Child인 EditText를 얻어 오기
EditText editText = (EditText) linearLayout.findViewById(R.id.et01);




블로그 이미지

Link2Me

,
728x90

기본 알림창인 AlertDialog 다이얼로그

AlertDialog 는 사용자에게 메세지나 경고를 알리기 위한 기능으로 Android 에서 지원하는 Dialog 이다. Toast 와는 다르게 Dialog 라서 Activity의 Focus를 가져간다.

=== activity_main.xml ===
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.tistory.link2me.app9.MainActivity">

    <Button
        android:id="@+id/btnAlert"
        android:layout_centerInParent="true"
        android:text="대화상자 보기"
        android:textSize="20dp"
        android:onClick="btnClicked"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/tv01"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="대화상자를 표시하려면 아래 버튼을 누르세요"
        android:textSize="15dp"
        android:textColor="#999"
        android:gravity="center_horizontal"
        android:layout_above="@id/btnAlert"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true" />

</RelativeLayout>


=== MainActivity.java ===

package com.tistory.link2me.app9;

import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {
    TextView textView;
    String msg;
    Button btn;

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

        textView = (TextView) findViewById(R.id.tv01);
    }

    public void btnClicked(View view){
        AlertDialog alertDialog = makeDialog();
        alertDialog.show();
    }

    // 알림 대화상자 생성
    private AlertDialog makeDialog(){
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle("알림");
        builder.setMessage("항목중에 하나를 선택하세요.");
        builder.setIcon(android.R.drawable.ic_dialog_alert);

        builder.setPositiveButton("확인", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                msg="OK 버튼 선택 : "+ Integer.toString(which);
                textView.setText(msg);
            }
        });

        builder.setNegativeButton("NO", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                msg="NO 버튼 선택 : "+ Integer.toString(which);
                textView.setText(msg);
                dialog.dismiss();
            }
        });

        // Cancel 버튼 설정
        builder.setNeutralButton("CANCEL", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                msg="CANCEL 버튼 선택 : "+ Integer.toString(which);
                textView.setText(msg);
            }
        });

        // Builder 클래스의 create() 메소드를 호출하여 대화상자 생성
        AlertDialog dialog = builder.create();

        return dialog;
    }
}



블로그 이미지

Link2Me

,
728x90

Debugging
F8 : Step over
F7 : Step into
Shift + F7 : Smart step into
Shift + F8 : Step out
Alt + F9 : Run to cursor
Alt + F8 : Evaluate expression
F9 : Resume program
Ctrl + F8 : Toggle breakpoint
Ctrl + Shift + F8 : View breakpoints

Alt + Delete : 안전하게 삭제(지우기전에 사용된 곳 확인 가능)
Alt + Enter : 캐스팅 자동완성 등 빠른 수정
Alt + F7 / Ctrl + F7 : 사용내용 전체찾기 / 파일에서 사용한것 찾기
Alt + Insert : Generate code( Getters, Setters, Constructors, hashCode/equals, toString )
Alt + Right/Left : 문서탭이동
Alt + Up/Down : 이전/다음 함수 이동

Alt + Shift + up/down : 선택한 위치의 줄을 위/아래로 움직임

Ctrl + / : 한줄주석
Ctrl + Shift + / : 블럭주석
Ctrl + ] / [ :  코드블럭 처음/끝 이동
Ctrl + Alt + B : Go to implementation(s)
Ctrl + Alt + F7 : 사용된 것 새창으로 보여줌.
Ctrl + Alt + I : Auto-indent line(s)
Ctrl + Alt + J : Surround with Live Template
Ctrl + Alt + L : 소스 코드 정리
Ctrl + Alt + O : import 정리
Ctrl + Alt + Shift + N : Go to symbol
Ctrl + Alt + T : Surround with… (if..else, try..catch, for, synchronized, etc.)
Ctrl + B  : Go to declaration (XML 파일에서 참조되는 리소스의 값을 확인할 수 있음)
Ctrl + Backspace : 단어처음까지 삭제
Ctrl + D : 라인복제 또는 선택블록 복제
Ctrl + Delete : 단어끝까지 삭제
Ctrl + E : 이전에 열었던파일 목록창 열기
Ctrl + Enter : 라인분리(Smart line split)
Ctrl + F : 찾기
Ctrl + F12 : 파일 구조보기
Ctrl + G : 줄번호로 이동.
Ctrl + I : Implement methods
Ctrl + J : Insert Live Template
Ctrl + mouse over code : 간단한 설명.
Ctrl + N : 클래스 열기
Ctrl + O : Override methods
Ctrl + Q : 빠른 문서보기
Ctrl + R : 바꾸기
Ctrl + Shift + ] / [ : 코드블럭 처음또는 끝까지 선택
Ctrl + Shift + B : Go to type declaration
Ctrl + Shift + F : 경로에서 찾기(Find in path)
Ctrl + Shift + F7 : 현재파일에서 하이라이트
Ctrl + Shift + I : Open quick definition lookup
Ctrl + Shift + J : 라인합치기(Smart line join)
Ctrl + Shift + N : 파일열기
Ctrl + Shift + R : 경로에서 바꾸기(Replace in path)
Ctrl + Shift + Space : 스마트 코드 완성(예상되는 타입의 메소드또는 변수명 )
Ctrl + Shift + U : 대소문자 변환
Ctrl + Shift + V : 이전에 클립보드에 복사한 히스토리 열기.
Ctrl + Space : 기본 코드 완성(클래스/메서드 입력하다가 Ctrl + Space 를 누르면 사용할 수 있음)
Ctrl + U : super-method/super-class 이동.
Ctrl + W : 연속적인 코드블럭 선택
Ctrl + Y : 라인삭제
F12 : 이전에 사용한 도구창 열기
F2 / Shift + F2 : 다음/이전 하이라이트된 에러로 이동.
F3 : 다음찾기
F4 : 해당 소스로 이동
F5 : 복사
F6 : 이동
Shift + Esc : 마지막에 사용한 도구창 닫기
Shift + F1 : 외부 문서보기(http://developer.android.com/reference로 이동)
Shift + F3 : 이전찾기
Shift + F6 : 이름바꾸기



MAC 용 단축키는 https://gist.github.com/stkent/349cdda974fdb9697be5 를 참조하면 된다.

블로그 이미지

Link2Me

,
728x90

PHP에서 MySQL을 연동하여 데이터를 가져오거나(select), 수정(update), 삭제(delete) 등을 할 때 사용하는 간단한 흐름이다.

네이버 지식인에 질문하는 사항을 가지고 간단하게 정리한다.


<?php
// 파일을 직접 실행하면 동작되지 않도록 하기 위해서
if(isset($_POST) && $_SERVER['REQUEST_METHOD'] == "POST"){
    @extract($_POST); // $_POST['id'] 라고 쓰지 않고, $id 라고 써도 인식되게 함
    include "db_info.php"; // db 연결
    $sql ="SELECT id,pass FROM testboarduser";
    $sql.=" where id='".$id."'"; // 조건문에서 얻는 결과가 1개
    if($result=mysql_query($sql,$conn)){
        $row=mysql_fetch_array($result);
        if($id == $row['id'] ){
        echo $row['id'];
        echo $row['pass'];
        } else {
        echo "틀림";
        }
    }
}
?>


The MySQLi Extension (MySQL Improved) is a relational database driver used in the PHP scripting language to provide an interface with MySQL databases.


PHP Connect to MySQL

- MySQL (procedural) : MySQL DB에서만 동작하며, , PHP 7.0 이상에선 동작안됨. PHP 5.5에서 deprecated

- MySQLi (procedural) : MySQL DB에서만 동작한다.

- MySQLi (object-oriented) : MySQL DB에서만 동작한다.
- PDO(PHP Data Objects) :
Oracle, PostgreSQL, SQLite, MySQL 등 12가지의 DB에서 동작한다.


MySQL DB에서 가져온 데이터(배열)을 반환하는 함수에 대해 알아보자. (절차지향 MySQL 방식)

 mysql_query

 MySQL 서버에 하나의 쿼리를 전송한다.

 select, show 와 같이 레코드를 생성하는 쿼리는 그 리소스를 반환한다.

 insert, update, delete 와 같이 결과 레코드가 없는 쿼리는 true, false 를 반환한다.

 mysql_result

 쿼리의 결과 레코드를 반환한다.

 <?php
 $dbconn = mysql_connect("localhost", "root", "mypass");
 $selectdb = mysql_select_db("test",$dbconn);

 $result = mysql_query("select name from test");
 $no_result = mysql_num_rows($result);
 echo "<h2>Here is a list of the test :</h2>";
 for ($i=0;$i<=$no_result;$i++){
     echo "<p>".mysql_result($result,$i)."</p>";
 }
 mysql_close($dbconn);
 ?>

 mysql_fetch_row

 쿼리의 결과 레코드를 숫자 인덱스 배열로 반환한다.
 mysql_query() 함수의 결과 레코드를 한 레코드 단위로 가져오며,

 이 레코드를 배열 형태로 되돌려 준다.

 이 배열에서 필드값을 가져오려면 인덱스로 숫자를 사용한다.

 mysql_fetch_assoc

 쿼리의 결과 레코드를 연관 배열로 반환한다.
 mysql_query() 함수의 결과 레코드를 한 레코드 단위로 가져오며,

 이 레코드를 배열 형태로 되돌려 준다.

 배열의 인덱스로 필드 이름을 사용할 수 있어서 직관적이고 사용하기 편리하다.

 mysql_fetch_array

 쿼리의 결과 레코드를  숫자 인덱스 배열이나 연관 배열로 반환한다.


select 구문으로 조회(mysql_query)하고 그 결과인  result set 에서 레코드를 한개씩 리턴해주는 함수에는 mysql_fetch_row, mysql_fetch_assoc, mysql_fetch_array 가 있다.


MySQL 함수명

MySQLi 함수명

리턴값

 mysql_fetch_row

 mysqli_fetch_row

 일반배열

mysql_fetch_assoc

 mysqli_fetch_assoc

 연관배열

 mysql_fetch_array

 mysqli_fetch_array

 일반배열 + 연관배열


mysql_fetch_row(resource $result)
mysql_fetch_row()는 데이터를 0으로 시작하는 숫자형 인덱스 배열로 반환한다.
가장 빠르다고 알려져 있으나 어느 컬럼을 가져왔는지 가독성이 떨어진다.
컬럼명이 얼마 안될 경우 혹은 문서 정리가 잘 되어있을때 사용 하도록 한다.
$row[0]; //인덱스 번호로만 데이터를 출력한다.

mysql_fetch_assoc(resource $result)
mysql_fetch_assoc()는 데이터를 문자형 연관 배열로 반환한다.
가장 많이 사용되는 함수로써 키값을 가지고 데이터를 출력한다.
데이터베이스에서 SELECT문으로 해당 컬럼을 키값만으로 출력이 가능하다.
$row['id'];
$row['userID']; // 필드의 이름은 대소문자를 구별한다.

mysql_fetch_array(resource $result [, int $result_type ])
$result_type 은 생략 가능하며, 생략하면 MYSQL_BOTH
mysql_fetch_array($result, MYSQL_NUM) = mysql_fetch_row($result)
mysql_fetch_array($result, MYSQL_ASSOC) = mysql_fetch_assoc($result)
mysql_fetch_array($result, MYSQL_BOTH)
인덱스(숫자)번호와 연관 배열의 키값으로 데이터를 가져 올 수 있다.
성능이 크게 차이는 없으나 데이터가 많을 시 key[0] 번째 값과 key['키'] 인덱스 배열과 연관배열값을 모두 가져 오기 때문에 불필요한 메모리가 쌓인다.


성능에 미치는 영향이 별로 없다면 mysql_fetch_array 함수를 사용하는 것이 편하다.


입문자들이 코딩하면서 책에 나온대로 따라 연습하면서 놓치는 부분은 사소한 곳에 있다.

$sql ="SELECT id,pass FROM testboarduser where id='".$id."'";
$result=mysql_query($sql,$conn);
이렇게 두줄로 처리하기도 하지만 더 심하게는 간단하게 한줄로 코딩한다고

$result=mysql_query("SELECT id,pass FROM testboarduser where id='".$id."'",$conn);

로 처리한다.


로직상 틀린 것은 없다.

하지만 문제점을 발견하기 위해서 디버깅을 한다고 치자.

$result=mysql_query("SELECT id,pass FROM testboarduser where id='".$id."'",$conn);

은 문제가 어디에서 생겼는지 발견하기가 쉽지 않다.


$sql ="SELECT id,pass FROM testboarduser where id='".$id."'";
echo $sql; // 데이터가 원하는 결과대로 나오는지 확인 목적
$result=mysql_query($sql,$conn);

이렇게 하면 DB 연결이 잘된 것인지 아닌지 확인이 가능해진다.


$sql ="SELECT id,pass FROM testboarduser";
$sql.=" where id='".$id."'";
$result=mysql_query($sql,$conn);

그러면 왜 이렇게 $sql 을 분리해서 코딩하는 걸까?

검색어를 입력해서 검색어가 포함된 결과를 돌려받고 싶다. 검색어 조건이 2개 이상이다.

이런 경우 등을 처리할 때 유연하게 처리가 가능하기 때문이다.


MySQLi 접속방식과 MySQL 접속방식으로 코딩시 DB 연결 부분을 실수하는 초보자가 많다.

MySQL 

 //SQL Query
 $sql= "SELECT * FROM TableName WHERE search='".$searchName."' LIMIT 5";
 $result =  mysql_query($sql,$conn);
//SQL Query 실행(Excute)

MySQLi

 //SQL Query
 $sql= "SELECT * FROM TableName WHERE search='".$searchName."' LIMIT 5";
 $result =  mysqli_query($conn, $sql);
//SQL Query 실행(Excute)



<?php
@extract($_GET); // $_GET['cat1'] 대신에 $cat1 이라고 써도 되도록 해준다.

include "db_info.php"; // db 연결

$sql ="SELECT id,pass FROM testboarduser";
$sql.=" where display=1";
if (isset($cat1)&& !empty($cat1)) $sql.= ' and category1='.$cat1;
if (isset($cat2)&& !empty($cat2)) $sql.= ' and category2='.$cat2;
if(isset($where) && !empty($where) && isset($keyword) && !empty($keyword)){
    if($where=="name"){
        $sql.= " and name LIKE '%".trim($keyword)."%'";
    } else if( $where=="phone" ){
        $sql.= " and (cellphone LIKE '%".trim($keyword)."%' OR officephone LIKE '%".trim($keyword)."%')";
    } else {
        $sql.= " and ".$where." like '%".trim($keyword)."%'";
    }
}

$sort  = isset($sort) ? $sort : 1;
if($sort ==1){
   $xsort      = "uid";
   $xorderby   = "desc";
}else if($sort ==2){
   $xsort      = "hit";
   $xorderby   = "desc";
}else if($sort ==3){
   $xsort      = "name";
   $xorderby   = "desc";
}else if($sort ==4){
   $xsort      = "cellphone";
   $xorderby   = "desc";
}

$xsort  = isset($xsort) ? $xsort : 'uid';
$xorderby= isset($xorderby) ? $xorderby : 'desc';

$sql.="order by ".$xsort." ".$xorderby."";

$curPage = isset($curPage)? $curPage : 1;  // 출력할 페이지 번호
$rowsPage = isset($rowsPage)? $rowsPage : 10; // 기본 10개씩 출력
$sql.=" LIMIT ".$curPage.",".$rowsPage."";


echo $sql; // 쿼리문이 제대로 된 것인지 확인 목적

$result=mysql_query($sql,$conn);
while($row=mysql_fetch_array($result)){
    // 출력할 내용 테이블 형태로 작성
}


?>


이와같이 복잡한 쿼리문을 유연하게 처리하기 위해서 $sql 변수를 분리해서 사용하는게 좋다.

LIMIT 개념 이해는 https://www.w3schools.com/php/php_mysql_select_limit.asp 사이트를 애용하면 대부분 해결 될 것이다.

찾고자 하는 걸 구글링을 하듯이 https://www.w3schools.com/ 사이트를 찾아보면 정말 유용한 사이트라고 감탄하게 된다.



같이보면 도움되는 게시글

http://link2me.tistory.com/1110


블로그 이미지

Link2Me

,
728x90

서버에서 주기적으로 백업을 하거나, 소스 등을 수정했을 경우 파일을 특정 백업서버로 백업을 하는 게 좋다.


백업 서버로 백업을 하는 스크립트다.


#!/bin/bash

COMPANY_NAME=   // 회사의 영문 약어명
TODAY=`date +%Y%m%d`
BACKUP_DIR=/root/backup
DEST_SERVER_DIR=/backup/Chart/${COMPANY_NAME}
#########################################################
# 다른 패키지와 혼동되지 않도록 PATH 우선 설정
#########################################################
export PATH=/usr/local/apache/bin:/usr/local/mysql/bin:/usr/local/mysql/scripts:${PATH}

#########################################################
# 기존 파일 있으면 지우기
#########################################################
cd ${BACKUP_DIR}
rm -f *
#########################################################
# MySQL 덤프
#########################################################
echo "Dumping MySQL database..."
mysqldump -u아이디 -p패스워드 DB명 > ${BACKUP_DIR}/${COMPANY_NAME}_mysqldb_backup_${
TODAY}.sql
echo "${COMPANY_NAME}_mysqldb_backup_${
TODAY}.sql DB create !!\n\n"
sleep 1

########################################################
# Web Source 백업
########################################################
echo "source backup doing !!\n\n"
cd /usr/local/apache/
tar cvzf ${COMPANY_NAME}_srcbackup_${
TODAY}.tar.gz htdocs/
mv ${COMPANY_NAME}_srcbackup_${
TODAY}.tar.gz ${BACKUP_DIR}

sleep 1
#########################################################
# Secure FTP 백업
#########################################################
# 백업할 파일이 있는 디렉토리로 경로 변경
cd ${BACKUP_DIR}
echo "now ${BACKUP_DIR} backup doing !!\n\n"

FTP_HOST="IP주소"

/usr/bin/sftp root@${FTP_HOST} << EOF
cd ${DEST_SERVER_DIR}
mput *
exit
EOF

# 로컬 백업파일 삭제
cd ${BACKUP_DIR}
rm -f *

exit 0


블로그 이미지

Link2Me

,
728x90


1. 리눅스 파일/디렉토리 구조 파헤치기

1.1 /bin

/bin에는 필수적인 실행 명령어들이 모여 있다. 대부분이 모든 사용자가 실행 가능한 것들이며 우리가 쓰는 거의 모든 기본 명령어들이 담겨 있다. 예를 들어 cat, chmod, chown, cp, date, echo, kill, ln, ls, mkdir, more, mount, mv, ps, pwd, rm, sh, su, vi 등등등...


1.2 /boot

말 그대로 시스템 부팅에 관련된 모든 파일을 담고 있다. 단, lilo(LInux LOader)의 설정 파일은 /etc/lilo.conf 에 있으며, lilo 자체는 명령어로서 /sbin/lilo에 위치하고 있다. 특히 커널 이미지인 vmlinuz 가 위치하고 있는데, lilo 가 아닌 다른 부트로더 프로그램들 모두가 공통적으로 vmlinuz 를 이용해 시스템을 부팅하므로 이 디렉토리가 매우 중요하다고 할 수 있다. 이 디렉토리의 파일들은 직접 편집할 수 없으며 커널 컴파일 등의 과정으로부터 생성된다.


1.3 /dev

각종 디바이스 파일들이 위치해 있는데 크게 블록 디바이스와 캐릭터 디바이스로 나뉠 수 있다. 블록 디바이스란 HDD와 같은 주변 장치를 말하는데, 데이타가 블록 단위로 읽고 쓰여지며 랜덤하게 액세스할 수 있다. 반면 캐릭터 디바이스는 입출력이 한 바이트 단위로 이루어지며 데이터가 순차적으로 읽고 쓰여진다. 디바이스를 새로 만들 때에는 mknod 명령을 이용하면 되며, 물론 /bin 에 위치해 있다. mount를 할 때에 필요한 디바이스 몇 개만 소개한다.

플로피 디스크 디바이스

/dev/fd0

첫 번째 FDD의 디바이스로 두 번째의 경우는 0 대신 1을, 세 번째의 경우는 2를 써주면 된다.

IDE 하드 디스크 디바이스

/dev/hda or /dev/hda1

마지막 부분의 hda에서 'a'는 위치를 나타내는 것으로, 'a'는 primary master, 'b'는 primary slave, 'c'는 secondary master, 'd'는 secondary slave를 의미한다. 그리고 그 뒤에 숫자가 없을 경우에는 전체를 의미한다. 숫자를 달 경우에는 파티션을 의미한다.

SCSI 하드 디스크 디바이스

/dev/sda, /dev/sda1

IDE 하드 디스크 디바이스와 같으나 'h' 대신 's'를 쓴다.

하드웨어 관련 디바이스

/dev/cdrom

CD-ROM 디바이스이다.


1.4 /etc

시스템 설정 파일들이 모여 있다. 그런 만큼 시스템을 백업하고자 할 때는 꼭 이 디렉토리를 가장 먼저 백업해야 한다. 각 파일들에 대한 설명은 에디터로 파일을 열어보면 주석처리로 설명이 되어 있으므로, 참고하면 될 것이다.

/etc/(cshrc.login, cshrc.cshrc, profile)

bash 나 csh,tcsh 사용자가 로그인할 때 기본으로 읽어들여 초기화시키는 파일이다. 그러므로 모든 사용자에게 공통으로 적용할 쉘 관련 사항이 있다면 이 파일들을 수정하면 된다. 각 사용자들은 자신의 홈 디렉토리에 개별 설정 파일이 있으므로 역시 원하는 대로 설정할 수 있다.

/etc/fstab

시스템 부팅시 처리되는 마운트와 관련한 사항들이 기록되어 있다.


/dev/hda1 / ext2 defaults 1 1 /dev/hdb5 /home ext2 defaults 1 2 /dev/hdb2 /public ext2 defaults 1 2 /dev/hda2 /public2 ext2 defaults 1 2 /dev/hdb6 /sysbackup ext2 defaults 1 2 /dev/hdb3 swap swap defaults 0 0 /dev/fd0 /mnt/floppy ext2 owner,noauto 0 0 /dev/cdrom /mnt/cdrom iso9660 owner,noauto,ro 0 0 none /proc proc defaults 0 0 none /dev/pts devpts gid=5,mode=620 0 0


첫 번째 항목은 마운트될 디바이스가 적혀 있다. 디바이스 이름은 이미 보고 넘어갔으니 모르는 사람은 없겠죠? 두 번째 항목은 마운트시킬 마운트 포인트가 적혀 있다. 세 번째 항목은 해당 디바이스의 파일 시스템 타입이 적혀 있다. ext2는 리눅스에서 사용하는 파일 시스템이며, msdosvfat는 각각msdos와 windows에서 사용하는 파일 시스템이고, iso9660은 CD-ROM에서 지원되는 파일 시스템이다. 자세한 것은 mount를 참고하자.

/etc/group

사용자 그룹이 정의되어 있으며, 다음과 같은 형식으로 쓰여져 있다.


Group Name:Password:GID:Member


/etc/issue

시스템에 로그인할 때 나타나는 'login:' 위에 나타나는 메시지이다. 만약 로그인할 때 나타나는 메시지를 바꾸고 싶으면 /etc/rc.d/rc.local 파일의 맨 아래 부분에 #로 시작되는 주석을 참고하자.

/etc/motd

'message of the day'의 약자로 로그인 직후의 메세지를 담고 있다. 관리자가 공지를 띄우는 데 유용한 파일.

/etc/passwd

사용자들의 계정 정보와 패스워드, 홈 디렉토리에 대한 정보를 담고 있으며 다음과 같은 형식으로 쓰여져 있다.


user ID:passwd(암호화상태):UID:GID:사용자 정보:홈디렉토리:로그인쉘


/etc/shadow

쉐도우 패스워드 파일


1.5 /home

사용자들의 모든 홈 디렉토리가 담겨 있다.


1.6 /lib

공유 라이브러리와 커널 모듈들이 담겨있다. 이전에 짚고 넘어간 /bin과 /sbin디렉토리에 있는 실행 명령어들이 실행될 때 필요한 공유 라이브러리들이 존재한다.


1.7 /mnt

마운트 포인트 디렉토리로써 꼭 여기에만 마운트해야 하는 것은 아니다. 하지만 일부러 만들어 놓인 곳이니 이곳을 쓰자^^;


1.8 /proc

일종의 가상 파일 시스템으로 메모리 정보만을 담고 있다. 우리가 흔히 사용하는 ps 프로그램이 여기에서 프로세스 정보 등을 참조한다.


1.9 /root

root(수퍼 유저)의 홈 디렉토리. 뭐 그게 끝이지 뭐...^^;


1.10 /sbin

수퍼 유저가 사용하는 실행 명령어들이 거의 다 들어 있다. 그러므로 물론 일반 유저는 사용할 수 없다. 예를 들어 halt, reboot, fdisk, mkfs등이 있다.


1.11 /tmp

임시 디렉토리이다. 그러므로 임시 파일들의 간이 저장 창고 역할을 한다.


1.12 /usr

/usr 은 리눅스 시스템에서 가장 많은 용량을 차지하는 부분으로 대부분의 프로그램들이 여기에 깔린다.

/usr/X11R6

/usr/X11, /usr/X386 이 모두 /usr/X11R6 의 심볼릭 링크이다. /usr/X11R6/lib/X11 에는 X-윈도와 관련한 여러가지 파일 및 디렉토리가 있다.

/usr/bin

/bin 에서 짐작되듯이, 대부분의 사용자 실행 명령어들이 담겨 있다. gcc나 perl등의 개발도구도 여기에 담겨 있다.

/usr/doc

역시 doc에서 유추할 수 있듯이 각종 리눅스에 관한 문서들이 담겨 있다.

최신버젼의 문서를 보길 원한다면 kldp.org(Korea Linux Documentation Project)를 방문하면 많은 도움이 될 것이다.

/usr/include

C, C++ 프로그램의 헤더 파일들이 담겨 있다.

/usr/info

여기에는 여러가지 GNU 프로그램들(gcc, make, autoconf...)의 info 파일들이 담겨 있다. 이 디렉토리를 이용하는 방법은 단순히 'info 프로그램 - ex) info gcc -'라고 치면 된다. 상당히 유용하므로 기본적으로 알아두자.

/usr/local

새로운 프로그램들이 설치되는 곳으로 프로그램 관리를 용이하게 할 수 있도록 되어 있다. make install로 프로그램 설치시 기본 디렉토리가 대부분 /usr/local 로 되어 있는 이유가 바로 그것이다. M모사의 '창문들'이라는 OS에 있는 'Program Files'라는 디렉토리와 유사하다고 보면 된다.

/usr/man

info 와 비슷한 역할을 한다. 메뉴얼 페이지가 담겨 있는 것으로 역시 'man 프로그램'을 쳐서 이용할 수 있다. 모르는 명령어들은 꼭 한 번씩 man으로 확인하는 습관을 갖길...

/usr/src

프로그램 소스들이 보관되어 있는 것으로 리눅스 커널이 위치하고 있는 곳이다.


1.13 /var

이 디렉토리 아래에는 시스템 작동 중 변경되는 파일들이 담겨 있다. 로그 파일이나 스풀 파일들이 그것들인데, 즉 다른 시스템과 공유가 되지 않음을 의미한다.


출처 : http://coffeenix.net/doc/kuls/file_system-4.html

블로그 이미지

Link2Me

,
728x90

안드로이드폰에서 앱을 배포하는 방법은 쉽다.
안드로이드 APK 파일을 메일이나 MMS 등 뭐가 됐든 상대방에게 전달만 하면 된다. 클릭해서 파일만 열면 바로 설치가 된다.

애플의 경우는 자체 앱스토어 외에 앱의 배포를 용인하지 않는다.
아이폰 앱용 바이너리 파일인 IPA를 다운 받는다고 깔리는게 아니다.
기업입장에서는 내부 자료 보안문제를 누군가에게 검사 받는 것도 좀 그렇다.
앱스토어가 아닌 자체 배포 채널이 있어야 관리가 용이하다.
회사내에서만 사용하는 업무용 어플리케이션인 경우 굳이 iOS 앱스토어에 올릴 필요가 없다.
애플 엔터프라이즈 계정은 일년에 299달러를 내고 기업계정을 등록하면,
앱스토어에 등록할 필요가 없이 직접 소속원에게 배포가 가능하다.
DUNS라는 국제사업자 번호가 있는 기업만 가능하다.
필요한 서류는 다음과 같다.
1. 최근 3개년 재무제표
2. 사업자등록증사본
3. 법인등기부등본 (말소사항 포함, 최근 3개월 이내 발급된 것)
4. 주주명부
5. 최근 3개월 4대보험 사업장 고지 내역서


https://developer.apple.com/programs/
아이디와 패스워드를 입력하면 발급받은 계정에 대한 Expires 정보를 확인할 수 있다.

iOS Provisioning Profiles
- iOS Distribution
- iOS Development

블로그 이미지

Link2Me

,
728x90

ArrayList
- 배열 기반, 데이터의 추가와 삭제 유용하지 못함, 순차적인 추가 삭제는 제일 빠름, 인덱스가 있어 임의의 요소에 대한 접근성이 뛰어남.
- 대량의 자료를 추가/삭제하는 경우에 내부적인 처리량이 늘어나서 상당한 성능저하를 가져온다.
- 각 데이터의 인덱스를 가지고 있기 때문에, 필요한 데이터의 접근이 한번만에 가능하다.
  많은 데이터를 한 번에 다 가져와서 여러번 참조해 쓸 때 최상의 성능을 나타낸다.
- 레퍼런스 타입의 데이터만 저장할 수 있다.
- 데이터 내용의 중복이 가능하다. null값을 허용한다.

ArrayList<String> list = new ArrayList<String>();
list.add("apple")'; // 추가
String str = list.get(2); // 데이터 가져오기
list.remove(1); // 데이터 삭제
list.set(0, "오렌지"); // 데이터 수정
int index = list.indexOf("apple"); // 인덱스 알아내기


ArrayList<Calendar_Item> list = new ArrayList<Calendar_Item>();

와 같이 String 대신에 Calendar_Item 이 오면 데이터 수정을 어떻게 해야 할까?

Calendar_Item item = new Calendar_Item();
item.setYear(year);
item.setMonth(month);
item.setDay(day);
item.setWeekday(weekday);
item.setColor(color);
item.setName(name);

item.setKey(key);

list.set(0,item);

즉, String 값 대신에 Calendar_Item item 값을 넣어주면 된다.

초보자로서 이렇게 생각하는 것이 참 어렵다.


검색으로 index 값인 0 를 찾아내는 로직이 구조상 속도가 빠르지 않는거 같다.

public int getIndexOfList(String search_key) {
    for (int temp = 0; temp < list.size(); temp++) {
        String key = list.get(temp).getKey();
        if (key != null && key.equals(search_key)) {
            return temp;
        }
    }
    return -1;
}

하나의 index 값을 찾아내는데 ArrayList 배열 전체를 순환하면서 일치하는 걸 찾아낸다.

검색으로 찾아야 할 데이터가 많다면 속도 저하는 당연히 발생할 것이다.



컬렉션 프레임웍에서는 컬렉션에 저장된 요소들을 읽어오는 방법을 표준화하였다.

컬렉션에 저장된 각 요소에 접근하는 기능을 가진 Iterator 인터페이스를 정의하고, Collection 인터페이스에는 Iterator를 반환하는 iterator()를 정의하고 있다.

List list = new ArrayList();
Iterator it = list.iterator();
while(it.hasNext()){
    System.out.println(it.next());
}



HashMap

- Map 인터페이스의 한 종류로, key와 value 를 묶어 하나의 entry 로 저장한다는 특징을 갖는다.

- key는 중복을 허용하지 않고, value는 중복 허용한다.

  동일한 key 가 입력되면, 나중에 입력된 데이터(key, value)로 덮어 씌워진다.

- Map에 종류에는 Hashtable, HashMap, LinkedHashMap, SortedMap, TreeMap 등이 있다.

- 배열과 연결이 결합된 형태, 추가, 삭제, 검색, 접근성이 모두 뛰어남, 검색에는 최고 성능을 보인다.
HashMap<키의타입, 데이터의 타입> 해쉬테이블 이름 = new HashMap<키의 타입, 데이터의 타입>();
- 해쉬 테이블에 저장할 데이터 타입과, 키 타입 두 가지 데이터 타입을 정해야 한다.
- HashMap<String, Integer> hashtable = new hashmap<String, Integer>();
- hashtable.put("apple", new Integer(100)); // 해쉬 테이블에 데이터 저장
- Integer num = hashtable.get("apple"); // 해쉬테이블에 저장된 데이터를 읽어온다.
- hashtable.remove("apple"); // 해쉬테이블에 있는 데이터 삭제

※ 멀티쓰레드에서는 HashTable을 쓴다


* Map 인터페이스의 주요 메소드


HashMap 메소드 

설명 

put() 

- 키(Key)와 값으로 구성된 새로운 데이터를 추가한다. 

get() 

- 지정한 키(Key)에 해당하는 데이터를 반환한다. 

remove() 

- 지정한 키(Key)에 해당하는 데이터를 삭제한다. 

containKey() 

- 지정한 키(Key)가 존재하는지 여부를 반환한다. 

containsValue() 

- 지정한 값이 존재하는지 여부를 반환한다. 

size() 

- Map의 요소 개수를 반환한다. 

isEmpty() 

- Map이 비어 있는지의 여부를 반환한다. 



*



출처: http://hyeonstorage.tistory.com/230 [개발이 하고 싶어요]

* Map 인터페이스의 주요 메소드


HashMap 메소드 

설명 

put() 

- 키(Key)와 값으로 구성된 새로운 데이터를 추가한다. 

get() 

- 지정한 키(Key)에 해당하는 데이터를 반환한다. 

remove() 

- 지정한 키(Key)에 해당하는 데이터를 삭제한다. 

containKey() 

- 지정한 키(Key)가 존재하는지 여부를 반환한다. 

containsValue() 

- 지정한 값이 존재하는지 여부를 반환한다. 

size() 

- Map의 요소 개수를 반환한다. 

isEmpty() 

- Map이 비어 있는지의 여부를 반환한다. 



*



출처: http://hyeonstorage.tistory.com/230 [개발이 하고 싶어요]

* Map 인터페이스의 주요 메소드


HashMap 메소드 

설명 

put() 

- 키(Key)와 값으로 구성된 새로운 데이터를 추가한다. 

get() 

- 지정한 키(Key)에 해당하는 데이터를 반환한다. 

remove() 

- 지정한 키(Key)에 해당하는 데이터를 삭제한다. 

containKey() 

- 지정한 키(Key)가 존재하는지 여부를 반환한다. 

containsValue() 

- 지정한 값이 존재하는지 여부를 반환한다. 

size() 

- Map의 요소 개수를 반환한다. 

isEmpty() 

- Map이 비어 있는지의 여부를 반환한다. 




출처: http://hyeonstorage.tistory.com/230 [개발이 하고 싶어요]

* Map 인터페이스의 주요 메소드

HashMap 메소드 

설명 

put(Object Key, Object Value)

 - 키(Key)와 값으로 구성된 새로운 데이터를 추가한다. 

get(Object Key)

 - 지정한 키(Key)에 해당하는 데이터를 반환한다. 

remove(Object Key)

 - 지정한 키(Key)에 해당하는 데이터를 삭제한다. 

boolean containsKey(Object Key)

출처: http://vaert.tistory.com/107 [Vaert Street]

boolean containsKey(Object Key)

 - 지정한 키(Key)가 존재하는지 여부를 반환한다. 

boolean containsValue(Object Value)

 - 지정한 값이 존재하는지 여부를 반환한다. 

clear()

 - HashMap에 저장된 모든 객체를 제거한다

Object clone()

 - 현재 HashMap을 복제하여 반환한다.
    newmap = (HashMap)map.clone();

newmap = (HashMap)map.clone();

출처: http://vaert.tistory.com/107 [Vaert Street]

int size() 

 - HashMap에 저장된 요소의 개수를 반환한다. 

isEmpty() 

 - Map이 비어 있는지의 여부를 반환한다. 

Collection values()

 - HashMap에 저장된 모든 값을 컬렉션 형태로 반환한다.


HashTable과 HashMap의 차이는 null 값을 허용하는데에 있다.
HashTable은 null 값을 허용하지 않지만, HashMap은 null 값을 허용한다.

HashMap의 경우 동기화를 지원하지 않지만 Hashtable은 동기화를 지원하여 다중 스레드 환경에서 사용하면 된다.
보통은 HashMap을 활용하고 동기화가 필요한 시점에서는 Java 5부터 제공하는 ConcurrentHashMap을 사용하는 것이 더 좋은 방법이라 표현한다.
HashMap은 저장된 요소들의 순회를 위해 Fail-Fast Iterator를 반환한다.
Hashtable은 같은 경우 Enumeration을 반환한다.


there are many differences between HashMap and Hashtable classes that are given below.

HashMapHashtable

1) HashMap is non synchronized. It is not-thread safe and can't be shared between many threads without proper synchronization code.

Hashtable is synchronized. It is thread-safe and can be shared with many threads.

2) HashMap allows one null key and multiple null values.

Hashtable doesn't allow any null key or value.

3) HashMap is a new class introduced in JDK 1.2.

Hashtable is a legacy class.

4) HashMap is fast.

Hashtable is slow.

5) We can make the HashMap as synchronized by calling this code
Map m = Collections.synchronizedMap(hashMap);

Hashtable is internally synchronized and can't be unsynchronized.

6) HashMap is traversed by Iterator.

Hashtable is traversed by Enumerator and Iterator.

7) Iterator in HashMap is fail-fast.

Enumerator in Hashtable is not fail-fast.

8) HashMap inherits AbstractMap class.

Hashtable inherits Dictionary class.



ArrayList 다음에 <T>,<String>,<Integer>,<Class명> 라고 쓰여진 형식을 많이 보았을 것이다.

이 <>를 제네릭(Generics)이라 하는데, 이 <>안에 어떠한 타입을 선언해주어 해당 ArrayList, List 등이 사용할 객체의 타입을 지정해준다는 뜻이다. 이는 다룰 객체의 타입을 미리 명시하여 객체의 형변환을 사용할 필요없게 하며, 내가 사용하고 싶은 데이터 타입만 사용할 수 있게 해주는 효과가 있다.


ArrayList 의 제네릭스로 HashMap<String,String> 을 사용한 예제다.

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map.Entry;

public class ArrayList_EX01 {

    public static void main(String[] args) {
        ArrayList <HashMap<String,String>> list = new ArrayList<HashMap<String,String>>();
        HashMap<String, String> map = new HashMap<String,String>();
        map.put("1", "고양이");
        map.put("3", "송아지");
        map.put("2", "강아지");
        //key, value로 String의 값들을 넣어준다.
        list.add(map);

        for(int i = 0; i < list.size(); i++){
            // ArrayList 사이즈 만큼 for문을 실행한다.
            System.out.println("list 순서 " + i + "번쨰");
            for( Entry<String, String> elem : list.get(i).entrySet() ){
                // list 각각 HashMap받아서 출력한다.
                System.out.println( String.format("키 : %s, 값 : %s", elem.getKey(), elem.getValue()) );
            }
        }

    }
}


'안드로이드 > Java 문법' 카테고리의 다른 글

[Java] 정보은닉과 캡슐화  (0) 2017.10.16
자바 기본 데이터형과 크기  (0) 2017.10.11
Java 클래스(Class) ★★★★★  (0) 2017.05.04
Java foreach 문  (0) 2017.03.01
자바 배열과 기본 문법  (0) 2016.07.11
블로그 이미지

Link2Me

,
728x90

동영상 강좌를 수강하면서 보니까 Android Studio 2.1.2 버전과 2.2.3 버전의 단축키 사용법이 좀 다른가보다.

동영상 강좌에서는 단축키를 사용해서 코드를 생성하는 방법이 나오지 않는다.

오프라인 강의를 통해서 Android Studio 강의를 들었던 단축키 사용법이 없다.


Android Eclipse 기반으로 작년도 6월에 잠깐 연습을 해보고 나서 회사 업무때문에 Android 독학하는 것이 어렵다는 생각에 포기하고 있다가 올해 2월에 야간 강의를 들으면서 다시 Android Studio 연습하면서 간단하게 정리를 해두려고 한다. 전문적인 개발이 아니다보니, 업무를 이것저것 하다보면 금새 잊어버리게 된다.







7번까지 실행하고 나면 자동으로 아래 화면이 만들어진다.



여기까지가 기본적인 Android Studio 사용법을 위한 준비과정이다.

이제 Layout 부분과 MainActivity.java 파일에 내용을 추가하면 된다.


Exercise_app1.zip


Android Studio 파일은 실행파일을 생성하고 나면 파일 사이즈가 상당히 커서 파일을 전부 올릴 수가 없다.

그래서 핵심적인 것만 첨부한다.

이 첨부된 코드를 보면서 필요한 부분을 복사해서 붙여넣기 해가면서 테스트하면 된다.


Java 의 정석 책도 같이 보고 있다.

DoIt 안드로이드 앱 프로그램 책의 동영상 강좌에 보면, Java 를 몰라도 Anroid 배울 수 있다고 설명한다.

하지만 Java 에 대한 기본개념이 약하면 응용력이 생길수 없다.

PHP도 잘 하는 것은 아니지만 프로그램을 좀 해보면서 느끼는 것은 배열 다루는 것이 정말 중요하다는 걸 많이 배웠다. Java 에서는 Class 개념과 배열, ArrayList 에 대한 개념이 강해야 할 거 같아서 Java 정석 책을 병행 공부중이다.


public class MainActivity extends AppCompatActivity

- C++에서는 여러 클래스로부터 상속받은 다중상속을 허용하지만, 자바에서는 단일 상속만을 허용한다.


접근제어자

- private : 같은 클래스 내에서만 접근이 가능하다.

- protected : 같은 패키지 내에서, 그리고 다른 패키지의 자손 클래스에서 접근이 가능하다.

- public : 접근 제한이 전혀 없다.


==== MainActivity.java ====

import android.app.Activity;
import android.content.Intent;
import android.graphics.Color;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import static android.R.attr.data;

public class MainActivity extends AppCompatActivity {

    public static final int REQUEST_Code = 100;

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

        Button btn = (Button) findViewById(R.id.btn_01);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // new 액티비티로 전환
                Intent intent = new Intent(getApplicationContext(),NewActivity.class);
                intent.putExtra("name","mike");
                startActivityForResult(intent,REQUEST_Code);
            }
        });

        // 버튼의 배경색 지정
        btn.setBackgroundColor(Color.parseColor("#FF00FF"));
    }

    // new 액티비티에서 응답을 받았을 때 처리사항
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if(requestCode == REQUEST_Code){
            Toast.makeText(getBaseContext(), "요청코드:"+resultCode, Toast.LENGTH_SHORT).show();

            if(resultCode == Activity.RESULT_OK){
                String name = data.getExtras().getString("name");
                Toast.makeText(getBaseContext(), "응답이름:"+name, Toast.LENGTH_SHORT).show();
            }
        }
    }
}




==== NewActivity.java ===

import android.app.Activity;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

public class NewActivity extends AppCompatActivity {

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

        Intent intent = getIntent();
        String name = intent.getExtras().getString("name");
        Toast.makeText(getApplicationContext(), "onCreate 호출됨:"+name, Toast.LENGTH_SHORT).show();

        Button btn = (Button) findViewById(R.id.backBtn);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(getApplicationContext(), "돌아가기 버튼이 눌렸어요.", Toast.LENGTH_LONG).show();
                close();
            }
        });
    }

    // Back 키를 눌렀을 때 액티비티를 닫고 메인화면으로 이동
    public boolean onKeyDown(int keyCode, KeyEvent key){
        if(keyCode == key.KEYCODE_BACK){
            close();
            return true;
        }
        return false;
    }

    protected void close(){
        // 응답값을 전달하기 위한 인텐트 생성
        Intent resultIntent = new Intent();
        resultIntent.putExtra("name","Link2me");

        // 응답값을 전달
        setResult(Activity.RESULT_OK,resultIntent);
        finish(); // 액티비티 닫기(현재 화면을 종료)
    }
}



keyCode

 상수 설명 
 KEYCODE_DPAD_LEFT  왼쪽 이동키
 KEYCODE_DPAD_RIGHT  오른쪽 이동키
 KEYCODE_DPAD_UP  위쪽 이동키
 KEYCODE_DPAD_DOWN  아래쪽 이동키
 KEYCODE_DPAD_CENTER  이동키 중앙의 버튼
 KEYCODE_A  알파벳 A (B부터는 KEYCODE_B 방식)
 KEYCODE_0  숫자 0 (1부터는 KEYCODE_1 방식)
 KEYCODE_CALL  통화
 KEYCODE_ENDCALL  통화종료
 KEYCODE_HOME  홈
 KEYCODE_BACK  뒤로
 KEYCODE_VOLUME_UP  볼륨증가버튼
 KEYCODE_VOLUME_DOWN  볼륨감소버튼


==== activity_main.xml ====

본인이 생성하는 부분과 달라지는 부분이 뭔지부터 알아보자면 색깔이 표시된 부분이 다르다.

Android Studio 2.2.3 에서는 자동완성 기능이 워낙 뛰어나서 코드 작성이 정말 편하다.

개념을 잘 이해하고 있다면 쉽게 코드를 작성할 수가 있겠더라.


<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.a05rg.app4.MainActivity">

    <Button
        android:id="@+id/btn_01"
        android:layout_centerInParent="true"
        android:text="new activity make"
        android:textAllCaps="false"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</RelativeLayout>

블로그 이미지

Link2Me

,