728x90



PHP 7.3을 yum 설치하고 info.php 로 확인해보면 아래와 같이 Server API 정보가 나온다.


PHP를 Apaceh + PHP + DB를 각각 분리하는 구조로 만들려면 PHP-FPM 을 설치하고 설정을 제대로 해주어야 한다.


PHP 7.3 yum 설치 방법은 간단하다.

#Step 1 – Prerequsitis
yum -y install epel-release
# Remi 저장소를 설치하고 활성화한다.
yum -y install http://rpms.remirepo.net/enterprise/remi-release-7.rpm

# yum 패키지를 관리
yum -y install yum-utils

#Step 2 – Install Apache2
yum -y install httpd

#Step 3 – Install PHP and FastCGI
# Disable repo for PHP 5.4
yum-config-manager --disable remi-php54
yum-config-manager --enable remi-php73

yum -y install --enablerepo=remi-php73 php php-cli mod_ssl php-fpm php-common php-imap php-ldap
yum -y install --enablerepo=remi-php73 php-curl php-hash php-iconv php-json php-openssl php-zip
yum -y install --enablerepo=remi-php73 php-pdo php-process php-mysqlnd php-devel php-mbstring php-mcrypt php-soap php-bcmath
yum -y install --enablerepo=remi-php73 php-pgsql php-libxml php-pear php-gd php-ssh2 php-xmlreader
yum -y install --enablerepo=remi-php73 php-xml php-xmlrpc php-pecl-apc php-sockets
yum -y install --enablerepo=remi-php73 php-tokenizer php-zlib php-gmp php-Icinga php-intl
 


설치 후에 환경 설정을 제대로 해주는 것이 PHP-FPM을 이용할 수 있는데, Virtual Host 설정 방법이 간단하지가 않더라.

보통 자료가 Apache 서버 대신 NGINX 서버로 설정하는 방법이 많이 검색된다.
Apache 서버와 PHP 서버를 분리해서 테스트를 해보고 성공하면 관련 코드를 간단하게 적어둘 생각이다.

같은 서버에서 Apache + PHP-FPM 설치하는 방법은 아래 사이트를 참조하면 해결할 수 있다.
설치 참고 사이트



https://webinformation.tistory.com/101


yum 설치하는 것을 한꺼번에 지우는 방법이다.

환경설정이 맞지 않아서 여러번 테스트 하다보니 개별 삭제가 너무 귀찮아서 한꺼번에 지우는 방법을 적어둔다.

# 설치 확인
rpm -qa | grep httpd
rpm -qa | grep php
rpm -qa | grep MariaDB

## 설치된 것 한꺼번에 지우기

rpm -qa | grep httpd >list
# vi list로 해서 삭제 안할 리스트는 선별 제거한다.
sudo yum -y remove $(awk '{print $1}' <list)


rpm -qa | grep php >list
# vi list로 해서 삭제 안할 리스트는 선별 제거한다.
sudo yum -y remove $(awk '{print $1}' <list)
 



DB서버에 MariaDB 10.3 설치 방법

vi /etc/yum.repos.d/MariaDB.repo
# MariaDB 10.3 CentOS repository list - created 2019-01-13 00:47 UTC
# http://downloads.mariadb.org/mariadb/repositories/
[mariadb]
name = MariaDB
baseurl = http://yum.mariadb.org/10.3/centos7-amd64
gpgkey=https://yum.mariadb.org/RPM-GPG-KEY-MariaDB
gpgcheck=1

yum -y install MariaDB-server MariaDB-client

# mariadb 부팅 시 자동 시작 설정
systemctl enable mariadb

# mariadb 시작 (둘 중 하나 실행)
systemctl start mariadb
service mariadb start

# mariadb 상태 확인
service mariadb status

# Maria DB 보안 설정하기
mysql_secure_installation
비밀번호 입력

vi /etc/my.cnf.d/server.cnf
[mysqld]
character-set-server = utf8
collation-server     = utf8_general_ci

service mariadb restart
mysql -u root -p
status
quit

semanage port -a -t mysqld_port_t -p tcp 3306

############################
### root 패스워드 분실 복구 ####
############################
# 서비스 정지
systemctl stop mariadb

# mariadb 안전모드 실행
sudo /usr/bin/mysqld_safe --skip-grant &

# 패스워드 변경
mysql -uroot mysql
update user set password=password('변경할비밀번호') where user='root';
flush privileges;
exit;

# 접속 테스트
mysql -uroot -p
비밀번호 입력

# 서비스 재시작
systemctl restart mariadb



CentOS 6.10 기반

PHP 5.6.40 yum 설치로 PHP-FPM 설치하는 방법


https://tecadmin.net/setup-httpd-with-fastcgi-and-php-on-centos-redhat/ 에 잘 나와 있다.

이 코드에서는 Apache +PHP-FPM 을 하나의 서버에서 동작하도록 되어 있다.

Virtual Host 설정을 해주어야 하는데 /etc/httpd/conf/httpd.conf 파일의 맨 하단에 Virtual Host 설정을 추가해주면 바로 동작이 되더라.
Virtual Host 설정 안해주면 phpinfo 정보로 위와 같은 그림만 확인되고 실제 PHP개발 코드가 동작이 안되더라.

아직 서버를 분리시켜서 테스트는 못해봤다. IP주소 설정 등을 해주어야 하므로 약간의 세팅 정보 변경이 필요할 거 같다.


rpm -qa | grep remi-release
rpm -qa | grep epel-release
yum -y install http://rpms.famillecollet.com/enterprise/remi-release-6.rpm
yum list remi-release

yum-config-manager --disable remi-php54
yum-config-manager --disable remi-php73
yum-config-manager --enable remi-php56

yum -y --enablerepo=remi,remi-php56 install httpd php php-cli mod_fcgid php-common php-devel php-ldap php-mbstring php-mcrypt php-mysqlnd php-sockets
yum -y --enablerepo=remi,remi-php56 install php-pecl-apc php-pear php-pdo php-pgsql php-pecl-mongo php-sqlite php-pcre php-gmp
yum -y --enablerepo=remi,remi-php56 install php-pecl-memcache php-pecl-memcached php-gd php-xml php-snmp php-libxml php-curl php-openssl
 

# 아래를 주석처리하면 PHP-PFM이 동작된다.
vi /etc/httpd/conf.d/php.conf
#    <FilesMatch \.(php|phar)$>
#        SetHandler application/x-httpd-php
#    </FilesMatch>

vi /var/www/cgi-bin/php.fastcgi
#!/bin/bash

PHPRC="/etc/php.ini"
PHP_FCGI_CHILDREN=4
PHP_FCGI_MAX_REQUESTS=1000
export PHPRC
export PHP_FCGI_CHILDREN
export PHP_FCGI_MAX_REQUESTS
exec /usr/bin/php-cgi


chown apache:apache /var/www/cgi-bin/php.fastcgi
chmod +x /var/www/cgi-bin/php.fastcgi

vi /etc/httpd/conf/httpd.conf
<VirtualHost *:80>
    ServerName localhost
    ServerAdmin jsk005@naver.com
    DocumentRoot /var/www/html
    ScriptAlias /cgi-bin/ "/var/www/cgi-bin/"
    <Directory "/var/www/html">
            Options +Indexes +FollowSymLinks +ExecCGI
            AddHandler php-fastcgi .php
            Action php-fastcgi /cgi-bin/php.fastcgi
            AllowOverride All
            Order allow,deny
            Allow from All
    </Directory>
</VirtualHost>

#Verify PHP-FPM version
/usr/sbin/php-fpm -version

service httpd restart

## 보안설정
chmod 640 /etc/httpd/conf/httpd.conf
chown root:root /etc/httpd/conf/httpd.conf
chmod 640 /etc/php.ini
chown root:root /etc/php.ini


#부팅시 자동 시작 설정
chkconfig httpd on

## httpd.conf 파일 수정
vi /etc/httpd/conf/httpd.conf
ServerName localhost

DocumentRoot "/var/www/html"
<Directory "/var/www/html">
# 허용하지 않은 디렉토리를 액세스할 수 있으므로 가능하면 FollowSymLinks 를 삭제하는 것이 좋다.
    Options IncludesNoExec
    AllowOverride None
    Order deny,allow
    Allow from all
</Directory>


vi /etc/php.ini
;시간설정 (앞에 ;지울것) 을 지우고 아래 내용 추가
date.timezone = "Asia/Seoul"
;<? 와 <?php 둘다 인식하게 함
short_open_tag = On
; PHP 가 받아들이는 POST data 최대 크기
;default 8M 로 되어 있음, upload_max_filesize 보다 크게 설정해야 함
post_max_size = 60M
memory_limit = 128M
upload_max_filesize = 50M

