728x90

Retrofit 라이브러리를 사용하면 Session 처리 OK이고, 코드가 심플한 점도 장점이다.

MVVM 적용한 코드로 된 강좌들을 접하다보니 안할 수가 없어 기존 코드를 MVVM 코드로 정리를 해 볼 필요성이 있어 코드를 수정하여 구현해 보고 나서 동작에 대한 개념이 이해되었다.

 

디자인 패턴은 최고의 개발자들이 만들어낸 솔루션이다. 또한, 오랫동안 사용되어 오면서 안정성 검증이 완료된 솔루션으로 심지어 디자인 패턴은 문서화도 잘 되어있다.

 

MVVM 패턴
MVVM 패턴은 마틴 파울러의 Presentation 모델 패턴에서 파생된 디자인 패턴이다. 
MVVM 패턴의 목표는 비즈니스 로직과 프레젠테이션 로직을 UI로부터 분리하는 것이다. 
비즈니스 로직과 프레젠테이션 로직을 UI로부터 분리하게 되면, 테스트, 유지 보수, 재사용이 쉬워진다. 

- Model : 어플리케이션에서 사용되는 데이터와 그 데이터를 처리하는 부분.
- View : 사용자에서 보여지는 UI 부분.
- View Model : View를 표현하기 위해 만들어진 View를 위한 Model.

  View와는 Binding을 하여 연결후 View에게서 액션을 받고 또한 View를 업데이트한다.

 

1. View에 입력이 들어오면 ViewModel에게 명령을 한다.
2. ViewModel은 필요한 데이터를 Model에게 요청한다.
3. Model은 ViewModel에게 요청된 데이터를 응답한다.
4. ViewModel은 응답 받은 데이터를 가공해서 저장한다.
5. View는 ViewModel과의 Data Binding으로 인해 자동으로 갱신된다.

 

 

Retrofit 라이브러리를 활용한 MVVM 적용 Login 샘플 코드 예제이다.

 

public class LoginActivity extends AppCompatActivity {
    private final String TAG = this.getClass().getSimpleName();
    Context mContext;
    private ActivityLoginBinding binding;
 
    private BackPressHandler backPressHandler;
    private AuthViewModel viewModel;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //setContentView(R.layout.activity_login);
        binding = ActivityLoginBinding.inflate(getLayoutInflater());
        View view = binding.getRoot();
        setContentView(view);
 
        backPressHandler = new BackPressHandler(this);
        mContext = LoginActivity.this;
 
        Utils.viewShowHide(binding.btnLogin, false);
        initView();
    }
 
    private void initView() {
        // viewModel init
        viewModel = new ViewModelProvider(this).get(AuthViewModel.class);
 
        // viewModel observe
        viewModel.getLoginResultLiveData().observe(thisnew Observer<LoginResult>() {
            @Override
            public void onChanged(LoginResult loginResult) {
                if(loginResult.getStatus().contains("success")){
                    Utils.goNextActivity(mContext,MainActivity.class);
                    finish();
                } else {
                    Utils.showAlert(mContext, loginResult.getStatus(), loginResult.getMessage());
                }
            }
        });
 
        // addTextChangedListener
        binding.etPw.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
 
            }
 
            @Override
            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
 
            }
 
            @Override
            public void afterTextChanged(Editable editable) {
                String userID = binding.etId.getText().toString().trim();
                String password = binding.etPw.getText().toString().trim();
                if (!userID.isEmpty() && !password.isEmpty())
                    Utils.viewShowHide(binding.btnLogin, true);
                // userID 와 패스워드 모두 입력하면 로그인 버튼이 활성화된다.
            }
        });
 
        // button setOnClickListener
        binding.btnLogin.setOnClickListener(view -> {
            login();
        });
 
    }
 
    private void login() {
        String userID = binding.etId.getText().toString().trim();
        String password = binding.etPw.getText().toString().trim();
        String uID = Utils.getDeviceId(mContext); // 스마트폰 고유장치번호
        String mfoneNO = Utils.getPhoneNumber(mContext); // 스마트폰 전화번호
        viewModel.login(userID, password,uID,mfoneNO);
        /*
        로그인 전달 변수 수정 시
        AutoViewModel.java 와 AutoRepository.java 파일 모두 수정
        */
    }
 
 
    @Override
    public void onBackPressed() {
        //super.onBackPressed();
        backPressHandler.onBackPressed();
    }
}

 

 

public class AuthViewModel extends ViewModel {
    private AuthRepository repository;
    private LiveData<LoginResult> _liveData;
 
    public AuthViewModel() {
    }
 
    public void login(String userID, String password, String uID, String mfoneNO) {
        repository.loginRepo(userID, password, uID, mfoneNO);
    }
 
    public LiveData<LoginResult> getLoginResultLiveData() {
        if (_liveData == null) {
            repository = new AuthRepository();
            _liveData = repository.getLoginResultLiveData();
        }
        return _liveData;
    }
 
}
 

 

 

 

public class AuthRepository {
    private RetrofitService loginService;
    private MutableLiveData<LoginResult> loginResultLiveData;
 
    public AuthRepository() {
        loginResultLiveData = new MutableLiveData<>();
        loginService = RetrofitAdapter.getClient(RetrofitURL.BaseUrl).create(RetrofitService.class);
    }
 
    public void loginRepo(String userID, String password, String uID, String mfoneNO){
        loginService.Login(userID,password,uID,mfoneNO)
                .enqueue(new Callback<LoginResult>() {
                    @Override
                    public void onResponse(Call<LoginResult> call, Response<LoginResult> response) {
                        loginResultLiveData.postValue(response.body());
                    }
 
                    @Override
                    public void onFailure(Call<LoginResult> call, Throwable t) {
                        loginResultLiveData.postValue(null);
                    }
                });
    }
 
    public LiveData<LoginResult> getLoginResultLiveData() {
        return loginResultLiveData;
    }
}

 

MVVM 기반 코드는 위 3개의 파일이 핵심인 거 같다.

전체 소스 코드는 Github 에 올려 두었다.

https://github.com/jsk005/JavaProjects/tree/master/retrofit_mvvm_login

 

GitHub - jsk005/JavaProjects: 자바 기능 테스트

자바 기능 테스트. Contribute to jsk005/JavaProjects development by creating an account on GitHub.

github.com

 

 

아래 GitHub를 참고하면 유용한 자료들이 많다.

https://github.com/probelalkhan?tab=repositories 

 

probelalkhan - Overview

probelalkhan has 147 repositories available. Follow their code on GitHub.

github.com

 

블로그 이미지

Link2Me

,
728x90

RxAndroid and Retrofit 를 활용하여 파일을 다운로드하고 PDF Viewer 처리하는 코드 예제이다.

 

build.gradle:

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" // 코틀린 혼용 사용을 위해 추가
 
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
    implementation 'com.squareup.okhttp3:logging-interceptor:4.4.0'
    implementation 'com.squareup.okhttp3:okhttp-urlconnection:4.9.0'
 
    // ReactiveX : Observable에서 보낸 이벤트를 Operator에서 가공하고 Subscribe에서 받는 것
    implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'
    implementation 'io.reactivex.rxjava3:rxjava:3.1.5'
    implementation "com.github.akarnokd:rxjava3-retrofit-adapter:3.0.0"
 
}

 

RetrofitService.java:

import io.reactivex.rxjava3.core.Observable;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.Response;
import retrofit2.http.Field;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.GET;
import retrofit2.http.POST;
import retrofit2.http.Streaming;
import retrofit2.http.Url;
 
public interface RetrofitService {
    // POST 방식으로 데이터를 주고 받을 때 넘기는 변수는 Field 라고 해야 한다.
 
