728x90
반응형

1. 같은 와이파이

 

2. 먼저 스마트폰을 컴퓨터에 USB로 연결

 

 

3. cmd 창에서 5555 포트로 지정

adb tcpip 5555

 

성공시 나타나는 내용 : restarting in TCP mode port: 5555

 

4. 이제 USB를 연결해제

 

그다음 스마트폰의 IP주소를 알아내야 합니다.

알아내는 방법은 다양하므로 몇가지만 알려드린후 넘어가겠습니다.

 

설정 앱 - 휴대폰 정보 - 상태(또는 네트워크) - IP 주소

설정 앱 - WIFI - WIFI 고급 설정(또는 WIFI 설정) - IP 주소

 

adb connect <Your IP>:5555

 

성공시 나타나는 내용 : connected to <IP>:5555

 

 

5. adb devices 로 연결 상태를 확인할 수 있습니다.

728x90
반응형
728x90
반응형

현재 android java 로 개발된 서비스 중인 앱은 sms 인증 방식을 restful api로 서버에 요청하고,

서버에서 aws를 통해 문자를 보내주는 형식으로 전화번호 인증을 하고있다.

저번주에, vpn을 통해 과도한 문자청구가 되고, 백엔드 개발팀이 rate limiting을 통해 악의적인 유저를 블락하려고 해봤으나,

금주에도 동일한 공격이 발생했다.

 

그래서 FE에서 구글 sms 를 사용하기로 했다. 구글 firebase 는 recapcha 방식을 통해 악의적인 공격을 막아주기 때문이다.

 

전에 firebase sms auth로 sms 를 사용했을때, 트래킹이 되지않아 현재의 방식으로 개발했는데,

vpn을 쓰며 천천히 인증을 계속 돌리는 상황이 막상 닥치니 빨리 구글 인증으로 바꾸는 방식으로 회의를 마쳤다.

 

일단 현재 앱은 google Oauth를 사용하고 있기에,  gradle 파일에 세팅은 완료 된 상태이다.

 

따라서, api를 쏴주던 함수만 수정하면된다.

 

if(parsedPhoneNumber != null){
    PhoneAuthProvider.getInstance().verifyPhoneNumber(
            "+"+parsedPhoneNumber,        // Phone number to verify
            60,                 // Timeout duration
            TimeUnit.SECONDS,   // Unit of timeout
            this,               // Activity (for callback binding)
            mCallbacks);        // OnVerificationStateChangedCallbacks
}

해당 부분으로 실행해주고, 

 

private void initCallback(){

        mCallbacks = new PhoneAuthProvider.OnVerificationStateChangedCallbacks() {

            @Override
            public void onCodeAutoRetrievalTimeOut(String verificationId) {
                btnNext.setEnabled(true);
//                if (progressDialog != null) {
//                    dismissProgressDialog(progressDialog);
//                }
//                notifyUserAndRetry("Your Phone Number Verification is failed.Retry again!");
            }

            @Override
            public void onVerificationCompleted(PhoneAuthCredential credential) {
                btnNext.setEnabled(true);
                Log.d("onVerificationCompleted", "onVerificationCompleted:" + credential);
//                if (progressDialog != null) {
//                    dismissProgressDialog(progressDialog);
//                }
//                signInWithPhoneAuthCredential(credential);
            }

            @Override
            public void onVerificationFailed(FirebaseException e) {
                btnNext.setEnabled(true);
                Log.w("onVerificationFailed", "onVerificationFailed", e);

//                if (progressDialog != null) {
//                    dismissProgressDialog(progressDialog);
//                }

                if (e instanceof FirebaseAuthInvalidCredentialsException) {
                    Log.e("Exception:", "FirebaseAuthInvalidCredentialsException" + e);
                } else if (e instanceof FirebaseTooManyRequestsException) {
                    Log.e("Exception:", "FirebaseTooManyRequestsException" + e);

                }

//                notifyUserAndRetry("Your Phone Number Verification is failed.Retry again!");
            }

            @Override
            public void onCodeSent(String verificationId,
                                   PhoneAuthProvider.ForceResendingToken token) {
                btnNext.setEnabled(true);
                Log.e("onCodeSent", "onCodeSent:" + verificationId);
                Log.e("Verification code:", verificationId);
                Intent intent = new Intent(getApplicationContext(), PhoneCertification.class);

                intent.putExtra("parsedPhoneNumber", parsedPhoneNumber);
                intent.putExtra("conuntryCd", conuntryCd);
                intent.putExtra("verificationId",verificationId);
                startActivity(intent);
                finish();
            }
        };
    }