; error_reporting 세팅
;개발환경
;error_reporting  =  E_ALL
; 실제 환경, 경고메시지 출력 안됨
;error_reporting = E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED
; 해킹의 먹이감이 되므로 평소에는 Off 로 해둔다. 에러가 있을 경우 디버깅 가능하도록 에러 메세지를 출력해준다.
display_errors = Off
; 평소에는 해제(Off)한다. 개발환경에서는 디버깅 가능하도록 On하면 에러 메세지를 출력해준다.
display_startup_errors = Off
log_errors = On
; 에러 로그 파일 설정이 없으면 Apache 로그 파일에 기록된다.
; 퍼미션 설정해서 /home/user/log/php.log 과 같이 설정
;error_log = filename

; register_globals = On 으로 하면, PHP 스크립트의 변수 값을 임의로 변경할 수 있는 취약성이 있다.
; off 로 설정한 후, $_GET, $POST 를 사용해서 사용자가 전달한 값을 얻어야 한다.
register_globals = Off

;UTF-8 로 문자 인코딩 설정하기
;해당 부분을 찾아서 아래와 같이 수정
default_charset = "UTF-8"
[mbstring]
mbstring.language = Korean
mbstring.internal_encoding = UTF-8
mbstring.http_input = pass
mbstring.http_output = pass
mbstring.encoding_translation = Off



블로그 이미지

Link2Me

,
728x90

Last Updated 2020.6.26


테스트 환경 : 삼성 갤럭시노트9 (AndroidQ)


안드로이드폰의 운영체제를 업그레이드하여 8.0 이다. (9.0 이상 사용폰은 마지막에 수정된 부분만 별도 언급)

API 23까지는 APK 파일을 서버에서 다운로드받고 자동으로 설치하는 화면을 띄우는 코드는

Intent intent = new Intent(Intent.ACTION_VIEW);
 Uri apkUri = Uri.fromFile(outputFile);
 intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 context.startActivity(intent);


Android 7.0(Nougat) 이상에서는 위 코드로 실행하면 에러가 발생한다.

앱 외부에서 file:// URI의 노출을 금지하는 StrictMode API 정책을 적용한다.
파일 URI를 포함하는 인텐트가 앱을 떠나면 FileUriExposedException 예외와 함께 앱에 오류가 발생한다.


애플리케이션 간에 파일을 공유하려면 content:// URI를 보내고 이 URI에 대해 임시 액세스 권한을 부여해야 한다.
FileProvider 그 권한을 가장 쉽게 부여하는 방법이다.


구글링해서 찾은 대부분의 자료들이 개념 중심으로 적혀 있어 내용 파악이 쉽지 않더라. 테스트한 코드 핵심 내용을 모두 적었다.


provider_paths.xml

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <files-path name="files" path="." />
</paths>


<files-path name="files" path="." />


File path = getFilesDir();
File file = new File(path, "abc.apk");


파일 경로를 변경해서 테스트 해보니 마찬가지로 잘 동작된다.

https://stackoverflow.com/questions/37074872/android-fileprovider-on-custom-external-storage-folder 참조


<external-path name="download" path="." />

File path = new File(Environment.getExternalStorageDirectory() + "/download");



AndroidManifest.xml

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

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />

    <application
        android:allowBackup="false"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme.NoActionBar"

        android:usesCleartextTraffic="true">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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


        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="${applicationId}.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/provider_paths"
                tools:replace="android:resource" />
        </provider>

        <activity android:name=".DownloadAPK" />
    </application>

</manifest>



MainActivity.java

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

import com.gun0912.tedpermission.PermissionListener;
import com.gun0912.tedpermission.TedPermission;

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {
    Context context;

    PermissionListener permissionlistener = new PermissionListener() {
        @Override
        public void onPermissionGranted() {
            MainActivity.this.Button_Click();
        }

        @Override
        public void onPermissionDenied(ArrayList<String> deniedPermissions) {
            Toast.makeText(MainActivity.this, "권한 허용을 하지 않으면 서비스를 이용할 수 없습니다.", Toast.LENGTH_SHORT).show();
        }
    };

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

        if (Build.VERSION.SDK_INT >= 23) {
            TedPermission.with(this)
                    .setPermissionListener(permissionlistener)
                    .setRationaleMessage("파일을 다운로드 하기 위해서는 접근 권한이 필요합니다")
                    .setDeniedMessage("앱에서 요구하는 권한설정이 필요합니다...\n [설정] > [권한] 에서 사용으로 활성화해주세요.")
                    .setPermissions(new String[]{"android.permission.WRITE_EXTERNAL_STORAGE", "android.permission.READ_EXTERNAL_STORAGE"})
                    .check();
        } else {
            Button_Click();
        }

    }

    private void Button_Click() {
        Button btn_dnload = (Button) findViewById(R.id.btn_image_download);
        btn_dnload.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(MainActivity.this, DownloadAPK.class);
                startActivity(intent);
            }
        });
    }
}


Value.java

public class Value extends Activity {
    public static final String APKNAME = "ABC.apk"; // APK name
    public static final String IPADDRESS = "http://100.100.100.100";
}



DownloadAPK.java

import android.content.Context;
import android.content.Intent;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.content.FileProvider;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.net.URLConnection;

public class DownloadAPK extends AppCompatActivity {
    Context context;
    private File outputFile;
    ProgressBar progressBar;
    TextView textView;
    LinearLayout linearLayout;
    DownloadFileFromURL downloadFileAsyncTask;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.downloadapk_activity);
        context = this.getBaseContext();

        linearLayout = (LinearLayout) findViewById(R.id.downloadprogress_layout);
        textView = (TextView) findViewById(R.id.txtView01);
        progressBar = (ProgressBar) findViewById(R.id.progressBar);

        DownloadAPK();
    }

    private void DownloadAPK() {
        // 백그라운드 객체를 만들어 주어야 다운로드 취소가 제대로 동작됨
        downloadFileAsyncTask = new DownloadFileFromURL();
        downloadFileAsyncTask.execute(Value.IPADDRESS + "/Download.php");
    }

    class DownloadFileFromURL extends AsyncTask<String, Integer, String> {

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            progressBar.setProgress(0);
        }

        @Override
        protected String doInBackground(String... apkurl) {
            int count;
            int lenghtOfFile = 0;
            InputStream input = null;
            OutputStream fos = null;

            try {
                URL url = new URL(apkurl[0]);
                URLConnection connection = url.openConnection();
                connection.connect();

                lenghtOfFile = connection.getContentLength(); // 파일 크기를 가져옴

                File path = getFilesDir();
                outputFile = new File(path, Value.APKNAME);
                if (outputFile.exists()) { // 기존 파일 존재시 삭제하고 다운로드
                    outputFile.delete();
                }

                input = new BufferedInputStream(url.openStream());
                fos = new FileOutputStream(outputFile);
                byte data[] = new byte[1024];
                long total = 0;

                while ((count = input.read(data)) != -1) {
                    if (isCancelled()) {
                        input.close();
                        return String.valueOf(-1);
                    }
                    total = total + count;
                    if (lenghtOfFile > 0) { // 파일 총 크기가 0 보다 크면
                        publishProgress((int) (total * 100 / lenghtOfFile));
                    }
                    fos.write(data, 0, count); // 파일에 데이터를 기록
                }

                fos.flush();

            } catch (Exception e) {
                e.printStackTrace();
                Log.e("UpdateAPP", "Update error! " + e.getMessage());
            } finally {
                if (input != null) {
                    try {
                        input.close();
                    }
                    catch(IOException ioex) {
                    }
                }
                if (fos != null) {
                    try {
                        fos.close();
                    }
                    catch(IOException ioex) {
                    }
                }
            }
            return null;
        }

        protected void onProgressUpdate(Integer... progress) {
            super.onProgressUpdate(progress);
            // 백그라운드 작업의 진행상태를 표시하기 위해서 호출하는 메소드
            progressBar.setProgress(progress[0]);
            textView.setText("다운로드 : " + progress[0] + "%");
        }

        protected void onPostExecute(String result) {
            if (result == null) {
                progressBar.setProgress(0);
                Toast.makeText(getApplicationContext(), "다운로드 완료되었습니다.", Toast.LENGTH_LONG).show();

                System.out.println("getPackageName : "+getPackageName());
                System.out.println("APPLICATION_ID Path : "+BuildConfig.APPLICATION_ID);
                System.out.println("outputFile Path : "+ outputFile.getAbsolutePath());
                System.out.println("Fie getPath : "+ outputFile.getPath());

                // 미디어 스캐닝
                MediaScannerConnection.scanFile(getApplicationContext(), new String[]{outputFile.getAbsolutePath()}, null, new MediaScannerConnection.OnScanCompletedListener() {
                    @Override
                    public void onScanCompleted(String s, Uri uri) {

                    }
                });


                // 다운로드한 파일 실행하여 업그레이드 진행하는 코드
                if (Build.VERSION.SDK_INT >= 24) {
                    // Android Nougat ( 7.0 ) and later
                    installApk(outputFile);
                    System.out.println("SDK_INT 24 이상 ");
                } else {
                    Intent intent = new Intent(Intent.ACTION_VIEW);
                    Uri apkUri = Uri.fromFile(outputFile);
                    intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                    getApplicationContext().startActivity(intent);
                    System.out.println("SDK_INT 23 이하 ");
                }

            } else {
                Toast.makeText(getApplicationContext(), "다운로드 에러", Toast.LENGTH_LONG).show();
            }
        }

        protected void onCancelled() {
            // cancel메소드를 호출하면 자동으로 호출되는 메소드
            progressBar.setProgress(0);
            textView.setText("다운로드 진행 취소됨");
        }
    }

    public void installApk(File file) {
        Uri uri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".fileprovider",file);
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.setDataAndType(uri, "application/vnd.android.package-archive");
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        context.startActivity(intent);
    }

}