    @FormUrlEncoded
    @POST(RetrofitUrl.PdfData)
    Call<PdfResult> getPdfData(
            @Field("keyword"String keyword,
            @Field("search"String search
    );
 
    @Streaming
    @GET
    Observable<Response<ResponseBody>> downloadFile(@Url String fileUrl);
 
}
 

 

RetrofitAdapter.java:

import hu.akarnokd.rxjava3.retrofit.RxJava3CallAdapterFactory;
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
 
public class RetrofitAdapter {
    static Retrofit retrofit = null;
    static Retrofit retrofit_service = null;
 
    public static Retrofit getClient() {
        if (retrofit == null) {
            retrofit = new Retrofit.Builder()
                    .baseUrl(RetrofitUrl.BASE_URL)
                    .client(createOkHttpClient())
                    .addConverterFactory(GsonConverterFactory.create())
                    .build();
        }
        return retrofit;
    }
 
    public static OkHttpClient createOkHttpClient() {
        // 네트워크 통신 로그(서버로 보내는 파라미터 및 받는 파라미터) 보기
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        //builder.cookieJar(new RetrofitCookieJar()); // Session 통신을 위한 기능
        // 네트워크 통신 로그(서버로 보내는 파라미터 및 받는 파라미터) 보기
        HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor();
        httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        builder.addInterceptor(httpLoggingInterceptor);
        builder.connectTimeout(15, TimeUnit.SECONDS);
        builder.readTimeout(20, TimeUnit.SECONDS);
        builder.writeTimeout(20, TimeUnit.SECONDS);
        builder.retryOnConnectionFailure(true);
        return builder.build();
    }
 
    public static <T> T createService(Class<T> serviceClass) {
        // implementation "com.github.akarnokd:rxjava3-retrofit-adapter:3.0.0"
        if (retrofit_service == null) {
            retrofit_service = new Retrofit.Builder()
                    .baseUrl(RetrofitUrl.BASE_URL)
                    .client(createOkHttpClient())
                    .addCallAdapterFactory(RxJava3CallAdapterFactory.create()).build();
        }
        return retrofit_service.create(serviceClass);
    }
 
}
 

 

RetrofitUrl.java:

public class RetrofitUrl {
    public static final String BASE_URL = "http://www.abc.com/";
 
    public static final String PdfData = "androidSample/getPDFData.php";
}

 

 

MainActivity.java:

import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.annotations.NonNull;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.core.ObservableEmitter;
import io.reactivex.rxjava3.core.ObservableOnSubscribe;
import io.reactivex.rxjava3.core.Observer;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.functions.Function;
import io.reactivex.rxjava3.schedulers.Schedulers;
import okhttp3.ResponseBody;
import okio.BufferedSink;
import okio.Okio;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
 
public class MainActivity extends AppCompatActivity implements BindPdfViewListAdapter.OnItemClickListener {
    private final String TAG = this.getClass().getSimpleName();
    Context mContext;
    
    @Override
    public void onItemClicked(View view, Pdf_Item item, int position) {
        Toast.makeText(getApplicationContext(), "잠시 기다리시면 "+item.getTitle()+" PDF 파일 열람이 가능합니다", Toast.LENGTH_LONG).show();
        String PDFUrl = RetrofitUrl.BASE_URL+item.getPdfurl();
        downloadPDFFile(item.getPdfurl());
    }
 
    public void downloadPDFFile(String fileUrl) {
        RetrofitService downloadService = RetrofitAdapter.createService(RetrofitService.class);
        downloadService.downloadFile(fileUrl)
                .flatMap(processResponse())
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(handleResult());
    }
 
    public Function<Response<ResponseBody>, Observable<File>> processResponse(){
        return new Function<Response<ResponseBody>, Observable<File>>() {
            @Override
            public Observable<File> apply(Response<ResponseBody> responseBodyResponse) throws Exception {
                return saveToDiskRx(responseBodyResponse);
            }
        };
    }
 
    private Observable<File> saveToDiskRx(final Response<ResponseBody> response){
        return Observable.create(new ObservableOnSubscribe<File>() {
            @Override
            public void subscribe(@NonNull ObservableEmitter<File> subscriber) throws Throwable {
                File filePath = new File(Environment.getExternalStorageDirectory() + "/download");
                File outputFile = new File(filePath, "tempPDF.pdf");
                if (outputFile.exists()) { // 기존 파일 존재시 삭제하고 다운로드
                    outputFile.delete();
                }
 
                BufferedSink bufferedSink = Okio.buffer(Okio.sink(outputFile));
                bufferedSink.writeAll(response.body().source());
                bufferedSink.close();
 
                subscriber.onNext(outputFile);
                subscriber.onComplete();
            }
        });
    }
 
    private Observer<File> handleResult() {
        return new Observer<File>() {
 
            @Override
            public void onSubscribe(@NonNull Disposable d) {
                Log.d(TAG, "OnSubscribe");
            }
 
            @Override
            public void onNext(@NonNull File file) {
                Log.d(TAG, "File downloaded to " + file.getAbsolutePath());
                // 다운로드한 파일 실행하여 PDF 보기 진행하는 코드
                if (Build.VERSION.SDK_INT >= 24) {
                    openPDF(file);
                } else {
                    Intent intent = new Intent(Intent.ACTION_VIEW);
                    Uri apkUri = Uri.fromFile(file);
                    intent.setDataAndType(apkUri, "application/pdf");
                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                    getApplicationContext().startActivity(intent);
                }
            }
 
            @Override
            public void onError(@NonNull Throwable e) {
                e.printStackTrace();
                Log.d(TAG, "Error " + e.getMessage());
            }
 
            @Override
            public void onComplete() {
                Log.d(TAG, "onCompleted");
            }
        };
    }
 
    void openPDF(File file) {
        Uri fileUri = FileProvider.getUriForFile(mContext, mContext.getApplicationContext().getPackageName() + ".fileprovider",file);
 
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.setDataAndType(fileUri, "application/pdf");
        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        try {
            startActivity(intent);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
 

 

전체 소스코드

https://github.com/jsk005/JavaProjects/tree/master/pdfviewer

 

GitHub - jsk005/JavaProjects: 자바 기능 테스트

자바 기능 테스트. Contribute to jsk005/JavaProjects development by creating an account on GitHub.

github.com

 

참고자료

https://androiderrors.com/rxandroid-and-retrofit-unable-to-create-call-adapter-for-io-reactivex-observableretrofit2-responseokhttp3-responsebody/

https://www.codexpedia.com/android/retrofit-2-and-rxjava-for-file-downloading-in-android/

 

이 자료는 참고하지 않았지만 나중에 보면 도움되는 것이 있을 수도 있겠다 싶어 URL를 적어둔다.

https://www.digitalocean.com/community/tutorials/android-rxjava-retrofit

 

 

 

 

 

 

 

블로그 이미지

Link2Me

,
728x90

Android 에서 로그인을 하면 서버에서 PHP가 받아서 처리하는 로그인 예제이다.

결과를 json 으로 받아서 Android에서 저장해서 처리한다.


<?php
// 파일을 직접 실행하는 비정상적 동작을 방지 하기 위한 목적
if(isset($_POST) && $_SERVER['REQUEST_METHOD'] == "POST"){
    @extract($_POST); // $_POST['loginID'] 라고 쓰지 않고, $loginID 라고 써도 인식되게 함
    if(isset($userID) && !empty($userID) && isset($password) && !empty($password)) {
        require_once 'phpclass/dbconnect.php';
        require_once 'phpclass/loginClass.php';
        $c = new LoginClass();

        header("Cache-Control: no-cache, must-revalidate");
        header("Content-type: application/json; charset=UTF-8");

        $rs = $c->LoginUserChk($userID,$password,$uID);
        if($rs > 0){
            $user = $c->getUser($userID, $password);
            if ($user != false) {
                $_SESSION['userID'] = $user['userID'];
                $_SESSION['userNM'] = $user['userNM'];
                $_SESSION['admin'] = $user['admin'];

                $row = array("userNM"=>$user['userNM'],"mobileNO"=>$user['mobileNO'],"profileImg"=>$user['idx']);

                $status = "success";
                $message = "";
                $userinfo = $row;
            } else {
                $status = "로그인 에러";
                $message = "다시 한번 시도하시기 바랍니다.";
                $userinfo = null;
            }

        } else if($rs === -1){
            $status = "단말 불일치";
            $message = '등록 단말 정보가 일치하지 않습니다. 관리자에게 문의하시기 바랍니다.';
            $userinfo = null;
        } else {
            $status = "로그인 에러";
            $message = '로그인 정보가 일치하지 않습니다';
            $userinfo = null;
        }
        $result = array(
            'status' => $status,
            'message' => $message,
            'userinfo' => $userinfo
        );
        echo json_encode($result);
    }
} else { // 비정상적인 접속인 경우
    echo 0; // loginChk.php 파일을 직접 실행할 경우에는 화면에 0을 찍어준다.
    exit;
}
?>


본 코드의 안드로이드 처리 부분 (Volley 라이브러리 활용)

https://github.com/jsk005/JavaProjects/blob/master/volleyloginsample/src/main/java/com/link2me/android/loginsample/LoginActivity.java 부분을 살펴보면 이해가 될 것이다.


Retrofit2 활용 예제는 GitHub에는 올리지 않았다.

@Parcelize
data class LoginResult (
    val status: String = "",
    val message: String = "",
    val userinfo: UserInfo? = null
): Parcelable

@Parcelize
data class UserInfo (
        val userNM: String = "",
        val mobileNO: String = "",
        val profileImg: String = ""
): Parcelable

void AutoLoginProgress() {
    userID = PrefsHelper.read("userid", "");
    userPW = PrefsHelper.read("userpw", "");

    if (userID != null && !userID.isEmpty() && userPW != null && !userPW.isEmpty()) {
        String uID = Utils.getDeviceId(mContext); // 스마트폰 고유장치번호
        String mfoneNO = Utils.getPhoneNumber(mContext); // 스마트폰 전화번호
        String AppVersion = String.valueOf(Utils.getVersionCode(mContext));

        mloginService.Login(userID,userPW,uID,mfoneNO,AppVersion)
                .enqueue(new Callback<LoginResult>() {
                    @Override
                    public void onResponse(Call<LoginResult> call, retrofit2.Response<LoginResult> response) {
                        LoginResult result = response.body();
                        if(result.getStatus().contains("success")){
                            PrefsHelper.write("userNM",result.getUserinfo().getUserNM());
                            PrefsHelper.write("mobileNO",result.getUserinfo().getMobileNO());
                            PrefsHelper.write("profileImg",result.getUserinfo().getProfileImg());
                        } else {
                            if(result.getStatus().contains("로그인 에러")){
                                startActivity(new Intent(SplashActivity.this, LoginActivity.class));
                                finish();
                            } else {
                                Utils.showAlert(mContext, result.getStatus(), result.getMessage());
                            }
                        }
                    }

                    @Override
                    public void onFailure(Call<LoginResult> call, Throwable t) {

                    }
                });
    } else {
        startActivity(new Intent(getApplication(), LoginActivity.class));
        finish();
    }
}


블로그 이미지

Link2Me

,
728x90

Retrofit2 라이브러리 Java 버전 코딩을 Kotlin 으로 변환해서 이용하는 법을 적어둔다.

Android Studio 에서 Java 파일 Kotlin 파일로 변환하는 기능을 이용하여 변환하고 나서 에러가 나는 부분을 수정하면 대부분 해결이 된다.

static 은 object 파일로 변환을 해버리는데, 여기서는 companion object 를 사용해서 수동 변환을 했다.


public class RetrofitAPI {
    private static Retrofit retrofit = null;

    public static Retrofit getClient() {
        retrofit = new Retrofit.Builder()
                .baseUrl(Value.IPADDRESS)
                .addConverterFactory(GsonConverterFactory.create())
                .client(createOkHttpClient())
                .build();

        return retrofit;
    }

    private static OkHttpClient createOkHttpClient() {
        // 네트워크 통신 로그(서버로 보내는 파라미터 및 받는 파라미터) 보기
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
        interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        builder.addInterceptor(interceptor);
        return builder.build();
    }
}

class RetrofitAPI {

    companion object{
        private var retrofit: Retrofit? = null

        fun getClient(): Retrofit? {
            retrofit = Retrofit.Builder()
                .baseUrl(Value.IPADDRESS)
                .addConverterFactory(GsonConverterFactory.create())
                .client(createOkHttpClient())
                .build()
            return retrofit
        }

        private fun createOkHttpClient(): OkHttpClient? {
            // 네트워크 통신 로그(서버로 보내는 파라미터 및 받는 파라미터) 보기
            val builder = OkHttpClient.Builder()
            val interceptor =
                HttpLoggingInterceptor()
            interceptor.setLevel(HttpLoggingInterceptor.Level.BODY)
            builder.addInterceptor(interceptor)
            return builder.build()
        }
    }

}


public interface RemoteService {// 서버에 호출할 메소드를 선언하는 인터페이스
    // POST 방식으로 데이터를 주고 받을 때 넘기는 변수는 Field 라고 해야 한다.
    @FormUrlEncoded
    @POST(RetrofitUrl.URL_LOGIN)
    Call<LoginResult> Login(
            @Field("userid") String userId,
            @Field("userpw") String userPw,
            @Field("token") String token,
            @Field("keyword") String keyword,
            @Field("mfoneNO") String mfoneNO,
            @Field("uuid") String uuid
    );
}

interface RemoteService { // 서버에 호출할 메소드를 선언하는 인터페이스
    // POST 방식으로 데이터를 주고 받을 때 넘기는 변수는 Field 라고 해야 한다.
    @FormUrlEncoded
    @POST(RetrofitUrl.URL_LOGIN)
    fun Login(
        @Field("userid") userId: String,
        @Field("userpw") userPw: String,
        @Field("token") token: String,
        @Field("keyword") keyword: String,
        @Field("mfoneNO") mfoneNO: String,
        @Field("uuid") uuid: String
    ): Call<LoginResult>
}



로그인 예제 변환

void AutoLoginProgress() { // 자동 로그인 체크 검사
    //Log.e("AutoLogin", "Auto Login Stage.");
    pref = getSharedPreferences("pref", Activity.MODE_PRIVATE);
    userID = pref.getString("userid", "");
    userPW = pref.getString("userpw", "");
    String keyword = Value.encrypt(Value.URLkey());
    String mfoneNO = Value.encrypt(getPhoneNumber());
    uID = Settings.Secure.getString(getApplicationContext().getContentResolver(), Settings.Secure.ANDROID_ID);

    if (userID != null && !userID.isEmpty() && userPW != null && !userPW.isEmpty()) {
        mloginService = RetrofitAPI.getClient().create(RemoteService.class);
        mloginService.Login(Value.encrypt(userID), Value.encrypt(userPW),"", keyword, mfoneNO, Value.encrypt(uID))
                .enqueue(new Callback<LoginResult>() {
                    @Override
                    public void onResponse(Call<LoginResult> call, Response<LoginResult> response) {
                        // 네트워크 통신 성공
                        LoginResult result = response.body();
                        if(result.getResult().contains("success")){

                            Log.e("Intor UserName",result.getUserinfo().getUsername());

                            Intent intent = new Intent(Intro.this, MapViewActivity.class);
                            intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NO_HISTORY);
                            startActivity(intent);
                            finish();

                        } else {
                            if(result.getResult().contains("로그인 에러")){
                                startActivity(new Intent(Intro.this, LoginActivity.class));
                                finish();
                            } else {
                                Utils.showAlert(Intro.this,result.getResult(),result.getMessage());
                            }
                        }
                    }

                    @Override
                    public void onFailure(Call<LoginResult> call, Throwable t) {

                    }
                });

    } else {
        startActivity(new Intent(getApplication(), LoginActivity.class));
        finish();
    }

}


fun AutoLoginProgress() { // 자동 로그인 체크 검사
     //Log.e("AutoLogin", "Auto Login Stage.");
     pref = getSharedPreferences("pref", Activity.MODE_PRIVATE)
     userID = pref?.getString("userid", "")
     userPW = pref?.getString("userpw", "")
     val keyword = Value.encrypt(Value.URLkey())
     val mfoneNO = Value.encrypt(phoneNumber)
     uID = Settings.Secure.getString(applicationContext.contentResolver,Settings.Secure.ANDROID_ID)
     if (userID != null && !userID!!.isEmpty() && userPW != null && !userPW!!.isEmpty()) {
         mloginService = RetrofitAPI.getClient()!!.create(RemoteService::class.java)
         mloginService!!.Login(Value.encrypt(userID), Value.encrypt(userPW),"", keyword, mfoneNO,Value.encrypt(uID))
             ?.enqueue(object : Callback<LoginResult> {
                 override fun onResponse(
                     call: Call<LoginResult>,
                     response: Response<LoginResult>
                 ) {
                     // 네트워크 통신 성공
                     val result = response.body()
                     if (result!!.result.contains("success")) {
                         Log.e("Intor UserName", result.userinfo!!.username)
                         val intent = Intent(this@Intro, MapViewActivity::class.java)
                         intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_NO_HISTORY)
                         startActivity(intent)
                         finish()
                     } else {
                         if (result.result.contains("로그인 에러")) {
                             startActivity(Intent(this@Intro,LoginActivity::class.java))
                             finish()
                         } else {
                             Utils.showAlert(this@Intro,result.result,result.message)
                         }
                     }
                 }

                 override fun onFailure(
                     call: Call<LoginResult?>,
                     t: Throwable
                 ) {
                 }
             })
     } else {
         startActivity(Intent(application,LoginActivity::class.java))
         finish()
     }
 }


블로그 이미지

Link2Me

,
728x90

작성일자 : 2020.7.14


Retrofit2 라이브러리를 이용하여 데이터 처리하는 걸 테스트하다가 완전 삽질을 한참 했다.

Array로 전체 데이터를 받아서 처리해야 하는데 그렇게 처리하지 않아서 헤메고 있었다.


Retrofit2 기본 개념은 https://link2me.tistory.com/1806 잘 정리되어 있다.


public interface DataCommAPI {
    // POST 방식으로 데이터를 주고 받을 때 넘기는 변수는 Field 라고 해야 한다.
    @FormUrlEncoded
    @POST(Value.AddressData)
    Call<List<Address_Item>> GetAddrData(
            @Field("keyword") String keyword,
            @Field("search") String search
    );
}

@Parcelize
data class Address_Item (
    // 결과를 받을 모델
    var uid: String="",
    var userNM: String="",
    var mobileNO: String?=null,
    var telNO: String?=null,
    var photo: String?=null,
    var checkBoxState: Boolean
): Parcelable


MainActivity.java

public class MainActivity extends AppCompatActivity {
    private final String TAG = this.getClass().getSimpleName();
    Context context;

    private ArrayList<Address_Item> addressItemList = new ArrayList<>(); // 서버에서 가져온 원본 데이터 리스트
    private ArrayList<Address_Item> searchItemList = new ArrayList<>(); // 검색한 데이터 리스트
    private RecyclerView mRecyclerView;
    private RecyclerViewAdapter mAdapter;
    private SearchView editsearch;

    DataCommAPI dataCommAPI;

    static boolean isMSG = false;
    static boolean isAll = false;
    static boolean flag = false;
    static CheckBox checkAll;

    RelativeLayout relative2;
    private BackPressHandler backPressHandler;

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

        initView();
    }

    private void initView() {
        isMSG = false;
        isAll = false;
        // Relative layout 정의
        relative2 = (RelativeLayout) findViewById(R.id.list_view_relative2);

        mRecyclerView = findViewById(R.id.address_listview);
        mRecyclerView.setHasFixedSize(true);
        LinearLayoutManager manager = new LinearLayoutManager(context);
        DividerItemDecoration decoration = new DividerItemDecoration(context,manager.getOrientation());
        mRecyclerView.addItemDecoration(decoration);
        mRecyclerView.setLayoutManager(manager);

        mAdapter = new RecyclerViewAdapter(context,addressItemList);
        mRecyclerView.setAdapter(mAdapter);

        // Adapter에 추가 데이터를 저장하기 위한 ArrayList
        getServerData(); // 서버 데이터 가져오기
    }

    private void getServerData() {
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(Value.IPADDRESS)
                .addConverterFactory(GsonConverterFactory.create())
                .client(createOkHttpClient())
                .build();

        dataCommAPI = retrofit.create(DataCommAPI.class);

        String keyword = Value.encrypt(Value.URLkey());
        dataCommAPI.GetAddrData(keyword,"").enqueue(new Callback<List<Address_Item>>() {
            @Override
            public void onResponse(Call<List<Address_Item>> call, Response<List<Address_Item>> response) {
                List<Address_Item> result = response.body(); // 전체 데이터

                addressItemList.clear(); // 서버에서 가져온 데이터 초기화
                searchItemList.clear();
               
                for(Address_Item item : result){
                    addressItemList.add(item);
                    searchItemList.add(item);
                }
                runOnUiThread(new Runnable() { // runOnUiThread()를 호출하여 실시간 갱신한다.
                    @Override
                    public void run() {
                        // 갱신된 데이터 내역을 어댑터에 알려줌
                        mAdapter.notifyDataSetChanged();
                    }
                });
            }

            @Override
            public void onFailure(Call<List<Address_Item>> call, Throwable t) {

            }
        });

    }

    private OkHttpClient createOkHttpClient() {
        // 네트워크 통신 로그(서버로 보내는 파라미터 및 받는 파라미터) 보기
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
        interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        builder.addInterceptor(interceptor);
        return builder.build();
    }

    public void showAlert(String title, String message) {
        AlertDialog.Builder builder = new AlertDialog.Builder(context);
        builder.setTitle(title);
        builder.setMessage(message)
                .setCancelable(false)
                .setPositiveButton("OK", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        dialog.dismiss();
                    }
                });
        AlertDialog alert = builder.create();
        alert.show();
    }

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



통신상태가 성공, 실패 여부를 먼저 확인한 다음에

ArrayList 데이터 전체를 출력하는 걸 처리하는 사항

@Parcelize
class AddressResult (
    val status: String,
    val message: String="",
    val addrinfo: List<Address_Item>? = null

): Parcelable
 

public interface RemoteService {
    // POST 방식으로 데이터를 주고 받을 때 넘기는 변수는 Field 라고 해야 한다.
    @FormUrlEncoded
    @POST(Value.AddressData)
    Call<AddressResult> GetAddrData(
            @Field("keyword") String keyword,
            @Field("search") String search
    );
}

private void getServerData() {
    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(Value.IPADDRESS)
            .addConverterFactory(GsonConverterFactory.create())
            .client(createOkHttpClient())
            .build();

    dataCommAPI = retrofit.create(RemoteService.class);

    String keyword = Value.encrypt(Value.URLkey());
    dataCommAPI.GetAddrData(keyword,"").enqueue(new Callback<AddressResult>() {
        @Override
        public void onResponse(Call<AddressResult> call, Response<AddressResult> response) {
            if(response.body().getStatus().contains("success")){
                addressItemList.clear(); // 서버에서 가져온 데이터 초기화
                searchItemList.clear();

                for(Address_Item item : response.body().getAddrinfo()){
                    addressItemList.add(item);
                    searchItemList.add(item);
                }
                runOnUiThread(new Runnable() { // runOnUiThread()를 호출하여 실시간 갱신한다.
                    @Override
                    public void run() {
                        // 갱신된 데이터 내역을 어댑터에 알려줌
                        mAdapter.notifyDataSetChanged();
                    }
                });
            } else {
                showAlert(response.body().getStatus(),response.body().getMessage());
            }
        }

        @Override
        public void onFailure(Call<AddressResult> call, Throwable t) {

        }
    });

}


추가 오픈일자 : 2020.9.15일

위 안드로이드 코드와 관련된 PHP 코드

<?php
// 파일을 직접 실행하면 동작되지 않도록 하기 위해서
if(isset($_POST) && $_SERVER['REQUEST_METHOD'] == "POST"){
    @extract($_POST); // POST 전송으로 전달받은 값 처리
    require_once 'config/config.php';
    require_once 'phpclass/dbconnect.php';
    require_once 'phpclass/loginClass.php';
    $c = new LoginClass;

    header("Cache-Control: no-cache, must-revalidate");
    header("Content-type: application/json; charset=UTF-8");

    $R = array(); // 결과 담을 변수 생성
    // 화면에 출력할 칼럼 발췌
    $sql = "select uid,userNM,mobileNO,telNO,photo from Person ";
    if(!empty($search)) {
        $sql .= "where userNM LIKE '%".$search."%' or mobileNO LIKE '%".$search."%'";
    }
    $result = mysqli_query($db,$sql);
    while($row = mysqli_fetch_array($result)) {
        $photo ="null";
        $photo_path = './photos/'.$row['photo'];
        if(file_exists($photo_path)) {
            $photo =$row['photo'];
        }
        array_push($R,  array("uid"=>$row['uid'],"userNM"=>$row['userNM'],"mobileNO"=>$row['mobileNO'],

                                        "telNO"=>$row['telNO'],"photo"=>$photo,"checkBoxState"=>false));
    }

    $status = "success";
    $message = "";
    $addrinfo = $R; // 전체 ArrayList 데이터

    $result = array(
        'status' => $status,
        'message' => $message,
        'addrinfo' => $addrinfo
    );
    echo json_encode($result);

}
?>


블로그 이미지

Link2Me

,
728x90

이전 코드 구현에서 Gson 라이브러리를 사용하여 코드를 변경하는 부분이 핵심이다.


package com.link2me.android.recyclerviewjava.model

import android.os.Parcelable
import kotlinx.android.parcel.Parcelize

@Parcelize
data class Address_Item (
    // 결과를 받을 모델, 코틀린 혼용하다보니 코틀린의 문법 적용을 받게되어 null 처리를 조심해야 하더라.
    var uid: String="",
    var userNM: String="",
    var mobileNO: String?=null,
    var telNO: String?=null,
    var photo: String?=null,
    var checkBoxState: Boolean
): Parcelable


package com.link2me.android.recyclerviewjava;

import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.util.Log;
import android.widget.CheckBox;
import android.widget.RelativeLayout;

import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.SearchView;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.toolbox.JsonObjectRequest;
import com.android.volley.toolbox.Volley;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.link2me.android.recyclerviewjava.adapter.RecyclerViewAdapter;
import com.link2me.android.recyclerviewjava.model.Address_Item;
import com.link2me.android.recyclerviewjava.model.DataCommAPI;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;

public class MainActivity extends AppCompatActivity {
    private final String TAG = this.getClass().getSimpleName();
    Context context;