콜백함수를 등록해준다.

 

6자리 인증하는 부분에서는

먼저 함수를 작성해주고

private void signInWithPhoneAuthCredential(PhoneAuthCredential credential) {
    mAuth.signInWithCredential(credential)
            .addOnCompleteListener(PhoneCertification.this, new OnCompleteListener<AuthResult>() {
                @Override
                public void onComplete(@NonNull Task<AuthResult> task) {
                    if (task.isSuccessful()) {
                        //verification successful we will start the profile activity
                        Log.e("성공 :::::::::","성공했다 ::::");

                    } else {


                        String message = "Somthing is wrong, we will fix it soon...";

                        if (task.getException() instanceof FirebaseAuthInvalidCredentialsException) {
                            message = "Invalid code entered...";
                        }
                        Log.e("실패했다 :::::",message);


                    }
                }
            });
}

해당함수를 PhoneAuthCredential 객체를 만들고 param으로 넘겨줬다

try{
    PhoneAuthCredential credential = PhoneAuthProvider.getCredential(verificationId, certification);
    signInWithPhoneAuthCredential(credential);
}catch (Exception e){
    Log.e("PhoneAuthCredential error ::::", String.valueOf(e));
}

RN에선 instance 를 사용하며 6자리까지 인증했던 기억이 나는데,

java에선 보내고, 해당 id 로 instance 에서 PhoneAuthCredential 객체를 생성한후, 그 객체에 대한 값을 확인하는 로직으로 돌아가는걸 알 수 있었다.

 끝

728x90
반응형
728x90
반응형

현재 우리 회사에서 서비스 하고있는 플랫폼엔 모두 Oauth가 적용되어있다.

 

최근 런칭한 서비스를 QA 돌리는중에, 로그아웃하니 처음 연결했던 구글 아이디로만 로그인한다는 QA가 있어서

 

아차 싶었다.

 

항상 Oauth를 사용해서 로그인 할때는, 해당 Oauth sdk(위젯)에서 로그아웃을 실행해줘야한다.

 

룰루랄라 해당 기능에서 로그인 기능을 만들어 놓은 함수

 

public void googleOauthSignOut() {
    try {
    mGoogleSignInClient.signOut()
            .addOnCompleteListener(this, task -> {
                mAuth.signOut();
            });
    gsa = null;
    }catch (Exception e){
        Log.e("signOut error :::;",e.toString());
    }
}

를 import 해서 사용했더니

 

Attempt to invoke virtual method 'android.os.Looper android.content.Context.getMainLooper()' on a null object reference

 

해당 에러가 났다.

 

전역으로 context를 관리하는데도, 가끔 nullpointException이 발생한다.

 

찾아보니 onCreate에서 객체 선언을 해줘야 한다는 글이 있어서, 해당 기능을 사용하는 Activity onCreate에서 

 

GoogleSignInOptions googleSignInOptions = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
        .requestIdToken(getString(R.string.default_web_client_id))
        .requestEmail()
        .build();

mGoogleSignInClient = GoogleSignIn.getClient(this, googleSignInOptions);

객체 선언을 해줬더니 정상 작동했다.

 

참고로 로그아웃할때는 항상

 

instance초기화(fcm / oAuth / local db 등등)을 잊지 말아야하는데, 이번 QA 검수 넣은 apk엔 oAuth 관련 초기화가 빠져있었다.

728x90
반응형
728x90
반응형

테스트 폰을 새로 받아서 기존 프로젝트를 빌드해보는데

 

The application could not be installed: SHELL UNRESPONSIVE

라는 에러가 떴다.

 

adb devices 명령어를 쳐봐도 제대로 연결이 되어있다고 나오는데, 

 

cache를 제거하고 새로 시작하니 정상작동했다.

 

RN 을 할때는 ./gradlew clean을 밥먹듯이 썼는데, 안드로이드 스튜디오를 사용하면서 캐시나 프로젝트 클린을 자주 사용하지 않는 것같다.