위 분홍색 installApk() 메서드는 에러가 발생할 것이다.

BuildConfig.APPLICATION_ID 를 제대로 인식하지 못하는 문제더라.


    public void installApk(File file) {
        Uri fileUri = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".fileprovider",file);
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.setDataAndType(fileUri, "application/vnd.android.package-archive");
        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        startActivity(intent);
        finish();
    } 

이 코드로 대체하면 정상적으로 앱 업데이트가 이루어진다.


앱 build.gradle

apply plugin: 'com.android.application'
// 코틀린 혼용 사용을 위해 추가
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.3"

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

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

}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" // 코틀린 혼용 사용을 위해 추가
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.core:core-ktx:1.2.0' // 코틀린 혼용 사용을 위해 추가
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'

    implementation 'com.journeyapps:zxing-android-embedded:3.6.0'
    implementation 'gun0912.ted:tedpermission:2.0.0'
    implementation 'com.google.android.material:material:1.0.0'

    implementation 'com.naver.maps:map-sdk:3.7.1' // 네이버 지도 SDK
    implementation 'com.google.android.gms:play-services-location:17.0.0'
}


안드로이드 8.0 출처를 알 수 없는 앱 설정화면 코드는 http://mixup.tistory.com/78 참조해서 해결했다.


Value.APKNAME 은 "abc.apk" 와 같은 명칭이다.
미디어 스캐닝은 파일 다운로드한 폴더의 내용을 확인할 목적으로 검색하다 알게된 걸 적용해봤다.


참고 사이트

https://pupli.net/2017/06/18/install-apk-files-programmatically/


http://stickyny.tistory.com/110


http://mixup.tistory.com/98


도움이 되셨다면 광고 클릭 해 주세요. 좋은 글 작성에 큰 힘이 됩니다

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

도움이 되셨다면 댓글 달아주세요. 좋은 글 작성에 큰 힘이 됩니다.

블로그 이미지

Link2Me

,
728x90

Java 코드로 만든 코드를 코틀린으로 변환하고 테스트를 하니까 제대로 동작이 안된다.

코드 일부를 수정해주고 나서 동작에 제대로 된다.

Volley 라이브리를 사용하면 간단하게 해결될 사항이지만 HttpURLConnection 으로 서버 데이터를 안드로이드폰으로 가져오는 방법도 알아두면 좋을 거 같아서 테스트하고 적어둔다.


private fun UpgradeChk() {
     val builder = Uri.Builder()
         .appendQueryParameter("os", "a")
     val postParams = builder.build().encodedQuery
     val getlastVersion = getlastVersion()
     getlastVersion.execute( Value.IPADDRESS + "/lastVersion.php", postParams)
 }

inner class getlastVersion : AsyncTask<String, Void, String>() {
    override fun doInBackground(vararg params: String): String {
        return try {
            HttpURLComm.getJson(params[0], params[1])
        } catch (e: Exception) {
            String.format("Exception: " + e.message)
        }
    }

    override fun onPostExecute(response: String) {
        version = Value.VERSION // 앱 버전
        version = version.replace("[^0-9]".toRegex(), "") // 버전에서 숫자만 추출
        Log.e("WEB", "Response: $response")
        val Response = response.replace("[^0-9]".toRegex(), "") // 버전에서 숫자만 추출
        println("Server Version : $Response")
        if (version.toInt() < Response.toInt()) { // 서버 버전이 더 높으면
            UpgradeProcess()
        } else {
            AutoLoginProgress()
        }
    }
}
 

import android.util.Log
import android.webkit.CookieManager
import com.link2me.android.enode.Value
import java.io.BufferedReader
import java.io.DataOutputStream
import java.net.HttpURLConnection
import java.net.URL

object HttpURLComm  {
    // serverURL : JSON 요청을 받는 서버의 URL
    // postParams : POST 방식으로 전달될 입력 데이터
    // 반환 데이터 : 서버에서 전달된 JSON 데이터
    @JvmStatic
    @Throws(Exception::class)
    fun getJson(serverUrl: String?, postParams: String?): String {
        try {
            Thread.sleep(100)
            val url = URL(serverUrl)
            val conn = url.openConnection() as HttpURLConnection
            // 세션 쿠키 전달
            val cookieString = CookieManager.getInstance().getCookie(Value.IPADDRESS)
            val sb = StringBuilder()
            if (conn != null) { // 연결되었으면
                //add request header
                conn.requestMethod = "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.connectTimeout = 10000
                conn.readTimeout = 10000
                conn.useCaches = false
                conn.defaultUseCaches = false
                conn.doOutput = true // POST 로 데이터를 넘겨주겠다는 옵션
                conn.doInput = true

                // Send post request
                val wr = DataOutputStream(conn.outputStream)
                wr.writeBytes(postParams)
                wr.flush()
                wr.close()
                val responseCode = conn.responseCode
                Log.e("TAG","GET Response Code : $responseCode")
                if (responseCode == HttpURLConnection.HTTP_OK) { // 연결 코드가 리턴되면
                    val allText: String = conn.inputStream.bufferedReader().use(BufferedReader::readText)
                    sb.append(allText.trim())
                }
            }
            conn.disconnect()
            return sb.toString()
            // 수행이 끝나고 리턴하는 값은 다음에 수행될 onProgressUpdate 의 파라미터가 된다
        } catch (e: Exception) {
            e.printStackTrace()
        } finally {

        }
        return ""
    }
}


https://github.com/irontec/android-kotlin-samples/blob/master/KotlinTest/app/src/main/java/com/irontec/examples/kotlintest/HttpUrlConnectionAsyncActivity.kt

를 참조하여 코드를 약간 보완했다.

object HttpURLComm {
    // serverURL : JSON 요청을 받는 서버의 URL
    // postParams : POST 방식으로 전달될 입력 데이터
    // 반환 데이터 : 서버에서 전달된 JSON 데이터
    @JvmStatic
    @Throws(Exception::class)
    fun getJson(serverUrl: String?, postParams: String?): String {
        Thread.sleep(100)
        val url = URL(serverUrl)
        val httpClient = url.openConnection() as HttpURLConnection
        // 세션 쿠키 전달
        val cookieString = CookieManager.getInstance().getCookie(Value.IPADDRESS)
        val sb = StringBuilder()
        if (httpClient != null) { // 연결되었으면
            //add request header
            httpClient.requestMethod = "POST"
            httpClient.setRequestProperty("USER-AGENT", "Mozilla/5.0")
            httpClient.setRequestProperty("Content-Type", "application/x-www-form-urlencoded")
            httpClient.setRequestProperty("Accept-Language", "en-US,en;q=0.5")
            if (cookieString != null) {
                httpClient.setRequestProperty("Cookie", cookieString)
                Log.e("PHP_getCookie", cookieString)
            }
            httpClient.connectTimeout = 10000
            httpClient.readTimeout = 10000
            httpClient.useCaches = false
            httpClient.defaultUseCaches = false
            httpClient.doOutput = true // POST 로 데이터를 넘겨주겠다는 옵션
            httpClient.doInput = true

            // Send post request
            val wr = DataOutputStream(httpClient.outputStream)
            wr.writeBytes(postParams)
            wr.flush()
            wr.close()

            if (httpClient.responseCode == HttpURLConnection.HTTP_OK) {
                try {
                    val stream = BufferedInputStream(httpClient.inputStream)
                    val data: String = readStream(inputStream = stream)
                    return data
                } catch (e: Exception) {
                    e.printStackTrace()
                } finally {
                    httpClient.disconnect()
                }
            } else {
                println("ERROR ${httpClient.responseCode}")
            }
        }
        return ""
    }

    fun readStream(inputStream: BufferedInputStream): String {
        val bufferedReader = BufferedReader(InputStreamReader(inputStream))
        val stringBuilder = StringBuilder()
        bufferedReader.forEachLine { stringBuilder.append(it) }
        return stringBuilder.toString()
    }
}
 