    private ArrayList<Address_Item> addressItemList = new ArrayList<>(); // 서버 원본 데이터 리스트
    private ArrayList<Address_Item> searchItemList = new ArrayList<>(); // 검색한 데이터 리스트
    private RecyclerView mRecyclerView;
    private RecyclerViewAdapter mAdapter;
    private SearchView editsearch;

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

        initView();
    }

    private void initView() {
        // Adapter에 추가 데이터를 저장하기 위한 ArrayList
        getServerData(Value.IPADDRESS+"getJSONData.php"); // 서버 데이터 가져오기
    }

    private void getServerData(String SERVER_URL) {
        HashMap<String, String> params = new HashMap<>();
        params.put("keyword", Value.encrypt(Value.URLkey()));
        JSONObject jsonObject = new JSONObject(params);

        // 1. RequestQueue 생성 및 초기화
        RequestQueue requestQueue = Volley.newRequestQueue(context);

        // 2. Request Obejct인 StringRequest 생성
        JsonObjectRequest request = new JsonObjectRequest(Request.Method.POST, SERVER_URL,jsonObject,
                (Response.Listener<JSONObject>) response -> {
                    Log.e("result", "" + response);
                    try {
                        if(response.getString("status").equals("success")){
                            String jsondata = response.getString("addrinfo");
                            showJSONList(jsondata);
                        } else {
                            showAlert(response.getString("status"),response.getString("message"));
                        }
                    } catch (JSONException e) {
                        e.printStackTrace();
                    }
                },
                error -> Log.d("error", "[" + error.getMessage() + "]")) {

        };

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

    private void showJSONList(String JSONdata) {
        try {
            JSONArray peoples = new JSONArray(JSONdata); // [] 대괄호
            addressItemList.clear(); // 서버에서 가져온 데이터 초기화

            Gson gson = new Gson();
            Type listType = new TypeToken<ArrayList<Address_Item>>(){}.getType();
            addressItemList = gson.fromJson(peoples.toString(),listType);
            searchItemList = gson.fromJson(peoples.toString(),listType);

            mRecyclerView = findViewById(R.id.address_listview);
            mAdapter = new RecyclerViewAdapter(context,addressItemList);
            LinearLayoutManager manager = new LinearLayoutManager(context);
            DividerItemDecoration decoration = new DividerItemDecoration(context,manager.getOrientation());

            mRecyclerView.addItemDecoration(decoration);
            mRecyclerView.setLayoutManager(manager);
            mRecyclerView.setAdapter(mAdapter);


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

    public void showAlert(String title, String message) {
        AlertDialog.Builder builder = new AlertDialog.Builder(context);
        builder.setTitle(title);
        builder.setMessage(message)
                .setCancelable(false)
                .setPositiveButton("OK", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        dialog.dismiss();
                    }
                });
        AlertDialog alert = builder.create();
        alert.show();
    }

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


블로그 이미지

Link2Me

,
728x90

오랫만에 서버에 있는 자료를 가져오는 걸 했는데 삽질을 한참이나 했다.

코틀린으로의 변환을 고려하여 앱 build.gradle은 아래와 같이 작성했다.


앱 build.gradle

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

android {
    compileSdkVersion 30
    buildToolsVersion "30.0.0"

    defaultConfig {
        applicationId "com.link2me.android.recyclerviewjava"
        minSdkVersion 23
        targetSdkVersion 30
        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.3.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'

    implementation 'com.google.android.material:material:1.1.0'
    implementation 'androidx.cardview:cardview:1.0.0'  // 레이아웃으로 사용할 CardView
    implementation 'androidx.recyclerview:recyclerview:1.1.0'

    implementation 'com.android.volley:volley:1.1.1'
    implementation 'com.google.code.gson:gson:2.8.6'

    // 이미지 출력용 Glide
    implementation 'com.github.bumptech.glide:glide:4.11.0'
    annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'

}
 


모델 작성

- 코틀린으로 코드를 구현했다. Java 와 코틀린 혼용 코딩이 가능하다.

package com.link2me.android.recyclerviewjava.model

import android.os.Parcelable
import kotlinx.android.parcel.Parcelize

@Parcelize
data class Address_Item (
    // 결과를 받을 모델
    val uid: String="",
    val userNM: String="",
    val mobileNO: String="",
    val telNO: String="",
    val photo: String="",
    val checkBoxState: Boolean
): Parcelable


Gson 라이브러리를 사용했는데도 불구하고, 데이터가 반환되지 않는 원인을 찾고자 Retrofit2 라이브러리 대신에 Volley 라이브러리를 사용해서 테스트 했다. 두 라이브러리는 동시에 사용할 수가 없더라.

그래서 먼저 Gson 라이브러리 사용없이 for문으로 데이터를 저장하는 걸 시도했다.


package com.link2me.android.recyclerviewjava;

import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.util.Log;
import android.widget.CheckBox;
import android.widget.RelativeLayout;

import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.SearchView;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.toolbox.JsonObjectRequest;
import com.android.volley.toolbox.Volley;
import com.link2me.android.recyclerviewjava.adapter.RecyclerViewAdapter;
import com.link2me.android.recyclerviewjava.model.Address_Item;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.ArrayList;
import java.util.HashMap;

public class MainActivity extends AppCompatActivity {
    private final String TAG = this.getClass().getSimpleName();
    Context context;

    private ArrayList<Address_Item> addressItemList = new ArrayList<>(); // 서버 원본 데이터 리스트
    private ArrayList<Address_Item> searchItemList = new ArrayList<>(); // 검색한 데이터 리스트
    private RecyclerView mRecyclerView;
    private RecyclerViewAdapter mAdapter;

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

        initView();
    }

    private void initView() {
        // Adapter에 추가 데이터를 저장하기 위한 ArrayList
        getServerData(Value.IPADDRESS+"getJSONData.php"); // 서버 데이터 가져오기
    }

    private void getServerData(String SERVER_URL) {
        HashMap<String, String> params = new HashMap<>();
        params.put("keyword", Value.encrypt(Value.URLkey()));
        JSONObject jsonObject = new JSONObject(params);

        // 1. RequestQueue 생성 및 초기화
        RequestQueue requestQueue = Volley.newRequestQueue(context);

        // 2. Request Obejct인 StringRequest 생성
        JsonObjectRequest request = new JsonObjectRequest(Request.Method.POST, SERVER_URL,jsonObject,
                (Response.Listener<JSONObject>) response -> {
                    Log.e("result", "" + response);
                    try {
                        if(response.getString("status").equals("success")){
                            String jsondata = response.getString("addrinfo");
                            showJSONList(jsondata);
                        } else {
                            showAlert(response.getString("status"),response.getString("message"));
                        }
                    } catch (JSONException e) {
                        e.printStackTrace();
                    }
                },
                error -> Log.d("error", "[" + error.getMessage() + "]")) {

        };

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

    private void showJSONList(String JSONdata) {
        try {
            JSONArray peoples = new JSONArray(JSONdata); // [] 대괄호
            addressItemList.clear(); // 서버에서 가져온 데이터 초기화
            for(int i=0;i < peoples.length();i++){
                JSONObject jsonObj = peoples.getJSONObject(i);
                final String uid = jsonObj.getString("uid");
                final String name = jsonObj.getString("userNM");
                final String mobileNO = jsonObj.getString("mobileNO");
                final String telNO = jsonObj.getString("telNO");
                final String photo = jsonObj.getString("photo");

                getServerDataList(uid,name,mobileNO,telNO,photo,false);
                selectDataList(uid,name,mobileNO,telNO,photo,false);
            }

            mRecyclerView = findViewById(R.id.address_listview);
            mAdapter = new RecyclerViewAdapter(context,addressItemList);
            LinearLayoutManager manager = new LinearLayoutManager(context);
            DividerItemDecoration decoration = new DividerItemDecoration(context,manager.getOrientation());

            mRecyclerView.addItemDecoration(decoration);
            mRecyclerView.setLayoutManager(manager);
            mRecyclerView.setAdapter(mAdapter);


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

    // 아이템 데이터 추가를 위한 메소드
    public void getServerDataList(String uid, String name, String mobileNO, String officeNO, String photo_image, boolean checkItem_flag) {
        Address_Item item = new Address_Item(uid,name,mobileNO,officeNO,photo_image,checkItem_flag);
        addressItemList.add(item);
    }

    public void selectDataList(String uid, String name, String mobileNO, String officeNO, String photo_image, boolean checkItem_flag) {
        Address_Item item = new Address_Item(uid,name,mobileNO,officeNO,photo_image,checkItem_flag);
        searchItemList.add(item);
    }

    public void showAlert(String title, String message) {
        AlertDialog.Builder builder = new AlertDialog.Builder(context);
        builder.setTitle(title);
        builder.setMessage(message)
                .setCancelable(false)
                .setPositiveButton("OK", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        dialog.dismiss();
                    }
                });
        AlertDialog alert = builder.create();
        alert.show();
    }

}


다른코드는 첨부파일을 참조하면 된다.

recyclerviewj_1.zip


블로그 이미지

Link2Me

,
728x90

그동안은 Retrofit을 사용할 일이 전혀 없었는데, 사용할 일이 있어서 관련 자료를 찾아서 테스트하고 적어둔다.

예제를 이해하는데 참 어려웠다. 그래서 유투브 동영상도 보고 이해하고 따라해보고 동작되는 걸 확인한 후에야 로직이 이해가 되었다.

구글에서 추천하는 Volley 라이브러리보다 속도가 빠르다고 검색결과가 나온다.

 

Retrofit 은 안전한 타입 방식의 HTTP 클라이언트로서 Android 와 Java 애플리케이션을 위한 라이브러리이다.

Json converter : JSON 타입의 응답결과를 객체로 매핑(변환)해주는 Converter

https://square.github.io/retrofit/ 를 참조한다. 그런데 설명만으로는 이해하기 어렵더라.

 

이해를 돕기 위해서 그림으로 설명한다.

Interface 생성

이 부분에는 GET/POST/PUT/DELETE 등 필요한 모든 함수를 선언하는 부분이다.

Call <> 사이에는 서버의 Json 형태에 따라 클래스 파일을 만들어서 넣어주면 된다.

/**
 * GET 방식, URL/getphoto.php/{code} 호출.
 * Data Type의 여러 개의 JSON을 통신을 통해 받음.
 * 주소값이 "http://www.abc.com/getphoto.php?code=1" 이 됨.
 * @param code 요청에 필요한 code
 * @return 다수의 Data 객체를 JSON 형태로 반환.
 */
@GET("/getphoto.php")
Call<Data> getData(@Query("code") String code);

 

 

앱 build.gradle 설정

    compileOptions { // 이거 안해주면 에러가 나오더라.
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

dependencies {
    implementation 'com.google.android.material:material:1.0.0'
    implementation 'com.squareup.retrofit2:retrofit:2.7.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.7.0'
    implementation 'com.squareup.okhttp3:logging-interceptor:4.4.0'
}

 

AndroidManifest.xml

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

android:usesCleartextTraffic="true"

 

activity_login.xml

구글에서 Meterial Design 예제를 검색하면 나온다. 그걸 참조해서 하면 된다.

 

Resource 관련 파일을 첨부한다.

res.zip
다운로드

 

 

LoginActivity.java

import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;

import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

public class LoginActivity extends AppCompatActivity implements View.OnClickListener {
    Context context;
    EditText etId;
    EditText etPw;
    String userID;
    String userPW;
    String uID; // 스마트 기기의 대체 고유값

    LoginAPI mloginService;

    PermissionListener permissionlistener = new PermissionListener() {
        @Override
        public void onPermissionGranted() {
            initView();
        }

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

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        context = LoginActivity.this;
        checkPermissions();
    }

    private void checkPermissions() {
        if (Build.VERSION.SDK_INT >= 23) { // 마시멜로(안드로이드 6.0) 이상 권한 체크
            TedPermission.with(context)
                    .setPermissionListener(permissionlistener)
                    .setRationaleMessage("앱을 이용하기 위해서는 접근 권한이 필요합니다")
                    .setDeniedMessage("앱에서 요구하는 권한설정이 필요합니다...\n [설정] > [권한] 에서 사용으로 활성화해주세요.")
                    .setPermissions(new String[]{
                            android.Manifest.permission.READ_PHONE_STATE,
                            android.Manifest.permission.READ_CALL_LOG, // 안드로이드 9.0 에서는 이것도 추가하라고 되어 있음.
                            android.Manifest.permission.CALL_PHONE,  // 전화걸기 및 관리
                            android.Manifest.permission.ACCESS_FINE_LOCATION
                    })
                    .check();

        } else {
            initView();
        }
    }

    private void initView() {
        etId = findViewById(R.id.et_id);
        etPw = findViewById(R.id.et_pw);
        Button btn_login = findViewById(R.id.btn_login);

        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(Value.BASE_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .client(createOkHttpClient())
                .build();


        mloginService = retrofit.create(LoginAPI.class);
    }

    @Override
    public void onClick(View v) {
        userID = etId.getText().toString().trim();
        userPW = etPw.getText().toString().trim();
        uID = Settings.Secure.getString(getApplicationContext().getContentResolver(), Settings.Secure.ANDROID_ID);
        mloginService.Login(userID, userPW, uID)
                .enqueue(new Callback<LoginResult>() {
            @Override
            public void onResponse(Call<LoginResult> call, Response<LoginResult> response) {
                // 네트워크 통신 성공
                LoginResult result = response.body();
                if(result.getResult().contains("success")){
                    Intent intent = new Intent(LoginActivity.this, MainActivity.class);
                    intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NO_HISTORY);
                    startActivity(intent);
                    finish();
                } else {
                    showAlert(result.getResult(),result.getMessage());
                }
            }

            @Override
            public void onFailure(Call<LoginResult> call, Throwable t) {
                // 네트워크 통신 실패
            }
        });
    }



    private OkHttpClient createOkHttpClient() {
        // 네트워크 통신 로그(서버로 보내는 파라미터 및 받는 파라미터) 보기
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
        interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        builder.addInterceptor(interceptor);
        return builder.build();
    }

    public void showAlert(String title, String message) {
        AlertDialog.Builder builder = new AlertDialog.Builder(context);
        builder.setTitle(title);
        builder.setMessage(message)
                .setCancelable(false)
                .setPositiveButton("OK", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        dialog.dismiss();
                    }
                });
        AlertDialog alert = builder.create();
        alert.show();
    }
}

public interface LoginAPI { // 서버에 호출할 메소드를 선언하는 인터페이스
    // POST 방식으로 데이터를 주고 받을 때 넘기는 변수는 Field 라고 해야 한다.
    @FormUrlEncoded
    @POST(Value.URL_LOGIN)
    Call<LoginResult> Login(
            @Field("userId") String userId,
            @Field("userPw") String userPw,
            @Field("uuid") String uuid
            );
}

public class LoginResult { // 결과를 받을 모델
    private String result;
    private String message;

    public String getResult() {
        return result;
    }

    public String getMessage() {
        return message;
    }
}

public class Value extends AppCompatActivity {
    public static final String BASE_URL = "http://www.abc.com/mobile/";
    public static final String URL_LOGIN = "login.php";
}

 

예제는 결과를 받아서 RecyclerView 에 보여준다거나 후속 action 에 대한 건 기술하지 않았다.

이 예제는 그냥 복사해서 붙여넣기를 어떻게 하는지에 대한 설명이라고 보면 된다.

PHP 파일에 대한 사항은 기술하지 않는다. 이미 본 블로그 다른 예제를 보면 충분히 이해할 수 있으리라.

 

참고하면 도움되는 글

https://thdev.tech/androiddev/2016/11/13/Android-Retrofit-Intro/

 

https://link2me.tistory.com/1850 ArrayList 처리 예제

 

 

Update : 2020.8.3일

ㅇ Retrofit2 라이브러리를 여러번 호출하는 걸 만들어야 한다면 함수화를 하는 게 좋다.

 

import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

public class RetrofitAPI {
    private static Retrofit retrofit = null;

    public static Retrofit getClient() {
        retrofit = new Retrofit.Builder()
                .baseUrl(Value.IPADDRESS)
                .addConverterFactory(GsonConverterFactory.create())
                .client(createOkHttpClient())
                .build();

        return retrofit;
    }

    private static OkHttpClient createOkHttpClient() {
        // 네트워크 통신 로그(서버로 보내는 파라미터 및 받는 파라미터) 보기
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
        interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        builder.addInterceptor(interceptor);
        return builder.build();
    }
}

 

사용 예제

public interface RemoteService {// 서버에 호출할 메소드를 선언하는 인터페이스
    // POST 방식으로 데이터를 주고 받을 때 넘기는 변수는 Field 라고 해야 한다.
    @FormUrlEncoded
    @POST(RetrofitUrl.URL_Verson)
    Call<VersionResult> lastVersion(
            @Field("os") String os
    );

    @FormUrlEncoded
    @POST(RetrofitUrl.URL_GroupList)
    Call<GroupResult> getGroupList(
            @Field("keyword") String keyword
    );
}

class RetrofitUrl {
    companion object {
        const val URL_Verson = "lastVersion.php"
        const val URL_GroupList = "groupList.php"
    }
}

private void UpgradeChk() {
    mloginService = RetrofitAPI.getClient().create(RemoteService.class);
    mloginService.lastVersion("0").enqueue(new Callback<VersionResult>() {
        @Override
        public void onResponse(Call<VersionResult> call, Response<VersionResult> response) {
            VersionResult result = response.body();

            version = Value.VERSION; // 앱 버전
            version = version.replaceAll("[^0-9]", ""); // 버전에서 숫자만 추출
            Log.d("WEB", "Response: " + response);

            String Response = result.getVersion().replaceAll("[^0-9]", ""); // 버전에서 숫자만 추출
            System.out.println("Server Version : " + Response);

            if (Integer.parseInt(version) < Integer.parseInt(Response)) { // 서버 버전이 더 높으면
                UpgradeProcess();
                if(Build.VERSION.SDK_INT >= 26)  {
                    NonSecretApp_Setting(); // 출처를 알 수 없는 앱 설정 화면 띄우기
                } else {
                    startActivity(new Intent(android.provider.Settings.ACTION_SECURITY_SETTINGS));
                }
            } else {
                AutoLoginProgress();
            }
        }

        @Override
        public void onFailure(Call<VersionResult> call, Throwable t) {

        }
    });
}

 

더 세부적인 것은 Github 에 올려진 소스코드를 참조하면 도움될 것이다.

https://github.com/jsk005/JavaProjects/tree/master/retrofitsample

 

GitHub - jsk005/JavaProjects: 자바 기능 테스트

자바 기능 테스트. Contribute to jsk005/JavaProjects development by creating an account on GitHub.

github.com

 

블로그 이미지

Link2Me

,
728x90

2013년 Google I/O에서 발표한 volley라는 Http 라이브러리를 이용하여 로그인 예제를 만들어봤다.

구글 자료들을 받아서 테스트 하다보니 여러가지 라이브러리로 만들어져 있어서 내가 만든 앱에 적용을 해보면서 기능을 테스트하고 적어둔다.

Volley 라이브러리를 사용하면 코드가 심플해져 사용하기 편하다는 걸 알 수 있을 것이다.


build.gradle 추가

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:27.1.1'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    implementation 'com.android.support:recyclerview-v7:27.1.1' // ListView 개선 버전
    implementation 'com.android.support:cardview-v7:27.1.1'
    implementation 'gun0912.ted:tedpermission:2.0.0'
    implementation 'com.android.volley:volley:1.1.1'
}


API28(Android 9) 이상을 사용하는 경우에는 추가로 고려할 사항이 있다.

https 통신을 기본으로 지원하고 http 통신은 기본 지원이 아니라서 아래 색깔 표시한 한줄을 추가해줘야 한다.

AndroidManifest.xml 파일에 추가할 사항

<application
    android:allowBackup="false"
    android:icon="@drawable/icon_console"
    android:label="@string/app_name"
    android:supportsRtl="true"
    android:theme="@style/AppTheme"
    android:usesCleartextTraffic="true"> <!-- http:// 통신일 경우에도 통신 가능하도록 -->
 

앱 build.gradle 은 androidX 로 변경한다. 구글링하면 나온다.

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