728x90
반응형
728x90
반응형

WebViewClient를 extends 한 webview 클래스를 만들어서 공통으로 사용중이다.

 

private class CustomWebViewClient extends WebViewClient {


    @Override
    public void onPageStarted(android.webkit.WebView webview, String url, Bitmap favicon) {
        webview.setVisibility(webview.INVISIBLE);
    }

    @Override
    public void onPageFinished(android.webkit.WebView view, String url) {

        spinner.setVisibility(View.GONE);
        view.setVisibility(web.VISIBLE);
        super.onPageFinished(view, url);

    }

    @Override
    public void doUpdateVisitedHistory(WebView view, String url, boolean isReload) {
        Log.e("doUpdateVisitedHistory url:::::",url);
        Log.e("doUpdateVisitedHistory view.toString:::::",view.toString());
        currentUrl = url;
        super.doUpdateVisitedHistory(view, url, isReload);
    }
}

위의 소스 코드중에 doUpdateVisitedHistory 를 통해, 웹의 url등을 알 수 있다.

728x90
반응형
728x90
반응형

에러 내용을 읽어보면 31 버전부터는 pendingIntent에 mutable 인지 immutable 인지 명시가 되어 있어야 한다고한다.

 

기존 코드에 FLAG_IMMUTABLE을 추가해줬다.

기존코드

PendingIntent pendingIntent = PendingIntent.getBroadcast(context, REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT );

추가후 코드

PendingIntent pendingIntent = PendingIntent.getBroadcast(context, REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);

 

728x90
반응형
728x90
반응형

앱이 백그라운드에서 돌거나, QA를 하면서 나왔던 에러를 다시 구현할때 어려움이 있어서,

앱내에 로그파일을 남겨야겠다고 생각했다. 

dev 와 staging에서만 사용하고, release 모드에선 해당 기능을 숨기거나, 앱내 폴더로 숨길 예정이다.

해당 코드는

 

public class LogFileWriter {
    private static final String TAG = "LogFileWriter";
    private static final String LOG_FILE_NAME = "애플리케이션_log.txt";

    public static void writeLogToFile(String message) {

        String logText = getCurrentTimestamp() + " - " + message + "\n";

        // 외부 저장소의 앱 디렉토리 경로 가져오기
        String appDirPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/애플리케이션";
        File appDir = new File(appDirPath);

        // 앱 디렉토리가 없으면 생성
        if (!appDir.exists()) {
            appDir.mkdirs();
        }

        // 로그 파일 생성 또는 열기
        File logFile = new File(appDir, LOG_FILE_NAME);
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(logFile, true); // 이어쓰기 모드로 파일 열기
            fos.write(logText.getBytes());
        } catch (IOException e) {
            Log.e(TAG, "Error writing log to file: " + e.getMessage());
        } finally {
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    Log.e(TAG, "Error closing file output stream: " + e.getMessage());
                }
            }
        }
    }

    private static String getCurrentTimestamp() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
        Date now = new Date();
        return sdf.format(now);
    }
}

이고 로그를 기록하고 싶은 try catch문에서 사용하면 된다

728x90
반응형
728x90
반응형

한시간에 한번씩 주소록이 동기화 되고 친구가 추가되었으면 좋겠다는 요청사항이 있었다.

앱을 사용중일때, 주소록을 가지고와서 api를 쏘고, room db를 업데이트 하면, 혹시라도 앱이 느려지는 것 처럼 보일까봐,

처음에는 주소록을 가지고 key value 값으로 no sql embeded db에 저장하고나서, 한시간에 한번씩 쏘고 받고 할까도 생각을 해봤는데,

주소록이 업데이트 되었는지 확인하는 로직을 넣으면 어짜피 연락처에 한번은 접근해야된다는 생각으로 

백그라운드 일때만 한번씩 주소록을 서버로 쏴주는 식으로 비지니스 로직을 정리했다.

 

public class SplashActivity extends BaseActivity {
   

    private static final int REQUEST_CODE = 5009;
    public static final String ACTION_ALARM = "프로젝트 이름.ALARM";

   
    @Override
    protected void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        // 디스플레이 화면 가로 세로 길이