블로그 이미지

Link2Me

,
728x90

https://link2me.tistory.com/1703 게시글에 작성된 것을 코틀린으로 변경한 것이다.


dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])
    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    implementation 'androidx.core:core-ktx:1.3.0'
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'

    implementation 'com.google.android.material:material:1.1.0'
    implementation 'gun0912.ted:tedpermission:2.0.0'
    implementation 'com.google.android.gms:play-services-maps:17.0.0' // 구글지도 라이브러리
    implementation 'com.google.android.gms:play-services-location:17.0.0' // 위치정보 라이브러리
}

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.SEND_SMS" />


activity_current_place.xml

<?xml version="1.0" encoding="utf-8"?>
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:map="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/realtimemap"
    android:name="com.google.android.gms.maps.SupportMapFragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MapsActivity" />


Android Studio 에서 제공하는 Java파일을 코틀린 파일로 변환하는 기능을 이용해서 변환하고 일부 코드를 좀 수정했다.

package com.link2me.android.googlemap

import android.Manifest
import android.annotation.SuppressLint
import android.content.Context
import android.content.DialogInterface
import android.content.pm.PackageManager
import android.location.Address
import android.location.Geocoder
import android.location.Location
import android.os.Bundle
import android.os.Looper
import android.telephony.SmsManager
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.widget.EditText
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.google.android.gms.location.*
import com.google.android.gms.maps.CameraUpdateFactory
import com.google.android.gms.maps.GoogleMap
import com.google.android.gms.maps.OnMapReadyCallback
import com.google.android.gms.maps.SupportMapFragment
import com.google.android.gms.maps.model.*
import java.io.IOException
import java.text.SimpleDateFormat
import java.util.*

class CurrentPlace : AppCompatActivity(), OnMapReadyCallback {
    private val TAG = this.javaClass.simpleName
    private lateinit var mContext: Context
    private lateinit var mMap: GoogleMap
    private var currentMarker: Marker? = null

    private lateinit var mFusedLocationProviderClient : FusedLocationProviderClient
    private lateinit var locationRequest: LocationRequest
    private var mCurrentLocatiion: Location? = null
    private var mCameraPosition: CameraPosition? = null
    private val mDefaultLocation = LatLng(37.56, 126.97)
    private var mLocationPermissionGranted = false
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        savedInstanceState?.let{
            mCurrentLocatiion = it.getParcelable(KEY_LOCATION)
            mCameraPosition = it.getParcelable(KEY_CAMERA_POSITION)
        }
        setContentView(R.layout.activity_current_place)
        mContext = this@CurrentPlace

        locationRequest = LocationRequest()
            .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY) // 정확도를 최우선적으로 고려
            .setInterval(UPDATE_INTERVAL_MS.toLong()) // 위치가 Update 되는 주기
            .setFastestInterval(FASTEST_UPDATE_INTERVAL_MS.toLong()) // 위치 획득후 업데이트되는 주기
        val builder = LocationSettingsRequest.Builder()
        builder.addLocationRequest(locationRequest)

        // Construct a FusedLocationProviderClient.
        mFusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this)

        // Build the map.
        val mapFragment = supportFragmentManager.findFragmentById(R.id.realtimemap) as SupportMapFragment
        mapFragment.getMapAsync(this)
    }

    /**
     * Saves the state of the map when the activity is paused.
     */
    override fun onSaveInstanceState(outState: Bundle) {
        mMap?.let{
            outState.putParcelable(KEY_CAMERA_POSITION, it.cameraPosition)
            outState.putParcelable(KEY_LOCATION, mCurrentLocatiion)
            super.onSaveInstanceState(outState)
        }
    }

    override fun onMapReady(map: GoogleMap) {
        Log.e(TAG, "onMapReady :")
        mMap = map
        setDefaultLocation() // GPS를 찾지 못하는 장소에 있을 경우 지도의 초기 위치가 필요함.
        locationPermission
        updateLocationUI()
        deviceLocation
    }

    private fun updateLocationUI() {
        mMap?.let{
            try {
                if (mLocationPermissionGranted) {
                    it.isMyLocationEnabled = true
                    it.uiSettings.isMyLocationButtonEnabled = true
                } else {
                    it.isMyLocationEnabled = false
                    it.uiSettings.isMyLocationButtonEnabled = false
                    mCurrentLocatiion = null
                    locationPermission
                }
            } catch (e: SecurityException) {
                Log.e("Exception: %s", e.message!!)
            }
        }
    }

    private fun setDefaultLocation() {
        currentMarker?.let{
            it.remove()
        }
        val markerOptions = MarkerOptions()
        markerOptions.position(mDefaultLocation)
        markerOptions.title("위치정보 가져올 수 없음")
        markerOptions.snippet("위치 퍼미션과 GPS 활성 여부 확인하세요")
        markerOptions.draggable(true)
        markerOptions.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_RED))
        currentMarker = mMap!!.addMarker(markerOptions)
        val cameraUpdate = CameraUpdateFactory.newLatLngZoom(mDefaultLocation, 15f)
        mMap.moveCamera(cameraUpdate)
    }

    fun getCurrentAddress(latlng: LatLng): String {
        // 위치 정보와 지역으로부터 주소 문자열을 구한다.
        var addressList: List<Address>? = null
        val geocoder =
            Geocoder(this, Locale.getDefault())

        // 지오코더를 이용하여 주소 리스트를 구한다.
        addressList = try {
            geocoder.getFromLocation(latlng.latitude, latlng.longitude, 1)
        } catch (e: IOException) {
            Toast.makeText(
                this,
                "위치로부터 주소를 인식할 수 없습니다. 네트워크가 연결되어 있는지 확인해 주세요.",
                Toast.LENGTH_SHORT
            ).show()
            e.printStackTrace()
            return "주소 인식 불가"
        }
        if (addressList.size < 1) { // 주소 리스트가 비어있는지 비어 있으면
            return "해당 위치에 주소 없음"
        }

        // 주소를 담는 문자열을 생성하고 리턴
        val address = addressList[0]
        val addressStringBuilder = StringBuilder()
        for (i in 0..address.maxAddressLineIndex) {
            addressStringBuilder.append(address.getAddressLine(i))
            if (i < address.maxAddressLineIndex) addressStringBuilder.append("\n")
        }
        return addressStringBuilder.toString()
    }

    var locationCallback: LocationCallback = object : LocationCallback() {
        override fun onLocationResult(locationResult: LocationResult) {
            super.onLocationResult(locationResult)
            val locationList =
                locationResult.locations
            if (locationList.size > 0) {
                val location = locationList[locationList.size - 1]
                val currentPosition =
                    LatLng(location.latitude, location.longitude)
                val markerTitle = getCurrentAddress(currentPosition)
                val markerSnippet =
                    "위도:" + location.latitude.toString() + " 경도:" + location.longitude
                        .toString()
                Log.d(TAG,"Time :" + CurrentTime() + " onLocationResult : " + markerSnippet)

                //현재 위치에 마커 생성하고 이동
                setCurrentLocation(location, markerTitle, markerSnippet)
                mCurrentLocatiion = location
            }
        }
    }

    private fun CurrentTime(): String {
        val today = Date()
        val date = SimpleDateFormat("yyyy/MM/dd")
        val time = SimpleDateFormat("hh:mm:ss a")
        return time.format(today)
    }

    fun setCurrentLocation(
        location: Location,
        markerTitle: String?,
        markerSnippet: String?
    ) {
        if (currentMarker != null) currentMarker!!.remove()
        val currentLatLng = LatLng(location.latitude, location.longitude)
        val markerOptions = MarkerOptions()
        markerOptions.position(currentLatLng)
        markerOptions.title(markerTitle)
        markerOptions.snippet(markerSnippet)
        markerOptions.draggable(true)
        currentMarker = mMap!!.addMarker(markerOptions)
        mMap.setOnMarkerClickListener {
            val inflater =
                mContext!!.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
            val layout = inflater.inflate(
                R.layout.send_message_popup,
                null
            )
            val lms_confirm =
                AlertDialog.Builder(mContext!!)
            lms_confirm.setTitle("발송할 휴대폰번호 등록")
            lms_confirm.setView(layout)
            val etphoneNO =
                layout.findViewById<View>(R.id.phoneno) as EditText
            // 확인 버튼 설정
            lms_confirm.setPositiveButton(
                "등록",
                DialogInterface.OnClickListener { dialog, which ->
                    val phoneNO =
                        etphoneNO.text.toString().trim { it <= ' ' }
                    if (phoneNO.length == 0) {
                        val phoneNO_confirm =
                            AlertDialog.Builder(mContext!!)
                        phoneNO_confirm.setMessage("휴대폰 번호를 입력하세요.").setCancelable(false)
                            .setPositiveButton(
                                "확인"
                            ) { dialog, which -> // 'YES'
                                dialog.dismiss()
                            }
                        val alert =
                            phoneNO_confirm.create()
                        alert.show()
                        return@OnClickListener
                    }
                    val gps_location =
                        location.latitude.toString() + "," + location.longitude
                            .toString()
                    SMS_Send(phoneNO, gps_location)
                })
            lms_confirm.setNegativeButton(
                "취소"
            ) { dialog, which -> dialog.dismiss() }
            lms_confirm.show()
            true
        }
        val cameraUpdate = CameraUpdateFactory.newLatLng(currentLatLng)
        mMap.moveCamera(cameraUpdate)
    }

    private fun SMS_Send(phoneNO: String, message: String ) { // SmsManager API
        var sms_message = "구글 지도 위치를 보내왔습니다.\n"
        sms_message += "http://maps.google.com/maps?f=q&q=$message\n누르면 상대방의 위치를 확인할 수 있습니다."
        Log.e(TAG, "SMS : $sms_message")
        try {
            //전송
            val smsManager = SmsManager.getDefault()
            val parts = smsManager.divideMessage(sms_message)
            smsManager.sendMultipartTextMessage(phoneNO, null, parts, null, null)
            Toast.makeText(
                applicationContext,
                "위치전송 문자보내기 완료!",
                Toast.LENGTH_LONG
            ).show()
        } catch (e: Exception) {
            Toast.makeText(
                applicationContext,
                "SMS faild, please try again later!",
                Toast.LENGTH_LONG
            ).show()
            e.printStackTrace()
        }
    }

    private val deviceLocation: Unit
        private get() {
            try {
                if (mLocationPermissionGranted) {
                    mFusedLocationProviderClient!!.requestLocationUpdates(
                        locationRequest,
                        locationCallback,
                        Looper.myLooper()
                    )
                }
            } catch (e: SecurityException) {
                Log.e("Exception: %s", e.message!!)
            }
        }

    private val locationPermission: Unit
        private get() {
            if (ContextCompat.checkSelfPermission(this.applicationContext,
                    Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
            ) {
                mLocationPermissionGranted = true
            } else {
                ActivityCompat.requestPermissions(
                    this, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
                    PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION
                )
            }
        }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<String>,
        grantResults: IntArray
    ) {
        mLocationPermissionGranted = false
        when (requestCode) {
            PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION -> {
                if (grantResults.size > 0
                    && grantResults[0] == PackageManager.PERMISSION_GRANTED
                ) {
                    mLocationPermissionGranted = true
                }
            }
        }
        updateLocationUI()
    }

    @SuppressLint("MissingPermission")
    override fun onStart() {
        super.onStart()
        if (mLocationPermissionGranted) {
            Log.d(TAG, "onStart : requestLocationUpdates")
            mFusedLocationProviderClient!!.requestLocationUpdates(
                locationRequest,
                locationCallback,
                null
            )
            mMap?.let{
                it.isMyLocationEnabled = true
            }
        }
    }

    override fun onStop() {
        super.onStop()
        mFusedLocationProviderClient?.let{
            it!!.removeLocationUpdates(locationCallback)
            Log.d(TAG, "onStop : removeLocationUpdates")
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        mFusedLocationProviderClient?.let{
            it.removeLocationUpdates(locationCallback)
            Log.d(TAG, "onDestroy : removeLocationUpdates")
        }
    }

    companion object {
        private const val DEFAULT_ZOOM = 15
        private const val PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION = 1
        private const val GPS_ENABLE_REQUEST_CODE = 2001
        private const val UPDATE_INTERVAL_MS = 1000 * 60 * 15 // 1분 단위 시간 갱신
        private const val FASTEST_UPDATE_INTERVAL_MS = 1000 * 30 // 30초 단위로 화면 갱신
        private const val KEY_CAMERA_POSITION = "camera_position"
        private const val KEY_LOCATION = "location"
    }
}