    // implementation 'com.android.support:appcompat-v7:28.0.0'
    //implementation 'com.android.support.constraint:constraint-layout:1.1.3'

    implementation 'androidx.appcompat:appcompat:1.0.2'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    implementation 'gun0912.ted:tedpermission:2.0.0'
    implementation 'com.android.volley:volley:1.1.1'
}


Volley를 사용하는 방법은
1) RequestQueue를 생성한다.
2) Request Object를 생성한다.
3) 생성한 Object를 RequestQueue로 넘긴다.


서버에서 받은 데이터가 텍스트인 경우에는 StringRequest 을 사용하고, JSON 데이터일 경우에는 JsonObjectRequest 를 사용하면 된다.

서버에서 데이터를 텍스트로 만들어서 보내기 때문에 StringRequest 를 사용하면 된다. 이렇게 하는 이유는 서버에서 에러가 발생했을 때 텍스트로 보내면 에러의 원인을 발견하고 서버 코드를 수정하기 쉽기 때문이다.


private void LoginVolley(final String loginID, final String loginPW) {
    // 1. RequestQueue 생성 및 초기화
    RequestQueue requestQueue = Volley.newRequestQueue(context);

    String url = Value.IPADDRESS + "/loginChk.php";

    // 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 + "]");
                }
            },
            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("loginID", Value.encrypt(loginID));
            params.put("loginPW", Value.encrypt(loginPW));
            return params;
        }
    };

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