        startHourlyFunction(this);

앱이 실행될때, 실행하는 activity에서

 

private void startHourlyFunction(Context context) {
    AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);

    // Calendar 객체를 사용하여 실행 시간 설정
    Calendar calendar = Calendar.getInstance();
    calendar.setTimeInMillis(System.currentTimeMillis());

    // 매시간마다 실행하려는 시간 설정
    calendar.set(Calendar.MINUTE, 0);
    calendar.set(Calendar.SECOND, 0);
    calendar.add(Calendar.HOUR_OF_DAY, 1);

    // 알람을 실행할 BroadcastReceiver 지정
    Intent intent = new Intent(context, AlarmReceiver.class);
    intent.setAction(ACTION_ALARM);
    PendingIntent pendingIntent = PendingIntent.getBroadcast(context, REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT);

    // 매시간마다 알람 설정
    alarmManager.setRepeating(
            AlarmManager.RTC_WAKEUP,
            calendar.getTimeInMillis(),
            AlarmManager.INTERVAL_HOUR,
            pendingIntent
    );
}

해당함수 실행 후,

public class AlarmReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent != null && intent.getAction() != null && intent.getAction().equals(SplashActivity.ACTION_ALARM)) {
            // 장치를 깨웁니다.
            PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
            PowerManager.WakeLock wakeLock = powerManager.newWakeLock(
                    PowerManager.PARTIAL_WAKE_LOCK, "MyApp:WakeLock");
            wakeLock.acquire();

            // 실행할 함수를 여기에 작성
            executeHourlyFunction();

            // WakeLock을 해제합니다.
            wakeLock.release();
        }
    }

    private void executeHourlyFunction() {
        Log.e("executeHourlyFunction","실행 ::::");
        try {
            // 앱이 백그라운드에 있을때 실행되도록
            if(앱이 백그라운드 인지 확인하는 함수){
                //전화번호부

                //API

                //sharedPreference 시간 업데이트
                long millis =
                        Calendar.getInstance(TimeZone.getTimeZone("GMT")).getTimeInMillis();
                GlobalPreference.setLongData("contactRefresh",millis);

            }
        }catch (Exception e){
            Log.e("executeHourlyFunction error ::",e.toString());
        }



    }
}

broadcast receiver를 달아줘서 로직 실행되도록 설정했다.

 

업데이트한 시간은 sharedPreference에 저장

 

manifest파일에

<uses-permission android:name="android.permission.WAKE_LOCK" /> <!-- AlarmReceiver -->

<receiver android:name=".broadcastReceiver.AlarmReceiver" />

추가했다.

728x90
반응형
728x90
반응형

현재 서비스 중인 채팅 앱에 링크가 있을때, 오픈그래프를 적용해달라는 요구사항이 있었다.

 

메세지중에, 링크 값이 있으면, 해당 채팅 VO의 값중 url을 체크해주는 boolean 값을 true로 바꾸고, 

 

Jsoup 라이브러리를 통해 해당 링크를 크롤링해와서 메타데이터에서 타이틀과 이미지를 가져오는 방식으로 로직을 짰다.

 

naver.com 을 보냈는데 해당에러가 발생해서 보니 http 프로토콜이 붙어있지 않아서 나는 에러였다.

당장은

if(!contentsUrl.startsWith("http")) return;

해당 코드를 추가해서 일단은 동작하도록 두었으나,

 

google 등 일부 url에서 미리보기가 안되는 문제가 있어서 추후 라이브러리를 교체할 예정이다.

728x90
반응형
728x90
반응형

onBackPressed()를 오버라이딩하면 가끔 원하는대로 오버라이딩이 안될때가있어서,

키버튼이 눌렸을때의 call back 함수인 onKeyDown을 오버라이딩 한 후, 

key code가 뒤로가기일때 따로 분기처리해서 함수처리를 해주었다.

@Override
public boolean onKeyDown(int keycode, KeyEvent event) {
   
    if(keycode ==KeyEvent.KEYCODE_BACK) {
     //여기서 이벤트 처리를 해주면 된다
        return true;
    }

    return false;
}

keycode 는 

https://developer.android.com/reference/android/view/KeyEvent

 

KeyEvent  |  Android Developers

 

developer.android.com

에서 참고하면된다.

728x90
반응형

+ Recent posts