블로그 이미지

Link2Me

,
728x90

위험권한을 편리하게 체크해주는 tedpermission 라이브러리를 코틀린으로 변환한 것과 뒤로 가기를 두번 연속 누르면 종료되는 함수를 적어둔다.

구글지도를 간단하게 구현할 목적이라 dependencies에 구글 라이브러리가 추가되어 있다.

 

앱 build.gradle

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

android {
    compileSdkVersion 30
    buildToolsVersion "30.0.0"

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

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

}

dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])
    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    implementation 'androidx.core:core-ktx:1.3.0'
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'

    implementation 'com.google.android.gms:play-services-maps:17.0.0'
    implementation 'com.google.android.gms:play-services-location:17.0.0'
    implementation 'gun0912.ted:tedpermission:2.0.0'
    implementation 'com.android.volley:volley:1.1.1'
    implementation 'com.google.android.material:material:1.1.0'

}

2021.7월 확인 사항

implementation 'io.github.ParkSangGwon:tedpermission:2.3.0' 로 변경되었더라.

 

AndroidManifest.xml

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

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

    <!-- 도시 블록 내에서 정확한 위치(네트워크 위치) -->
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

    <!-- 정확한 위치 확보(네트워크 위치 + GPS 위치) -->

    <application
        android:allowBackup="false"
        android:icon="@drawable/icon"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme"
        android:usesCleartextTraffic="true">
        <activity android:name=".MapsActivity"></activity>
        <activity
            android:name=".SplashActivity"
            android:theme="@style/AppTheme.NoActionBar">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

        <meta-data
            android:name="com.google.android.geo.API_KEY"
            android:value="@string/google_maps_key" />

        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="${applicationId}.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/provider_paths"
                tools:replace="android:resource" />
        </provider>
    </application>

</manifest>

 

SplashActivity.kt

import android.Manifest
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.provider.Settings
import android.util.Log
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.gun0912.tedpermission.PermissionListener
import com.gun0912.tedpermission.TedPermission

class SplashActivity : AppCompatActivity() {
    var mContext: Context? = null
    private val SPLASH_TIME_OUT: Long = 2000 // 2 sec

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        mContext = this@SplashActivity;
        checkPermissions()
    }

    var permissionlistener: PermissionListener = object : PermissionListener {
        override fun onPermissionGranted() {
            initView()
        }

        override fun onPermissionDenied(deniedPermissions: MutableList<String?>?) {
            Toast.makeText(
                this@SplashActivity,
                "권한 허용을 하지 않으면 서비스를 이용할 수 없습니다.",
                Toast.LENGTH_SHORT
            ).show()
        }
    }

    private fun checkPermissions() {
        if (Build.VERSION.SDK_INT >= 26) { // 출처를 알 수 없는 앱 설정 화면 띄우기
            val pm: PackageManager = mContext!!.getPackageManager()
            Log.e("PackageName", packageName)
            if (!pm.canRequestPackageInstalls()) {
                startActivity(
                    Intent(
                        Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES,
                        Uri.parse("package:$packageName")
                    )
                )
            }
        }
        if (Build.VERSION.SDK_INT >= 23) { // 마시멜로(안드로이드 6.0) 이상 권한 체크
            TedPermission.with(mContext)
                .setPermissionListener(permissionlistener)
                .setRationaleMessage("앱을 이용하기 위해서는 접근 권한이 필요합니다")
                .setDeniedMessage("앱에서 요구하는 권한설정이 필요합니다...\n [설정] > [권한] 에서 사용으로 활성화해주세요.")
                .setPermissions(
                    Manifest.permission.ACCESS_FINE_LOCATION,
                    Manifest.permission.ACCESS_COARSE_LOCATION
                )
                .check()
        } else {
            initView()
        }
    }

    private fun initView() {
        Handler().postDelayed({
            startActivity(Intent(this, MapsActivity::class.java))
            finish()  // close this activity
        }, SPLASH_TIME_OUT)
    }

}

 

import android.app.Activity
import android.os.Build
import android.widget.Toast

class BackPressHandler(private val activity: Activity) {
    private var backKeyPressedTime: Long = 0
    private var toast: Toast? = null
    fun onBackPressed() {
        if (isAfter2Seconds) {
            backKeyPressedTime = System.currentTimeMillis()
            // 현재시간을 다시 초기화
            toast = Toast.makeText(
                activity,
                "\'뒤로\'버튼을 한번 더 누르면 종료됩니다.",
                Toast.LENGTH_SHORT
            )
            toast!!.show()
            return
        }
        if (isBefore2Seconds) {
            appShutdown()
            toast!!.cancel()
        }
    }

    // 2초 지났을 경우
    private val isAfter2Seconds: Boolean
        private get() = System.currentTimeMillis() > backKeyPressedTime + 2000

    // 2초가 지나지 않았을 경우
    private val isBefore2Seconds: Boolean
        private get() = System.currentTimeMillis() <= backKeyPressedTime + 2000