실제 사용하는 코드를 보면서 이해도를 높여보자.

코딩하면서 항상 신경써서 고려할 사항은 Log 를 보면서 문제가 서버쪽에 있는 것인지 안드로이드 코드에 문제가 있는 것인지 파악할 수 있어야 한다.


 private void LoginVolley(final String loginID, final String loginPW) {
    // 1. RequestQueue 생성 및 초기화
    RequestQueue requestQueue = Volley.newRequestQueue(context);

    String url = Value.IPADDRESS + "/loginChk.php";

    // 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("loginID", Value.encrypt(loginID));
            params.put("loginPW", Value.encrypt(loginPW));
            params.put("uID", Value.encrypt(Value.getPhoneUID(context)));
            params.put("AppVersion", Value.VERSION);
            params.put("phoneVersion", Build.VERSION.RELEASE);
            params.put("phoneBrand", Build.BRAND);
            params.put("phoneModel", Build.MODEL);
            params.put("keyword", Value.encrypt(Value.URLkey()));
            return params;
        }
    };

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

private void showJSONList(String JSONdata) {
    // 서버 정보를 파싱하기 위한 변수 선언
    try {
        JSONObject jsonObj = new JSONObject(JSONdata);
        JSONArray logininfo = jsonObj.getJSONArray("result");

        JSONObject c = logininfo.getJSONObject(0);
        idx = Value.decrypt(c.getString("IDX"));
        dept1Code = Value.decrypt(c.getString("Dept1Code"));
        dept2Code = Value.decrypt(c.getString("Dept2Code"));

        System.out.println("idx : " + idx);

        if (Integer.parseInt(idx) > 0) { // 로그인 정보 일치
            pref = getSharedPreferences("pref", Activity.MODE_PRIVATE);
            SharedPreferences.Editor editor = pref.edit();

            editor.putString("id", loginID);
            editor.putString("pw", loginPW);
            editor.putString("autologin", "checked"); // 로그인되면 무조건 자동로그인 처리
            editor.putString("idx", idx); // 로그인 사용자의 idx
            editor.putString("category1", dept1Code);
            editor.putString("category2", dept2Code);
            editor.putString("uID", uID);
            editor.putString("registrationID", null);

            editor.commit();

            startActivity(new Intent(getApplication(), Main.class));
            finish(); // 현재 Activity 를 없애줌

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

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


참고 사항

ㅇ Android 로그인 폼 예제(https://link2me.tistory.com/1532)  에 이 코드를 추가해 보면 편하다.

ㅇ Anroid JSON 파헤치기 https://link2me.tistory.com/1247


이 글이 도움되었다면 000 부탁드립니다.

블로그 이미지

Link2Me

,
728x90

AsyncTask 를 Class 화하여 사용하는 방법이다.


private void getJSONFromAsyncHttpClient() {
    Uri.Builder builder = new Uri.Builder()
            .appendQueryParameter("uID", Value.encrypt(Value.getPhoneUID(context)))
            .appendQueryParameter("userID", Value.encrypt(userID))
            .appendQueryParameter("searName", Value.encrypt(gubun))
            .appendQueryParameter("searValue", Value.encrypt(tmpStr))
            .appendQueryParameter("keyword", Value.encrypt(Value.URLkey()));
    String params = builder.build().getEncodedQuery();

    AsyncHttpComm asyncHttpComm = new AsyncHttpComm();
    try {
        String result = asyncHttpComm.execute(Value.IPADDRESS + "/SearchList.php", params).get();
        showJSONList(result);
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    }
} 


위 코드를 보면 AsyncHttpComm Class를 호출하고 결과 문자열을 String result 로 받고 있다.

그리고 showJSONList(result) 에 결과를 넣어서 서버로 받은 메시지를 파싱하여 RecyclerView 에서 처리하면 된다.


그럼 이제 AsyncHttpComm Class 는 어떻게 되어 있는지 보자.

public class AsyncHttpComm extends AsyncTask<String, Void, String> {

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


String 변수를 받아서 백그라운드 처리하고, String 결과를 받고 있다.


또 메소드 처리되어 있는 PHPComm Class 는 어떻게 만들어져 있나 보자.

세션에 대한 내용이 적혀 있는데 별 의미가 없는 거 같다.

내가 아직 잘 몰라서 그런지 안드로이드와 PHP 간 통신하는데 있어서 세션값을 물고 다니면서 처리하는 방식으로 구현하지 않아도 통신하는데 전혀 문제가 되지 않는다.

스마트폰 고유장치 값, 키 값을 이용하여 PHP 에서 해킹시도 자체를 방지할 수 있다.


public class PHPComm extends Activity {

    // serverURL : JSON 요청을 받는 서버의 URL
    // postParams : POST 방식으로 전달될 입력 데이터
    // 반환 데이터 : 서버에서 전달된 JSON 데이터
    public static String getJson(String serverUrl, String postParams) throws Exception {

        BufferedReader bufferedReader = null;
        try {  
            Thread.sleep(100);
            URL url = new URL(serverUrl);  
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            // 세션 쿠키 전달
            String cookieString = CookieManager.getInstance().getCookie(Value.IPADDRESS);
            
            StringBuilder sb = new StringBuilder();  

            if(conn != null){ // 연결되었으면
                //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.setConnectTimeout(10000);
                conn.setReadTimeout(10000);
                conn.setUseCaches(false);
                conn.setDefaultUseCaches(false);
                conn.setDoOutput(true); // POST 로 데이터를 넘겨주겠다는 옵션
                conn.setDoInput(true);

                // Send post request
                DataOutputStream wr = new DataOutputStream(conn.getOutputStream());
                wr.writeBytes(postParams);
                wr.flush();
                wr.close();

                int responseCode = conn.getResponseCode();
                System.out.println("GET Response Code : " + responseCode);        
                if(responseCode == HttpURLConnection.HTTP_OK){ // 연결 코드가 리턴되면
                    bufferedReader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
                    String json;
                    while((json = bufferedReader.readLine())!= null){
                        sb.append(json + "\n");
                    }      
                }
                bufferedReader.close();
            }
            System.out.println("PHP Comm Out : " + sb.toString());
            return sb.toString().trim();  
            // 수행이 끝나고 리턴하는 값은 다음에 수행될 onProgressUpdate 의 파라미터가 된다
        } catch(Exception e){  
            return new String("Exception: " + e.getMessage());
        }   
 
    }
}


이 두개의 Class 를 만들고 나면 Library 를 사용하는 것만큼 편하게 Text 데이터를 수신하여 파싱하고 결과처리를 쉽게 할 수 있다.


http://www.masterqna.com/android/29387/asynctask-%EC%9D%98-get-%EB%A9%94%EC%86%8C%EB%93%9C-%EC%A7%88%EB%AC%B8

에 나온 내용을 보니

AsyncTask.get()은 UI thread를 block 한다고
AsyncTaks안에 onPostExecute()를 implement하고 Handle 하라고 나온다.
시간이 오래 걸리는 작업은 AsyncTask.get()을 사용하지 말자.



이런거 귀찮다?????

좀 더 간편하게 라이브러리 이용하여 하고 싶다면

앱 build.gradle 에 아래 라이브러리를 추가한다.

dependencies {
    implementation 'com.loopj.android:android-async-http:1.4.9'
}


코드는 아래와 같이 심플하다.


 private void getJSONFromAsyncHttpClient() {
    RequestParams params = new RequestParams();
    params.put("keyword", Value.encrypt(Value.URLkey()));
    params.put("uID", Value.encrypt(Value.getPhoneUID(context)));
    params.put("userID", Value.encrypt(userID));
    params.put("searName", Value.encrypt(gubun));
    params.put("searValue", Value.encrypt(tmpStr));

    AsyncHttpClient client = new AsyncHttpClient();
    client.post(Value.IPADDRESS + "/SearchList.php", params, new TextHttpResponseHandler() {
        @Override
        public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) {
            System.out.println("statusCode : " + statusCode);
            Toast.makeText(Search.this, "서버와의 통신 중 에러가 발생했습니다.", Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onSuccess(int statusCode, Header[] headers, String responseString) {
            System.out.println("statusCode : " + statusCode);
            //System.out.println("responseString : " + responseString);
            showJSONList(responseString);
        }
    });
}


라이브러리가 모든 폰에서 잘 동작되는지 여부를 확인한 것은 아니라서 표준 HttpUrlConnection 을 이용하여 통신하는 걸 구현하여 사용중이다.

Asynchronous HTTP Client 라이브러리에 대한 내용은 http://link2me.tistory.com/1493 참조하면 된다.


도움이 되셨다면 공감 그리고 ... 클릭 해 주시면 큰 힘이 됩니다.

블로그 이미지

Link2Me

,
728x90

HttpURLConnection 에서 PHP 세션 헤더 정보를 추출하는 코드를 적어둔다.

완벽하게 완성된 코드를 만들려면 테스트가 좀 더 필요하다.

PHP 서버와 통신하기 위해 헤더 정보를 추출해서 통신하는 것이 꼭 필요한지 여부를 검토해야 한다.

일반적으로 이런 헤더가 없어도 통신하는데 전혀 지장이 없다.

다만, 보안문제에 더 완벽한 방법이 있을까 하는 측면에서 이런 저런 검토를 해보는 중이다.

http://link2me.tistory.com/1493 Android Asynchronous Http Client 코드와 비교해보는 것도 좋을 거 같다.

PHP 세션 헤더를 추출할 일은 없지만 서버와의 통신하는 코드가 더 편리한 것을 이용하면 될 거 같아서 이런 저런 코드 테스트 한 걸 적어둔다.


관련 코드

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.net.NetworkInfo;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.provider.Settings;
import android.support.v7.app.AppCompatActivity;
import android.telephony.TelephonyManager;
import android.widget.Toast;

import com.google.firebase.iid.FirebaseInstanceId;
import com.tistory.link2me.common.AES256Cipher;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;

public class Intro extends AppCompatActivity {
    Boolean loginChecked;
    public SharedPreferences settings;
    String loginID;
    String loginPW;
    String getDeviceID; // 스마트기기의 장치 고유값
    private static final String SESSION_COOKIE = "sessionid";

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

        // 네트워크 연결 검사
        if(NetworkConnection() == false){
            NotConnected_showAlert();
        }

        // 업그레이드 유무 검사

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

    }

    private  class AsyncLogin extends AsyncTask<String, Void, String> {
        ProgressDialog pdLoading = new ProgressDialog(Intro.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();
                // 단말기의 ID 정보를 얻기 위해서는 READ_PHONE_STATE 권한이 필요
                TelephonyManager mTelephony = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
                if (mTelephony.getDeviceId() != null){
                    getDeviceID = mTelephony.getDeviceId();  // 스마트폰 기기 정보
                } else {
                    getDeviceID = Settings.Secure.getString(getApplicationContext().getContentResolver(), Settings.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");
                    conn.setUseCaches(false);
                    conn.setDefaultUseCaches(false);
                    conn.setDoOutput(true); // POST 로 데이터를 넘겨주겠다는 옵션
                    conn.setDoInput(true); // 서버로부터 응답 헤더와 메시지를 읽어들이겠다는 옵션

                    // 전달할 인자들
                    params[0] = AES256Cipher.AES_Encode(params[0], Value.AES256Key);
                    params[1] = AES256Cipher.AES_Encode(params[1], Value.AES256Key);

                    Uri.Builder builder = new Uri.Builder()
                            .appendQueryParameter("loginID", params[0])
                            .appendQueryParameter("loginPW", params[1])
                            .appendQueryParameter("deviceID", getDeviceID)
                            //.appendQueryParameter("phoneVersion", Build.VERSION.RELEASE)
                            //.appendQueryParameter("phoneBrand", Build.BRAND)
                            //.appendQueryParameter("phoneModel", Build.MODEL)
                            .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){ // 연결 코드가 리턴되면
                        // PHP 세션 헤더 정보 추출
                        String cookieTemp = conn.getHeaderField("Set-Cookie");
                        System.out.println("PHP Set-Cookie : " + cookieTemp);
                        if (cookieTemp.length() > 0) {
                            String[] splitCookie = cookieTemp.split(";");
                            String[] splitSessionId = splitCookie[0].split("=");
                            cookieTemp = splitSessionId[1];
                            SharedPreferences.Editor editor = settings.edit();
                            editor.putString(SESSION_COOKIE, cookieTemp);
                            editor.commit();
                            System.out.println("PHP SESSION_COOKIE : " + cookieTemp);
                        }
                        // 본문 정보 추출
                        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("JSON 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(Integer.parseInt(result) > 0){ // 로그인 정보 일치하면 uid 정보를 받음
                System.out.println("uid value : " + result);
                SharedPreferences.Editor editor = settings.edit();
                editor.putString("idx", result);
                Toast.makeText(Intro.this,"로그인 성공", Toast.LENGTH_SHORT).show();
                startActivity(new Intent(getApplication(), MainActivity.class));
                finish(); // 현재 Activity 를 없애줌

            } else {
                startActivity(new Intent(getApplication(), Login.class));
                finish();
            }
        }
    }

    public void NotConnected_showAlert() {
        AlertDialog.Builder builder = new AlertDialog.Builder(Intro.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();
    }

    public 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;
    }
}



블로그 이미지

Link2Me

,
728x90

android 의 비동기 HTTP 라이브러리 Asynchronous Http Client 에 대한 자료는

http://loopj.com/android-async-http/

http://yasirameen.com/2016/05/asynchronous-http-client-android/

https://java2android1108.blogspot.com/2018/01/android-web-services-using-android.html

를 참조하면 기본적인 사용법은 알 수 있다.

한글 설명은 http://edoli.tistory.com/91 를 참조하면 된다.

예제 설명이 잘된 자료는 http://yasirameen.com/2016/05/asynchronous-http-client-android/ 다.


아래 예제는 자동 로그인하는 Intro.java 코드다. 사용해보니 코드가 심플하고 편한 거 같다.

import cz.msebera.android.httpclient.Header;

public class Intro extends AppCompatActivity {
    Boolean loginChecked;
    public SharedPreferences settings;
    String loginID;
    String loginPW;
    String getDeviceID; // 스마트기기의 장치 고유값

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

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

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

        RequestParams params = new RequestParams();
        params.put("loginID", Value.encrypt(loginID));
        params.put("loginPW", Value.encrypt(loginPW));
        params.put("deviceID", getDeviceID);


        AsyncHttpClient client = new AsyncHttpClient();
        client.post(Value.IPADDRESS + "/loginChk.php", params, new TextHttpResponseHandler() {

            @Override
            public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) {
                System.out.println("statusCode : " + statusCode);
                System.out.println("responseString : " + responseString);
            }

            @Override
            public void onSuccess(int statusCode, Header[] headers, String responseString) {
                System.out.println("Response Value : " + responseString);
                if(Integer.parseInt(responseString) > 0){ // 로그인 정보 일치하면 uid 정보를 받음
                    SharedPreferences.Editor editor = settings.edit();
                    editor.putString("idx", responseString);
                    Toast.makeText(Intro.this,"로그인 성공", Toast.LENGTH_SHORT).show();
                    startActivity(new Intent(getApplication(), MainActivity.class));
                    finish(); // 현재 Activity 를 없애줌

                } else {
                    startActivity(new Intent(getApplication(), Login.class));
                    finish();
                }
            }
        });
    }

}


Android Studio 3.X 버전에서는 코드 자동완성이 잘 되는데 2.3.3 에서는 코드 자동완성이 잘 안된다.

관련 헤더 정보와 코드를 적어둔다.

import com.loopj.android.http.AsyncHttpClient;
import com.loopj.android.http.JsonHttpResponseHandler;
import com.loopj.android.http.RequestParams;

import org.json.JSONObject;

import cz.msebera.android.httpclient.Header;

public class MainActivity extends AppCompatActivity {

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

        // 서버에서 데이터 가져오기
        getJSONFromAsyncHttpClient();

    }

    public void getJSONFromAsyncHttpClient(){
        RequestParams params = new RequestParams();
        params.put("key", "value");
        params.put("more", "data");
        AsyncHttpClient client = new AsyncHttpClient();
        String url = Value.IPADDRESS + "/orgJSONList.php";
        client.get(url, params, new JsonHttpResponseHandler() {
            @Override
            public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
                // Handle resulting parsed JSON response here
            }

            @Override
            public void onFailure(int statusCode, Header[] headers, String res, Throwable t) {

            }
        });
    }

} 


2019.7.20일자로 1.4.10 버전이 나왔다.

https://loopj.com/android-async-http/

블로그 이미지

Link2Me

,
728x90

OKHttp 라이브러리를 이용하여 간단하게 POST 로 데이터를 전송할 경우 알아두어야 할 사항을 적어둔다.

OKHttp 라이브러리 사용조건이 최소 KIKAT 버전 이상인 거 같더라. (확실한 것은 더 테스트를 해봐야....)


RequestBody 객체 부분이 파일을 첨부하는 경우와 일반 텍스트를 전송하는 경우가 약간 다르다.

나머지 부분은 동일하다.  파일을 업로드하는 경우에는 OKHttp 라이브러리를 사용하는 것이 HttpURLConnection 으로 하는 것보다 코드도 심플하고 잘 동작한다.


httpClient 는 Handler를 사용하지 않는 곳에서도 동작이 되는데, OKHttpClient는 AsyncTask 또는 Handler 에서만 동작되더라.

로그인 버튼을 누르면 httpClient 가 동작되도록 되어 있는 코드를 그냥 단순하게 OKHttp 라이브러리를 사용해서 변환하면 동작이 안된다.


httpClient = new DefaultHttpClient();
localContext = new BasicHttpContext();
httpPost = new HttpPost(Value.IPADDRESS + "/loginChk.php");

entity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE);

try {
    entity.addPart("loginID", new StringBody(Value.encrypt(ID)));
    entity.addPart("loginPW", new StringBody(Value.encrypt(PW)));
} catch (UnsupportedEncodingException e1) {
    e1.printStackTrace();
}

httpPost.setEntity(entity);
try {
    response = httpClient.execute(httpPost, localContext);
} catch (ClientProtocolException e1) {
    e1.printStackTrace();
} catch (IOException e1) {
    e1.printStackTrace();
}
try {
    reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent(), "UTF-8"));
} catch (UnsupportedEncodingException e1) {
    e1.printStackTrace();
} catch (IllegalStateException e1) {
    e1.printStackTrace();
} catch (IOException e1) {
    e1.printStackTrace();
}