    private fun appShutdown() {
        // 홈버튼 길게 누르면 나타나는 히스토리에도 남아있지 않고 어플 종료
        if (Build.VERSION.SDK_INT >= 21) activity.finishAndRemoveTask() else activity.finish()
        System.exit(0)
    }
}

 

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity

class MapsActivity : AppCompatActivity() {
    private var backPressHandler: BackPressHandler? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_maps)

        backPressHandler = BackPressHandler(this); // 뒤로 가기 버튼 이벤트
    }

    override fun onBackPressed() {
        backPressHandler!!.onBackPressed()
    }
}

 

var a = readLine()?.capitalize()
// ?. : 안전 호출 연산자


var a = readLine()!!.capitailize()
// !! : non-null 단언 연산자. null이 될 수 없다는 것을 단언하는 연산자다.

 

블로그 이미지

Link2Me

,
728x90

아래 코드는 소스 설치를 위해서 만들어서 사용했던 스크립트 파일이다.

CentOS 버전 업데이트 방법
yum update

# IP주소 설정정보 확인
ifconfig

# 아이피,서브넷 마스크,(게이트웨이)
cat /etc/sysconfig/network-scripts/ifcfg-eth0

# 컴퓨터이름,(게이트웨이)
cat /etc/sysconfig/network

# DNS를 위한 네임서버를 지정
cat /etc/resolv.conf
cat /etc/hosts

# 네트워크 살았는지 죽었는지 테스트
ping 168.126.63.1

== 설정 작업
vi /etc/sysconfig/network-scripts/ifcfg-eth0
DEVICE=eth0
BOOTPROTO=none
HWADDR=00:1A:64:98:AF:12
ONBOOT=yes
NETMASK=255.255.255.64
IPADDR=100.100.67.101
GATEWAY=100.100.67.65
TYPE=Ethernet
DNS1=168.126.63.1
DNS2=168.126.63.2

# 네트워크 재시작
service network restart

=================================
# httpd 데몬 확인
ps -ef | grep httpd
# MySQL 데몬 확인
ps -ef | grep mysql

# 아파치(Apache) 시작
/etc/init.d/httpd start
/etc/init.d/httpd stop

# MySQL 시작
/etc/init.d/mysqld start
/etc/init.d/mysqld stop

=================================
# RPM이 설치 되어 있는지 확인
rpm -qa httpd php mysql
rpm -qa | grep http
rpm -qa | grep mysql
rpm -qa | grep php

# 기존 패키지 삭제
yum remove -y httpd mysql php

# 설치된 것이 제대로 제거되었는지 확인
rpm -qa httpd php mysql
rpm -qa | grep http
rpm -qa | grep mysql
rpm -qa | grep php

# 제거되지 않고 남아 있는 것이 있다면 rpm -e --nodeps (의존성에 개의치 않고 삭제) 를 붙여준다.
rpm -e --nodeps jakarta-commons-httpclient-3.0-7jpp.4.el5_10
rpm -e --nodeps php-cli-5.1.6-45.el5_11
rpm -e --nodeps php-common-5.1.6-45.el5_11

전부 지워졌는지 확인하고 나서 APM 소스 설치에 들어간다.

# 모든 웹서버 데몬 죽이기
killall httpd

# 기존 패키지 stop
/etc/init.d/mysqld stop

# MySQL 데몬 확인
ps -ef | grep mysql
# 구동중인 프로세스 죽이기
kill -9 프로세스번호

=========================================================
yum install -y make gcc g++ gcc-c++ autoconf automake libtool pkgconfig findutils oepnssl openssl-devel
yum install -y openldap-devel pcre-devel libxml2-devel lua-devel curl curl-devel libcurl-devel flex
yum install -y zlib zlib-devel cpp perl bison freetype freetype-devel freetype-utils
yum install -y ncurses-devel libtermcap-devel bzip2-devel
yum install -y libjpeg libjpeg-devel libjpeg-turbo-devel gd gd-devel gdbm-devel php-mbstring libexif-devel
yum install -y libmcrypt libmcrypt-devel libvpx libvpx-devel libXpm libXpm-devel icu libicu libicu-devel
yum install -y t1lib t1lib-devel gmp-devel mhash* gettext gettext-devel libtidy libtidy-devel libxslt libxslt-devel
yum install -y libedit-devel libc-client libc-client-devel pam-devel readline-devel libpng libpng-devel krb5-devel db4-devel expat*
yum install -y xml*
yum install -y gd*
yum install -y pango*
yum install -y php-devel phpize
yum install -y net-snmp net-snmp-utils
=============================================

# 설치할 디렉토리 만들기
cd /usr/local/
mkdir APM

#################### MySQL 소스 설치 #################
# cmake 설치
https://src.fedoraproject.org/lookaside/extras/cmake/ 적정버전을 다운로드 한다.
버전이 너무 높아도 설치가 안된다.
# 1. cmake 압축 풀고 설치 (기존 설치된 것 있으면 지우고 자동 재설치)
cd /usr/local/APM/
rm -rf cmake-2.8.12/
tar -xzf cmake-2.8.9.tar.gz
cd cmake-2.8.9
./bootstrap
make && make install

https://src.fedoraproject.org/lookaside/extras/community-mysql/ 에서 최신버전을 구한다.
# 2. mysql 설치
cd /usr/local/APM/
rm -rf mysql-5.6.40/
tar -xvzf mysql-5.6.40.tar.gz
cd mysql-5.6.40
cmake \
 -DCMAKE_INSTALL_PREFIX=/usr/local/mysql \
 -DDEFAULT_CHARSET=utf8 \
 -DDEFAULT_COLLATION=utf8_general_ci \
 -DWITH_EXTRA_CHARSETS=all \
 -DENABLED_LOCAL_INFILE=1 \
 -DMYSQL_DATADIR=/usr/local/mysql/data \
 -DMYSQL_USER=mysql \
 -DWITH_INNOBASE_STORAGE_ENGINE=1 \
 -DWITH_ARCHIVE_STORAGE_ENGINE=1 \
 -DWITH_BLACKHOLE_STORAGE_ENGINE=1 \
 -DWITH_PERFSCHEMA_STORAGE_ENGINE=1 \
 -DMYSQL_UNIX_ADDR=/usr/local/mysql/mysql.sock \
 -DSYSCONFIGDIR=/etc \
 -DMYSQL_TCP_PORT=3306

make && make install

=== PATH 추가 ===
## /root 디렉토리로 이동
cd
vi .bash_profile
# PATH 경로에 추가
PATH=$PATH:$HOME/bin:/usr/local/mysql/bin
저장후
source .bash_profile

cd /usr/local/mysql/
# 시스템에 mysql 그룹 생성
groupadd mysql
useradd -g mysql mysql
chown -R mysql:mysql /usr/local/mysql
# mysql 설치 디렉토리에 대한 mysql 권한 부여

# mysql 설정 파일 복사 (반드시 복사해야 정상적으로 처리됨)
cd support-files 한 다음 ll 해서 파일명 확인 필요
#cp support-files/my-medium.cnf /etc/my.cnf
cd ..
cp support-files/my-default.cnf /etc/my.cnf

# 기본 DB 생성
./scripts/mysql_install_db --user=mysql --datadir=/usr/local/mysql/data


# 시스템이 재부팅되어도 mysql 이 자동 실행되도록 설정
# 기존 설치된 파일이 있으면 overwrite 하면 됨
cp support-files/mysql.server /etc/init.d/mysqld

vi /etc/init.d/mysqld
datadir=/usr/local/mysql/data
로 변경하고 저장 (데이터가 저장되는 기본 디렉토리)

# 등록이 잘 되었는지 확인(자동시작)
chkconfig --add mysqld
#chkconfig mysql on -- 나중에 추가했음
chkconfig --list | grep mysqld

# MySQL 서버와 프로그램을 연결해 줄 소켓을 링크 파일로 생성한다.
ln -s /var/lib/mysql/mysql.sock /usr/local/mysql/mysql.socket

==== MySQL 구동
/etc/init.d/mysqld start

MYSQL root 암호 설정
mysql -uroot -p
password: 그냥 엔터키 (패스워드가 설정되지 않았을 때, 설정되어 있으면 접속 불가)
use mysql;
update user set password=password('암호') where user='root';
flush privileges;
select host, user, password from user;
quit

################################################################
=========== Apache 소스 설치 ===============
# https://apr.apache.org/download.cgi 에서 apr-1.5.2.tar.gz / apr-util-1.5.4.tar.gz 파일 다운로드
3. 아파치 설치
cd /usr/local/APM/
rm -rf apr-1.5.2/
#wget http://mirror.apache-kr.org/apr/apr-1.5.2.tar.gz
tar -xvzf apr-1.5.2.tar.gz
cd apr-1.5.2
./configure --prefix=/usr/local/apr
make && make install

cd /usr/local/APM/
rm -rf apr-util-1.5.4/
#wget http://mirror.apache-kr.org/apr/apr-util-1.5.4.tar.gz
tar -xvzf apr-util-1.5.4.tar.gz
cd apr-util-1.5.4
./configure --prefix=/usr/local/aprutil --with-apr=/usr/local/apr/ --with-openssl
make && make install

# pcre(Perl Compatible Regular Expressions)
https://sourceforge.net/projects/pcre/ 에서 최신버전 다운로드 가능하나 버전에 맞는 걸 설치해야 한다.

cd /usr/local/APM/
rm -rf pcre-8.38/
#wget http://downloads.sourceforge.net/project/pcre/pcre/8.38/pcre-8.38.tar.gz
tar -xvzf pcre-8.38.tar.gz
cd pcre-8.38
./configure --prefix=/usr/local/pcre
make && make install


https://httpd.apache.org/download.cgi 에서 최신버전 다운로드 또는 아래 버전 다운로드
https://www.icewalkers.com/linux/software/58450/Apache-Server.html 에서 맞는 버전 다운로드
cd /usr/local/APM/
rm -rf httpd-2.4.25/
#wget http://mirror.apache-kr.org/httpd/httpd-2.4.25.tar.gz
tar -xvzf httpd-2.4.25.tar.gz
cd httpd-2.4.25
./configure \
 --prefix=/usr/local/apache \
 --enable-module=so \
 --enable-mods-shared=most \
 --enable-maintainer-mode \
 --enable-deflate \
 --enable-headers \
 --enable-proxy \
 --enable-proxy-http \
 --enable-proxy-ajp \
 --enable-proxy-balance
 --enable-ssl \
 --enable-rewrite-all \
 --with-apr=/usr/local/apr \
 --with-apr-util=/usr/local/aprutil/ \
 --with-pcre=/usr/local/pcre
make && make install

# Uninstall Apache on a CentOS / Red Hat Enterprise Linux (RHEL)
yum erase httpd httpd-tools apr apr-util

컴파일 실수가 발생하면, 해당 디렉토리에서 바로 make distclean 으로 삭제한다.
그리고 나서 다시 실행한다.

cp /usr/local/apache/bin/apachectl /etc/init.d/httpd

# 리부팅시 아파치 프로세스를 자동으로 띄운다
cd /usr/local/APM/
ln -s /init.d/httpd /etc/rc0.d/K90httpd
ln -s /init.d/httpd /etc/rc3.d/S89httpd
ln -s /init.d/httpd /etc/rc5.d/S89httpd

vi /etc/init.d/httpd
둘째줄에서 i 를 눌러 Insert 모드로 변경한 다음에 아래 5줄을 복사하여 붙여넣기하여 삽입하고 저장
# chkconfig: 2345 90 90
# description: init file for Apache server daemon
# processname: /usr/local/apache/bin/apachectl
# config: /usr/local/apache/conf/httpd.conf
# pidfile: /usr/local/apache/logs/httpd.pid

cd /usr/local/APM/httpd-2.4.25
chkconfig --add httpd
chkconfig --list httpd
chkconfig --level 2345 httpd on

vi /usr/local/apache/conf/httpd.conf
User daemon 과 Group daemon 라인을 찾아서 daemon 대신에 nobody 로 변경
#ServerName www.example.com:80 아래줄에 추가
ServerName localhost
#처럼 해당 서버주소이름으로 변경. 도메인이 있으면 도메인으로 변경처리

# Options Indexes FollowSymLinks 에서 Indexes 제거해야 디렉토리 경로 보이는 것 방지됨
# 그리고 아래처럼 변경해줌
DocumentRoot "/usr/local/apache/htdocs"
<Directory "/usr/local/apache/htdocs">
    Options IncludesNoExec
    AllowOverride None
    Order deny,allow
    Allow from all
    Require all granted
</Directory>

아파치(Apache) 시작
/etc/init.d/httpd start

http://100.100.67.101/ --> Web browser 에서 It works! 나오면 성공적인 설치되었다는 표시

----It works 화면이 안될때 오류사항 해결 ------
/etc/sysconfig/iptables 에서 80포트 방화벽 해제

#############################
======= PHP 소스 설치 =======
4. PHP 설치
cd /usr/local/APM/
rm -rf libiconv-1.16/
tar -xvzf libiconv-1.16.tar.gz
cd libiconv-1.16
./configure --prefix=/usr/local/iconv
make && make install

cd /usr/local/APM/
tar -xvzf libmcrypt-2.5.8.tar.gz
cd libmcrypt-2.5.8
./configure --prefix=/usr/local/
make && make install

# https://www.php.net/releases/index.php 에서 php-5.6.40.tar.gz 다운로드

# \ 다음에 공백이 있으면 에러 발생하므로 주의한다.

cd /usr/local/APM/
tar -xvzf php-5.6.40.tar.gz
cd php-5.6.40
./configure --prefix=/usr/local/php \
 --with-config-file-path=/usr/local/apache/conf \
 --with-apxs2=/usr/local/apache/bin/apxs \
 --with-mysql=/usr/local/mysql \
 --with-mysqli=/usr/local/mysql/bin/mysql_config \
 --with-pdo-mysql=shared,/usr/local/mysql \
 --enable-mysqlnd \
 --with-mysql=mysqlnd \
 --with-mysqli=mysqlnd \
 --with-pdo-mysql=mysqlnd \
 --with-iconv=/usr/local/iconv \
 --with-curl=/usr/lib \
 --enable-mod-charset \
 --enable-sigchild \
 --with-libxml-dir \
 --with-openssl \
 --with-mhash \
 --with-mcrypt \
 --with-gd \
 --with-zlib \
 --with-zlib-dir \
 --with-bz2 \
 --with-gdbm \
 --with-freetype-dir=/usr/local/lib \
 --with-jpeg-dir=/usr/local/php \
 --with-png-dir \
 --with-gettext \
 --with-kerberos \
 --with-mcrypt=/usr/local/ \
 --enable-mbstring \
 --with-regex=php \
 --enable-sockets \
 --enable-calendar \
 --enable-dba \
 --enable-exif \
 --enable-ftp \
 --enable-sysvmsg \
 --enable-sysvsem \
 --enable-sysvshm \
 --enable-wddx \
 --enable-zip

make && make install

//위 명령 실행시 아래와 같은 오류가 난다면 해결책 한 후 위에 명령 다시 실행.
해결책 : # yum -y install curl* 또는 yum -y install curl & yum -y install curl-devel

# php.ini 샘플파일을 환경설정 디렉토리에 복사
cp php.ini-development /usr/local/apache/conf/php.ini

# 아파치와 연동을 위해 추가로 httpd.conf 3군데 수정
vi /usr/local/apache/conf/httpd.conf
<IfModule dir_module>
DirectoryIndex index.html index.php   =>index.php추가
</IfModule>

<IfModule log_config_module>
    LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
    LogFormat "%h %l %u %t \"%r\" %>s %b" common

    <IfModule logio_module>
      # You need to enable mod_logio.c to use %I and %O
      LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combinedio
    </IfModule>

    # 그림 이미지, css, js는 기록하지 않게 처리
    SetEnvIfNoCase Request_URI "\.(jpg|png|gif|css|ico|js|swf)$" notloglist

    #CustomLog "logs/access_log" common
    #CustomLog "logs/access_log" combined
    #CustomLog "|/아파치 설치 경로/bin/rotatelogs /로그를 저장할 경로/파일명 %Y%m%d%H 86400 +540" combined
    # 날짜별로 로그기록 남기기
    CustomLog "|/usr/local/apache/bin/rotatelogs /usr/local/apache/logs/access_log_%Y%m%d 86400 +540" combined env=!notloglist
</IfModule>

//아래 두줄을 <IfModule mime_module>에 추가
    AddType application/x-httpd-php .php .html
    AddType application/x-httpd-php-source .phps
<IfModule mime_module>
    AddType application/x-compress .Z
    AddType application/x-gzip .gz .tgz
    #PHP Enable
    AddType application/x-httpd-php .php .html
    AddType application/x-httpd-php-source .phps
</IfModule>

# lib, inc 등을 사용할 경우에는 반드시 AddType application/x-httpd-php .php .html .inc .lib .xml 처럼 설정해줘야 한다.
# 이 부분을 빠뜨리면 웹페이지 상에서 텍스트로 인식하여 파일의 내용이 그대로 노출된다.
# 파일 확장자가 htm 으로 끝나면 이 부분도 추가를 해줘야 한다.
# 코드가 뭔지 모르게 하고 싶다면 .do 를 사용하는 것도 방법 중의 하나이다.