try {
    Response = reader.readLine();
    Log.d(TAG, "응답 ====" + Response);
} catch (IOException e1) {
    e1.printStackTrace();
}


위 코드는 Eclipse 기반의 minSdkVersion 8에서 동작되는 코드인데 Android Studio minSdkVersion 16 이상으로 변환하고 FCM을 연동하니까 동작이 안되더라. FCM(Firebase Cloud Message)는 테스트를 하다보니 minSdkVersion 10 인거 같더라.

HttpClient 는 Android SDK API 23 부터는 더이상 지원을 하지 않는다.

앱 Gradle에서 compileSdkVersion 23으로 한 경우에는 useLibrary 'org.apache.http.legacy'를 하라고 나온다.

내가 테스트한 환경은 compileSdkVersion 22로 했다. 퍼미션 지정하는 것이 귀찮기도 하고 Eclipse 툴로 컴파일 한 것이 잘 동작하는 걸 Android Studio 기반으로 다시 코드를 수정 개발해야 하는 상황이라 수정을 조금이라도 덜 하는 방법을 찾아보려고 했다.

구글링을 해보면 HttpClient 를 사용할 수 있는 방법이 나오는데 그 방법으로 하는데 에러가 나면서 문제가 생긴다.

그래서 코드를 수정 사용해야 하는데 AsyncTask 기반의 OKHttp 또는 HttpURLConnection 로 변환해야 정상 동작된다.


PHPComm.getJson(params[0], params[1]) 코드는 http://link2me.tistory.com/1247 를 참조하면 된다.

OKHttp 로그인 예제 http://link2me.tistory.com/1353 를 보면서 이 내용을 보면 이해할 수 있다.


Uri.Builder builder = new Uri.Builder()
    .appendQueryParameter("loginID", Value.encrypt(ID))
    .appendQueryParameter("loginPW", Value.encrypt(PW));
String postParams = builder.build().getEncodedQuery();
AsyncTaskLogin asyncLogin = new AsyncTaskLogin();
asyncLogin.execute(Value.IPADDRESS + "/loginChk.php", postParams);

 class AsyncTaskLogin extends AsyncTask<String, Void, String> {

    @Override
    protected String doInBackground(String... params) {
        // 이곳에 OKHttp 라이브러리 기반 코드를 작성
        // 또는 HttpURLConnection 코드를 작성한다.
        try {
            return PHPComm.getJson(params[0], params[1]);
        } catch (Exception e) {
            return new String("Exception: " + e.getMessage());
        }
    }

    protected void onPostExecute(String result) {

    }
}


HttpClient 를 사용하는 방법이 http://goodnut.tistory.com/60 에 나오는데 이 방법으로는 테스트를 안해봤다.


아래 코드는 단독으로는 동작이 안되고 에러가 발생한다.

반드시 Handler or AsyncTask 에서 동작하도록 코드를 작성해야 한다.


void Agree() {
    String url = Value.IPADDRESS + "/regAssent.php";

    RequestBody requestBody = new FormBody.Builder()
            .add("idx", idx)
            .add("assent", "1")
            .build();

    Request request = new Request.Builder()
            .url(url)
            .post(requestBody)
            .build();

    OkHttpClient client = new OkHttpClient();
    try {
        Response response = client.newCall(request).execute();
        Log.d(TAG, "응답 ====" + response);
    } catch (IOException e) {
        e.printStackTrace();
    }
}
 


참고하면 도움되는 사이트


http://www.vogella.com/tutorials/JavaLibrary-OkHttp/article.html


http://www.zoftino.com/android-okhttp-example


블로그 이미지

Link2Me

,
728x90

OKHttp 라이브러리를 이용한 로그인 예제를 만들어봤다.


http://square.github.io/okhttp/ 에 가면 최신버전을 확인할 수 있다.


1. Gradle 추가

    compile 'com.squareup.okhttp3:okhttp:3.8.1'

    gradle 에 추가하고 Sync 까지 해준다.


2. AndroidManifest.xml


3. OKHttpAPICAll 메소드

 import android.os.Build;

import com.tistory.link2me.common.AES256Cipher;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;

import okhttp3.FormBody;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

public class OKHttpAPICall {
    //GET network request
    public static String GET(OkHttpClient client, HttpUrl url) throws IOException {
        Request request = new Request.Builder()
                .url(url)
                .build();
        Response response = client.newCall(request).execute();
        return response.body().string();
    }

    //POST network request
    public static String POST(OkHttpClient client, String url, RequestBody body) throws IOException {
        Request request = new Request.Builder()
                .url(url)
                .post(body)
                .build();
        Response response = client.newCall(request).execute();
        return response.body().string();
    }

    //Login request body
    public static RequestBody LoginBody(String loginID, String loginPW, String deviceID) {
        // 전달할 인자들
        String username = null;
        String password = null;
        try {
            username = AES256Cipher.AES_Encode(loginID, Value.AES256Key);
            password = AES256Cipher.AES_Encode(loginPW, Value.AES256Key);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (InvalidAlgorithmParameterException e) {
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        }

        return new FormBody.Builder()
                .add("action", "login")
                .add("format", "json")
                .add("loginID", username)
                .add("loginPW", password)
                .add("deviceID", deviceID)
                .add("phoneVersion", Build.VERSION.RELEASE)
                .add("phoneBrand", Build.BRAND)
                .add("phoneModel", Build.MODEL)
                .build();
    }

}


4. Login.java

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

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import okhttp3.OkHttpClient;

import static com.tistory.link2me.okhttplogin.OKHttpAPICall.LoginBody;

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