###### php.ini 수정사항  ###########
vi /usr/local/apache/conf/php.ini
시간설정 (앞에 ;지울것) 을 지우고 아래 내용 추가
date.timezone = "Asia/Seoul"
;<? 와 <?php 둘다 인식하게 함
short_open_tag = On
; PHP 가 받아들이는 POST data 최대 크기
;default 8M 로 되어 있음, upload_max_filesize 보다 크게 설정해야 함
post_max_size = 60M
memory_limit = 128M
upload_max_filesize = 50M

; error_reporting 세팅
;개발환경
;error_reporting  =  E_ALL
; 실제 환경, 경고메시지 출력 안됨
;error_reporting = E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED
; 해킹의 먹이감이 되므로 평소에는 Off 로 해둔다. 에러가 있을 경우 디버깅 가능하도록 에러 메세지를 출력해준다.
display_errors = Off
; 평소에는 해제(Off)한다. 개발환경에서는 디버깅 가능하도록 On하면 에러 메세지를 출력해준다.
display_startup_errors = Off
log_errors = On
; 에러 로그 파일 설정이 없으면 Apache 로그 파일에 기록된다.
; 퍼미션 설정해서 /home/user/log/php.log 과 같이 설정
;error_log = filename

; register_globals = On 으로 하면, PHP 스크립트의 변수 값을 임의로 변경할 수 있는 취약성이 있다.
; off 로 설정한 후, $_GET, $POST 를 사용해서 사용자가 전달한 값을 얻어야 한다.
register_globals = Off

;UTF-8 로 문자 인코딩 설정하기
;해당 부분을 찾아서 아래와 같이 수정
default_charset = "UTF-8"
[mbstring]
mbstring.language = Korean
mbstring.internal_encoding = UTF-8
mbstring.http_input = pass
mbstring.http_output = pass
mbstring.encoding_translation = Off

5. 퍼미션 설정 및 점검
# 일반계정 유출 등에 의해 파일이 변조될 수 있으므로 확인하여 조치한다.
# Apache and PHP 환경설정 파일 퍼미션 변경
# httpd.conf 파일의 경로 찾기
find / -name httpd.conf
chmod 640 /usr/local/apache/conf/httpd.conf
chown root:root /usr/local/apache/conf/httpd.conf
chmod 640 /usr/local/apache/conf/php.ini
chown root:root /usr/local/apache/conf/php.ini
# Document Root 디렉토리 설정 확인 및 변경
# Document Root 디렉토리는 755, 파일은 644로 되어 있는지 확인하고 변경
cd /usr/local/apache/htdocs
# 가능하면 home/httpd/ 하위 디렉토리에 Document Root 가 설정되도록 변경(httpd.conf 파일내에서)
chown 작성자(nobody) /usr/local/apache/htdocs
chgrp 작성자그룹 /usr/local/apache/htdocs
chmod 755 /usr/local/apache/htdocs

## httpd restart
/etc/init.d/httpd stop
/etc/init.d/httpd start
/etc/init.d/mysqld start

## 정상적으로 설치되었는지 여부 확인
http://100.100.67.101/info.php

index.php 에 아래 3줄을 추가해서 정상적으로 보이면 APM 설치는 정상적으로 잘 되었다고 보면 된다.
<?php
phpinfo();
?>


블로그 이미지

Link2Me

,

MySQL DB 백업

SQL 2020. 6. 13. 00:00
728x90

MySQL/MariaDB 백업, 복구에 대한 명령어를 적어둔다.


## 모든 DB 백업
mysqldump -uroot -p --all-databases > alldbbackup.sql

## 모든 DB 복구
mysql -u root -p < alldbbackup.sql


## 특정 DB 백업

mysqldump -uroot -p --databases kchart > kchart.sql


# kchart DB에서 특정 테이블 여러개 백업
mysqldump -uroot -p kchart allowHosts > allowHosts.sql
mysqldump -uroot -p kchart allowHosts > allowHosts.sql
mysqldump -uroot -p kchart allowHosts SYS_MEMBER > sys_allow_backup.sql

## DB 접속

mysql -u root -p


// DB 생성
create database kchart default character set utf8;



## root 비밀번호 설정
use mysql;
update user set password=password('설정할root비번') where user='root';
flush privileges;

## DB 사용권한 등록

## username, password, dbname을 각각 상황에 맞게 수정한다.

use mysql;
create user username@localhost identified by 'password';
grant all privileges on dbname.* to username@localhost;
flush privileges;


### 사용자 현황 검색

use mysql;
select host, user, password from user;



## DB 백업본 업로드
mysql -u root -p
비밀번호 입력
use kchart;
source partmember.sql;



블로그 이미지

Link2Me

,
728x90

네이버 지도를 이용하기 위해서는 네이버 클라우드 플랫폼에 환경 설정 정보를 등록해야 한다.

 

https://www.ncloud.com/ 에 로그인 하고 나서 결제 연동 정보 등을 등록하고 이용하고자 하는 API를 선택한다.

 

기본 초기 설정 정보는 https://imweb.me/faq?mode=view&category=29&category2=37&idx=48381 참조하시라.

 

 

 

 

 

 

Web URL 또는 Android 앱 패키지 명을 등록하고 저장하면 앱 또는 Android에서 naver map aip를 이용할 수 있다.

 

 

 <meta-data
   android:name="com.naver.maps.map.CLIENT_ID"
   android:value="클라이언트값" />

 

Client ID 값을 복사하여 클라이언트값에 붙여넣기한다.

 

네이버 지도 소개

https://navermaps.github.io/android-map-sdk/guide-ko/0.html

 

네이버지도 데모파일

https://github.com/navermaps/android-map-sdk

 

오픈 소스를 활용한 지도 구글 서비스 개발 (서버 : Node.js)

https://github.com/kairo96/

이 자료는 책을 구입해서 같이 봐야 이해하기 쉽고 도움된다.

http://www.kyobobook.co.kr/product/detailViewKor.laf?ejkGb=KOR&mallGb=KOR&barcode=9791185890968&orderClick=LAG&Kc=

 

블로그 이미지

Link2Me

,
728x90

구글에서 제공하는 함수를 이용하여 거리를 계산하는 걸 시도했으나 실패를 했다.

API_KEY를 새로 생성해서도 해보고 기존 API를 이용해서도 해보았으나, 역시 잘 안된다.




위와 같이 설정하면 얼추 동작이 되는 거 같기도 하다.



private void getMapDistanceData(String latitude, String longitude) {
    // 1. RequestQueue 생성 및 초기화
    RequestQueue requestQueue = Volley.newRequestQueue(mContext);
    String url = "https://maps.googleapis.com/maps/api/distancematrix/json?units=metric&mode=transit&origins=37.541,126.986&destinations=35.1595454,126.8526012&region=KR&key=API_KEY";

    // 2. Request Obejct인 StringRequest 생성
    StringRequest request = new StringRequest(Request.Method.POST, url,
            new Response.Listener<String>() {
                @Override
                public void onResponse(String response) {
                    Log.d("result", "[" + response + "]");
                    showJSONList(response);
                }
            },
            new Response.ErrorListener() {
                @Override
                public void onErrorResponse(VolleyError error) {
                    Log.d("error", "[" + error.getMessage() + "]");
                }
            }) {
        @Override
        protected Map<String, String> getParams() throws AuthFailureError {
            Map<String, String> params = new HashMap<>();
            params.put("keyword", Value.encrypt(Value.URLkey()));
            params.put("reg_date", Value.encrypt(Value.Today()));
            return params;
        }
    };

    // 3) 생성한 StringRequest를 RequestQueue에 추가
    requestQueue.add(request);
}
 


두 좌표(위도, 경도)간의 거리결과를 얻는 것이 목적이라 위 코드는 포기하고 PHP 코드를 검색해서 만들어서 사용했더니 만족스런 결과가 나온다.

function getDistance($lat1, $lng1, $lat2, $lng2) { // 위, 경도 거리 계산
    $earth_radius = 6371;
    $dLat = deg2rad($lat2 - $lat1);
    $dLon = deg2rad($lng2 - $lng1);
    $a = sin($dLat/2) * sin($dLat/2) + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * sin($dLon/2) * sin($dLon/2);
    $c = 2 * asin(sqrt($a));
    $d = $earth_radius * $c;
    return $d * 1000; // m 거리 반환
    //return $d; // km 거리 반환
}

function StDistance($StID, $lat1, $lng1){
    if(strlen($lat1) < 1 || strlen($lng1) < 1) {
        return "X";
    }
    $sql = "select latitude,longitude from Station where StID=?";
    $params = array($StID);
    $stmt = $this->db->prepare($sql);
    $stmt->execute($params);
    if($row = $stmt->fetch()){
        return $this->getDistance($lat1, $lng1, $row[0], $row[1]);
    } else {
        return "X";
    }
}


DB에 저장된 위도, 경도값과 현재 위치에서 반환한 위도, 경도 값의 차이를 기준으로 거리 계산을 하도록 하면 원하는 결과를 얻을 수 있다.


블로그 이미지

Link2Me

,