    String loginID;
    String loginPW;
    CheckBox autologin;
    Boolean autologinchk;
    String idx;
    public SharedPreferences settings;

    private OkHttpClient client;
    String response;

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


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

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

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

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

        settings = getSharedPreferences("settings", Activity.MODE_PRIVATE);
        autologinchk = settings.getBoolean("autologin", false);
        if (autologinchk) {
            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()){
                    String url = Value.IPADDRESS + "/loginChk.php";
                    // 단말기의 ID 정보를 얻기 위해서는 READ_PHONE_STATE 권한이 필요
                    TelephonyManager mTelephony = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
                    if (mTelephony.getDeviceId() != null){
                        getDeviceID = mTelephony.getDeviceId();  // 스마트폰 기기 정보
                    } else {
                        getDeviceID = Settings.Secure.getString(getApplicationContext().getContentResolver(), Settings.Secure.ANDROID_ID);
                    }
                    new AsyncLogin().execute(url,loginID, loginPW,getDeviceID);
                }
            }
        });
    }


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

        @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) {
            client = new OkHttpClient();
            try {
                response = OKHttpAPICall.POST(client, params[0], LoginBody(params[1], params[2],params[3]));

                Log.d("Response", response);
                return response;
            } catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        }

        protected void onPostExecute(String result){
            pdLoading.dismiss();
            if(Integer.parseInt(result) > 0){ // 로그인 정보 일치
                idx = result;
                Toast.makeText(getApplicationContext(),"로그인 성공", Toast.LENGTH_SHORT).show();
                startActivity(new Intent(getApplication(), MainActivity.class));
                finish(); // 현재 Activity 를 없애줌

            } else if(result.equalsIgnoreCase("-1")){ // 등록된 단말기와 불일치
                deviceDismatch_showAlert();
            } else if (result.equalsIgnoreCase("0")) {
                showAlert();
            } else {
                Toast.makeText(getApplicationContext(), "서버로부터 정보가 잘못 전송되었습니다", 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("autologin", true);
            editor.putString("idx", idx);

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

    }

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

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

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

    }

    private boolean NetworkConnection() {
        int[] networkTypes = {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;
    }

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

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

    }

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

}


5. Value.java

import android.app.Activity;

public class Value extends Activity {
    public static final String IPADDRESS = "http://192.168.0.100";
    public static final String VERSION = "1.0.0"; // App Version
    public static final String AES256Key = "hijklmn123456opqrstuvwxyz";
}


위 예제에 사용된 java 및 AndroidManifest.xml 소스 파일


OKHttp_Login.zip



참조 사이트

https://www.sitepoint.com/consuming-web-apis-in-android-with-okhttp/



블로그 이미지

Link2Me

,
728x90

PHP 서버에 있는 자료를 가져와서 안드로이드에서 처리하는 AsyncTask 함수다.

요청 : Text POST, 결과 : JSON, Text

테스트 환경 : Android Studio, Eclipse


 === PHPComm.java ===

 package com.tistory.link2me.common;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;

import android.app.Activity;
import android.util.Log;
import android.webkit.CookieManager;

public class PHPComm extends Activity {

    // serverURL : JSON 요청을 받는 서버의 URL
    // postParams : POST 방식으로 전달될 입력 데이터
    // 반환 데이터 : 서버에서 전달된 JSON 데이터
    public static String getJson(String serverUrl, String postParams) throws Exception {

        BufferedReader bufferedReader = null;  
        try {  
            Thread.sleep(100);
            URL url = new URL(serverUrl);  
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            // 세션 쿠키 전달
            String cookieString = CookieManager.getInstance().getCookie(Value.IPADDRESS);
            
            StringBuilder sb = new StringBuilder();  
            sb.setLength(0);

            if(conn != null){ // 연결되었으면
                //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.setConnectTimeout(10000);
                conn.setReadTimeout(10000);
                conn.setUseCaches(false);
                conn.setDefaultUseCaches(false);
                conn.setDoOutput(true); // POST 로 데이터를 넘겨주겠다는 옵션
                conn.setDoInput(true);

                // Send post request
                DataOutputStream wr = new DataOutputStream(conn.getOutputStream());
                wr.writeBytes(postParams);
                wr.flush();
                wr.close();

                int responseCode = conn.getResponseCode();
                System.out.println("GET Response Code : " + responseCode);        
                if(responseCode == HttpURLConnection.HTTP_OK){ // 연결 코드가 리턴되면
                    bufferedReader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
                    String json;
                    while((json = bufferedReader.readLine())!= null){
                        sb.append(json + "\n");
                    }      
                }
                bufferedReader.close();
            }
            System.out.println("PHP Comm Out Size : " + sb.length()); // 서버에서 보내준 결과의 길이
            System.out.println("PHP Comm Out : " + sb); // 서버에서 보내준 내용
            return sb.toString().trim();
            
        } catch(Exception e){  
            return new String("Exception: " + e.getMessage());
        }
 
    }
}


위 함수의 사용법 예제

 public class MainActivity extends Activity {
    String idx;
    String regdate;
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        SharedPreferences pref = getSharedPreferences("pref", Activity.MODE_PRIVATE);
        idx = pref.getString("idx", "");
        regdate = pref.getString("regdate", "");
        SQLiteDBChk(idx, regdate); // 서버 데이터를 가져와서 SQLiteDB에 저장하는 로직

    }

    private void SQLiteDBChk(String idx, String regdate) {
        Uri.Builder builder = new Uri.Builder()
            .appendQueryParameter("idx", idx)
            .appendQueryParameter("regdate", regdate);
        String postParams = builder.build().getEncodedQuery();

        getChartData getData = new getChartData();
        getData.execute(Value.IPADDRESS + "/SQLiteDB.php", postParams);

    }

    class getChartData extends AsyncTask<String, Void, String> {

        @Override
        protected String doInBackground(String... params) {
            try {
                return PHPComm.getJson(params[0], params[1]);
                // 수행이 끝나고 리턴하는 값은 다음에 수행될 onProgressUpdate 의 파라미터가 된다
            } catch (Exception e) {
                return new String("Exception: " + e.getMessage());
            }
        }

        protected void onPostExecute(String result) {
            // 서버에서 전송받은 결과를 파싱하여 처리

        }
    }

}


위 코드 파일

PHPComm.zip

위 코드는 Eclipse 에서는 쿠키값이 잘 전달되는데 Android Studio 에서는 쿠키정보가 성공적이지 못했다.

쿠키를 이용해서 정보를 주고받지 않고 다른 키(idx)를 이용하여 정보를 주고 받는다.

쿠키 부분 코드는 삭제해도 되고 그냥 두어도 된다.


블로그 이미지

Link2Me

,
728x90

파일 다운로드 함수를 구현하기에 앞서 AsyncTask 다운로드 상태바 표시되는 예제다.

동영상 강좌 듣는 것을 테스트 하면서 명칭 일부를 수정한 거다.

AsyncTask 개념 설명은 http://link2me.tistory.com/1031 을 참조하면 된다.

실제 코드 구현하면서 에러 발생하는 부분 등을 같이 포함해서 기록해 두고 있다.


AsyncTask_ex01.zip


import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    TextView textView;
    ProgressBar pBar;
    DownloadFileAsyncTask downloadFileAsyncTask;

    int value;

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

        textView = (TextView)findViewById(R.id.txtView01);
        pBar = (ProgressBar)findViewById(R.id.progressBar);

        Button btn_Start = (Button)findViewById(R.id.btnStart);
        btn_Start.setOnClickListener(new View.OnClickListener(){
            public void onClick(View v){
                // 백그라운드 객체를 만들어줌
                downloadFileAsyncTask = new DownloadFileAsyncTask();
                downloadFileAsyncTask.execute();
            }
        });

        Button btn_Cancel = (Button)findViewById(R.id.btnCancel);
        btn_Cancel.setOnClickListener(new View.OnClickListener(){
            public void onClick(View v){
                downloadFileAsyncTask.cancel(true);
            }
        });
    }

    class DownloadFileAsyncTask extends AsyncTask<Integer, Integer, Integer> {

        protected void onPreExecute() { //초기화 단계에서 사용되는 메소드
            value = 0;
            pBar.setProgress(value);
        }

        protected Integer doInBackground(Integer... params) {
            // doInBackground 메소드는 필수 메소드
            while(isCancelled() == false){
                value++;
                if(value >=100){
                    break;
                }else{
                    publishProgress(value);
                }
                try{
                    Thread.sleep(100);
                }catch(Exception e){}
            } //while of End

            return value;
        }

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

        protected void onPostExecute(Integer result) {
            //백그라운 작업이 끝난후에 호출되는 메소드
            pBar.setProgress(0);
            textView.setText("작업 진행 완료");
        }

        protected void onCancelled(){
            // cancel메소드를 호출하면 자동으로 호출되는 메소드
            // 작업을 취소했을 경우에 사용되는 메소드
            pBar.setProgress(0);
            textView.setText("작업 진행 취소됨");
        }
    }
}

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

    <TextView
        android:id="@+id/titleText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="AsyncTask Exercise"
        android:textSize="20dp"
        android:gravity="center"
        android:textColor="@color/colorAccent"
        android:layout_margin="10dp"
        />

    <TextView
        android:id="@+id/txtView01"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="18dp"
        android:padding="10dp"
        android:text="Progress Status"/>

    <ProgressBar
        android:id="@+id/progressBar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        style="@style/Base.Widget.AppCompat.ProgressBar.Horizontal"
        android:layout_marginTop="5dp"
        android:max="100"
        android:padding="10dp"
        />

    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="10dp">

        <Button
            android:id="@+id/btnStart"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:layout_marginTop="5dp"
            android:layout_marginRight="20dp"
            android:text="시작"
            android:textSize="18dp"/>
        <Button
            android:id="@+id/btnCancel"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:layout_marginTop="5dp"
            android:text="취소"
            android:textSize="18dp"
            />

    </LinearLayout>
</LinearLayout>



블로그 이미지

Link2Me

,
728x90

비동기 처리를 하기 위해서는 별도의 Thread 를 생성하여 public void run()  메소드를 구현하면 되지만
안드로이드에서는 직접 Thread 를 생성하기 보다는 AsyncTask 를 사용하길 권장한다.

AsyncTask 는 execute( Runnable) 메소드도 제공하기 때문에 별도의 스레드 작업을 할때도 사용할 수 있다.

AsyncTask.execute(new Runnable() {
    @Override
    public void run() {
        try {
            getJson(Value.IPADDRESS + "/MyTask.php", params,context);
        } catch (Exception e) {
            new String("Exception: " + e.getMessage());
        }
    }
});


위 코드에서 context 가 들어간 이유는 public class Broadcast_Receiver extends BroadcastReceiver 에서 처리하는 코드이기 때문이다.

일반적인 AsyncTask 를 사용해서 처리하려고 하는데 코드 실행순서가 순차적으로 실행되지 않는 현상이 있었다.

아래 코드내에서는 순차적으로 실행되는데 PostExecute(String result) 에서 처리할 코드가 제대로 먹히지 않았다.

내가 아직 기본기가 부족해서 놓치는 부분이 있어서 인거 같다. (일반 AsyncTask  : http://link2me.tistory.com/1031)


서버에서 결과를 받아다가 처리한 이후에 코드가 실행될 줄 알았는데 비동기식이라 그런지 다음 코드가 먼저 실행되어 버리면서 전혀 기대하지 않았던 결과가 나왔다.

그래서 검색해서 찾아낸 코드가 AsyncTask.execute(new Runnable() 였고, 이 코드를 활용하여 원하는 결과를 얻을 수가 있었다.


@Override
public void onReceive(Context context, Intent intent) {
    // 네트워크 연결 체크
    NetworkChk(context, intent);

    // 잠든 화면 깨우기
    WakeLock(context, intent);
    
    // 전화 수신 체크
    CallReceivedChk(context, intent);
}

private void CallReceivedChk(final Context context, Intent intent) {

    if (state.equals(TelephonyManager.EXTRA_STATE_RINGING)) {

        AsyncTask.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    getJson(Value.IPADDRESS + "/MyTask.php",params,context);
                } catch (Exception e) {
                    new String("Exception: " + e.getMessage());
                }
            }
        });

}


이 코드가 메모리 문제까지 완벽하게 처리해주는지 여부는 아직 모른다.

병렬처리를 하는게 맞을 거 같아서 코드를 AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() 로 수정 적용했다.

AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
    @Override
    public void run() {
       
    }
});


충분한 테스트 이후에 문제가 없다면 결과를 다시 기록해두련다.



AsyncTask가 처음 도입되었을때는 순차적으로 하나의 백그라운드 쓰레드를 실행하였으나, DONUT(1.6버전)부터는 여러개의 태스크를 병렬적으로 처리하도록 변경이 되었고, 다시 HONEYCOMB(3.0버전)부터는 하나의 쓰레드만 실행하게끔 돌아왔다. 그리고 THREAD_POOL_EXECUTOR를 사용한 executeOnExecutor(Executor, Params...) 로 쓰레드를 병렬적으로 실행시킬수 있게 했다.

if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.HONEYCOMB) {
  myTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else {
  myTask.execute();
}

AsyncTask.execute(new Runnable() {
    @Override
    public void run() {
       
    }
});

허니콤의 3.2버전에서는 AsyncTask의 쓰레드는 순차적으로 실행되므로 이전 태스크가 종료되기를 기다려야 한다.
그러므로 대기하지 않고 바로 실행시켜주려면 별도의 스레드에서 실행해야한다.
멀티 쓰레드로 Concurrent하게 데이터를 가져오거나 동작하게 하기 위해서는, 아래와 같이 AsyncTask를 실행할 때, AsyncTask.THREAD_POOL_EXECUTOR 스케줄러를 지정해야 한다.
new MyAsyncTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, “”);

AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
    @Override
    public void run() {
       
    }
});


참고하면 도움이 될 사이트

http://www.programing-style.com/android/android-api/android-asynctask-order-execute/

블로그 이미지

Link2Me

,
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

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

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

안드로이드와 PHP 간에 로그인 연동처리 방법을 보강했다.

네트워크 상태체크, 폰에 자동로그인 정보기록하기, POST로 로그인 처리 하는 부분까지는 이전 게시글에 있는데 본격적으로 서버에 있는 다른 자료를 검색하면서 SESSION 검사를 해서 비정상 접속은 차단하는 것까지 해야 안전하기 때문에, 세션정보까지 연동되도록 처리하는 과정을 추가했다.


처음에 세션이 생성되는지에 대한 로그, 생성된 세션이 다른 Activity 에서 제대로 받는지 여부에 대해서 확인하는 로그인데 동일한 세션 값이 넘어가는 걸 확인할 수 있다.


자료 검색해서 원하는 결과를 제대로 얻기까지 많은 블로그를 탐독하고 찾으면서 겨우 성공했다.

PHP 코드상에서 처리하는 사항은 잘 아는데 안드로이드 부분은 처음 접하다보니 어렵다.

Web에서는 세션정보가 잘 넘어가는데 안드로이드에서 세션처리를 해주어야만 넘어간다.


===== loginChk.php ======

<?php
session_start();
if(isset($_POST['loginID']) && !empty($_POST['loginID']) && isset($_POST['loginPW']) && !empty($_POST['loginPW'])) {
    $loginID = trim($_POST['loginID']);
    $loginPW = trim($_POST['loginPW']);
    $deviceID = trim($_POST['deviceID']);

    $deviceID = $deviceID ? $deviceID : '';  // 안드로이드 폰의 장치 ID (안드로이드 코드에서 확인)

    if(empty($deviceID)){
        require_once $_SERVER['DOCUMENT_ROOT'].'/dbconnect.php';
        require_once $_SERVER['DOCUMENT_ROOT'].'/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=mobile/list.php'>");
            }
        }

    } else {
        require_once $_SERVER['DOCUMENT_ROOT'].'/db.info.php';
        require_once $_SERVER['DOCUMENT_ROOT'].'/phpclass/dbClass.php';
        $conn=new MySQLiDbClass();
        $DB_CONNECT = $conn->isConnectDb($DB);

        require_once $_SERVER['DOCUMENT_ROOT'].'/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 'Login Success';
        } else if($result == '0') {
            echo 'Login Fail';
        } else {
            echo 'Phone Dismatch';
        }
    }
   
} else {
    echo("<meta http-equiv='Refresh' content='0; URL=loginForm.php'>");
}
?>


===== get_json.php ======

<?php
session_start();
if(!($_SESSION['userID'] && $_SESSION['userPW'])) {
    exit;
}

$query = $_REQUEST['search'];

    include_once $_SERVER['DOCUMENT_ROOT'].'/db.info.php';
    require_once $_SERVER['DOCUMENT_ROOT'].'/phpclass/dbClass.php';
    require_once $_SERVER['DOCUMENT_ROOT'].'/phpclass/xmlClass.php';

    $conn = new MySQLiDbClass(); // DB 함수
    $DB_CONNECT = $conn->isConnectDb($DB);

    // 화면에 출력할 칼럼 발췌
    $sql = "select uid,name,mobile from Person ";
    if(!empty($query)) {
        $sql .= "where name LIKE '%".$query."%' or mobile LIKE '%".$query."%'";
    }
    $result = mysqli_query($DB_CONNECT,$sql);

    // 1. JSON 데이터 생성방법
    $c = new jsonClass();
    echo $c->JSONEncode($result,'result');

?>


안드로이드 로그인 소스코드 부분이다.

IP주소 부분을 여러곳에서 사용해야 해서 별도로 Value.java 파일로 작성했다.


===== Value.java ====

import android.app.Activity;

public class Value extends Activity {
    public static final String IPADDRESS = "http://IP주소"; // SERVER IP
    public static final String VERSION = "1.0.0"; // App Version

}

===== Login.java =====

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

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 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;

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 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));
           
            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("Response : " + responsePost);
           
            runOnUiThread(new Runnable() {
                public void run() {
                    dialog.dismiss();
                }
            });
           
            if(responsePost.equalsIgnoreCase("Login Success")){ // 로그인 정보 일치
                runOnUiThread(new Runnable() {
                    public void run() {
                        Toast.makeText(Login.this,"Login Success", Toast.LENGTH_SHORT).show();
                    }
                });
               
                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(500);
               
                startActivity(new Intent(this.getApplicationContext(), MainActivity.class));
                finish(); // finish()를 호출해서 Activity를 없애줌

            } else if(responsePost.equalsIgnoreCase("Phone Dismatch")){ // 등록된 단말기와 불일치
                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(); // 동기화 종료
    }
}



===== MainActivity.java =======

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.app.ActionBar;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Vibrator;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.CookieManager;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends Activity {

    private ListView mListView = null;
    private ListViewAdapter mAdapter = null;

    // 서버 정보를 파싱하기 위한 변수 선언
    String myJSON; 
    private static final String TAG_RESULTS="result"; 
    private static final String TAG_UID = "uid"; 
    private static final String TAG_NAME = "name"; 
    private static final String TAG_Mobile ="mobile"; 
 
    JSONArray peoples = null;   

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
       
        // ActionBar 제거하기
        ActionBar actionbar = getActionBar();
        actionbar.hide();

        mListView = (ListView) findViewById(R.id.listView); 
        mAdapter = new ListViewAdapter(this);       
       
        // 서버에 있는 정보를 읽어다가 mAdapter.addItem 에 추가하는 과정
        getDbData(Value.IPADDRESS + "/mobile/get_json.php");
       
        mListView.setAdapter(mAdapter);
       
        TextView searchView = (TextView) findViewById(R.id.SearchView);
        TextView phonebookView = (TextView) findViewById(R.id.PhonebookView);       

        searchView.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v) {
                Toast.makeText(getApplicationContext(), "검색화면으로 이동합니다", Toast.LENGTH_LONG).show();
                Intent intent = new Intent(MainActivity.this, Search_Item.class);
                startActivity(intent);
            }           
        });
       
        phonebookView.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v) {               
                Toast.makeText(getApplicationContext(), "내 폰의 전화번호부를 가져옵니다", Toast.LENGTH_LONG).show();
                Intent intent = new Intent(MainActivity.this, PhonebookActivity.class);
                startActivity(intent);
            }
           
        });

    }

   
    private void getDbData(String string) {
        class GetDataJSON extends AsyncTask<String, Void, String>{ 
           
            @Override 
            protected String doInBackground(String... params) { 
 
                String uri = params[0];
               
 
                BufferedReader bufferedReader = null; 
                try { 
                    URL url = new URL(uri); 
                    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                    // 세션 쿠키 전달
                    String cookieString = CookieManager.getInstance().getCookie(Value.IPADDRESS);
                   
                    StringBuilder sb = new StringBuilder(); 
 
                    if(conn != null){ // 연결되었으면
                        //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.setConnectTimeout(10000);
                        conn.setReadTimeout(10000);
                        conn.setUseCaches(false);
                        conn.setDefaultUseCaches(false);
                        conn.setDoOutput(true); // POST 로 데이터를 넘겨주겠다는 옵션
                        //conn.setDoInput(true);
                       
                        int responseCode = conn.getResponseCode();
                        System.out.println("GET Response Code : " + responseCode);       
                        if(responseCode == HttpURLConnection.HTTP_OK){ // 연결 코드가 리턴되면
                            bufferedReader = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
                            String json;
                            while((json = bufferedReader.readLine())!= null){
                                sb.append(json + "\n");
                            }     
                        }
                        bufferedReader.close();
                    }
                    return sb.toString().trim(); 
 
                } catch(Exception e){ 
                    return new String("Exception: " + e.getMessage());
                }  
 
            } 
 
            protected void onPostExecute(String result){ 
                myJSON=result; 
                showList(); 
            }           
        } 
       
        GetDataJSON g = new GetDataJSON(); 
        g.execute(string);                
       
    }
   
    protected void showList() {
        // 서버에서 읽어온 정보를 mAdapter 에 저장하고 화면에 출력
        try { 
            JSONObject jsonObj = new JSONObject(myJSON); 
            peoples = jsonObj.getJSONArray(TAG_RESULTS);
           
            for(int i=0;i<peoples.length();i++){ 
                JSONObject c = peoples.getJSONObject(i); 
                String uid = c.getString(TAG_UID); 
                String name = c.getString(TAG_NAME); 
                String mobile = c.getString(TAG_Mobile); 
                Drawable myIcon = getResources().getDrawable( R.drawable.ic_launcher );
               
                mAdapter.addItem(myIcon,uid,name,mobile);
            }
           
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    mAdapter.notifyDataSetChanged();
                }
            });
           
        } catch (JSONException e) { 
            e.printStackTrace(); 
        }

    } 

    class ViewHolder {
         public LinearLayout child_layout;
         public ImageView mImage;     
         public Button childListBtn;     
         public TextView name;
         public TextView mobile;
    }

    private class ListViewAdapter extends BaseAdapter {

        private Context mContext = null;
        private ArrayList<ListData> mListData = new ArrayList<ListData>();
       
        public ListViewAdapter(Context mContext) {
            super();
            this.mContext = mContext;
        }
       
        @Override
        public int getCount() {       
            return mListData.size();
        }

        @Override
        public Object getItem(int position) {       
            return mListData.get(position);
        }

        @Override
        public long getItemId(int position) {       
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder viewHolder;
            View view = convertView;
            if (view == null) {
                viewHolder = new ViewHolder();
         
                LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                view = inflater.inflate(R.layout.list_item, parent, false);
               
                view.setBackgroundColor(0x00FFFFFF);
                view.invalidate();
         
                viewHolder.child_layout = (LinearLayout) view.findViewById(R.id.child_layout);
                viewHolder.mImage = (ImageView) view.findViewById(R.id.mImage);
                viewHolder.childListBtn = (Button ) view.findViewById(R.id.childListBtn);
                viewHolder.name = (TextView) view.findViewById(R.id.name);
                viewHolder.mobile = (TextView) view.findViewById(R.id.mobile);
         
                view.setTag(viewHolder);
            } else {
                viewHolder = (ViewHolder) view.getTag();
            }
           
            final ListData mData = mListData.get(position);
           
            if (mData.mImage != null) {
                viewHolder.mImage.setVisibility(View.VISIBLE);
                viewHolder.mImage.setImageDrawable(mData.mImage);
            } else {
                viewHolder.mImage.setVisibility(View.GONE);
            }
           
            viewHolder.childListBtn.setText(mData.uid);
            viewHolder.name.setText(mData.name);
            viewHolder.mobile.setText(mData.mobile);
           
            viewHolder.childListBtn.setOnClickListener(new Button.OnClickListener(){

                @Override
                public void onClick(View v) {
                   
                    Vibrator vibe = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
                    vibe.vibrate(50);
                   
                    AlertDialog showdialog = new AlertDialog.Builder(MainActivity.this)
                            .setTitle(mData.name)
                            .setMessage(mData.mobile + " 통화하시겠습니까?")
                            .setPositiveButton("예",
                                    new DialogInterface.OnClickListener() {

                                        public void onClick(DialogInterface dialog,int which) {

                                            Intent i = new Intent(Intent.ACTION_CALL,Uri.parse("tel:"+ mData.mobile));
                                            i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                                            startActivity(i);
                                        }

                                    })
                            .setNegativeButton(
                                    "아니오",
                                    new DialogInterface.OnClickListener() {

                                        public void onClick(DialogInterface dialog,int which) {
                                            dialog.dismiss();
                                        }
                                    }).create();
                    showdialog.show();
                }
               
            });
           
            return view;
        }

        public void addItem(Drawable icon, String uid, String name, String mobile){
            ListData addInfo = null;
            addInfo = new ListData();
            addInfo.mImage = icon;
            addInfo.uid = uid;
            addInfo.name = name;
            addInfo.mobile = mobile;
                    
            mListData.add(addInfo);
        }
       
        public void remove(int position){
            mListData.remove(position);
            mAdapter.notifyDataSetChanged();
        }
   
   
    }
   
    // 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);
     }

}


지금까지 단계적으로 배우면서 작성한 코드에 대한 정리본인 셈이라고 볼 수도 있다.

기존 포스팅 자료에 비해 내용이 점점 보강되어간다.


폰에 있는 전화번호 읽어오기는 생각보다 처리하는 걸 고민해야 하는 거 같다.

알아야 할 지식도 더 많은 거 같아서 자바 책을 좀 들여다보고 있다.

블로그 이미지

Link2Me

,