2016년 3월 4일 금요일

로고 화면 및 광고

Handler handler = new Handler() {
public void handleMessage(Message msg) {
//Intent intent = new Intent(Logo.this, LetterActivity.class);
Intent intent = new Intent(Logo.this, SetupMyInfo.class);
startActivity(intent);
finish();
}
};

handler.sendEmptyMessageDelayed(0, 1400);



private AdView adView;
private InterstitialAd interstitial;

AdRequest adRequest;
LinearLayout adLayout;
private void showAd() {
adView = new AdView(this);
adView.setAdSize(AdSize.SMART_BANNER);
adView.setAdUnitId(MyUtil.AD_UNIT_ID);

adLayout = (LinearLayout) findViewById(R.id.adSpace);
adLayout.addView(adView);

interstitial = new InterstitialAd(this);
interstitial.setAdUnitId(MyUtil.AD_INTERSTITIAL_ID);

interstitial.setAdListener(new AdListener() {
@Override
public void onAdLoaded() {
}

@Override
public void onAdFailedToLoad(int errorCode) {
}

@Override
public void onAdClosed() {
interstitial.loadAd(adRequest);
}
});


if (MyUtil.TESTING) {
adRequest = new AdRequest.Builder()
.addTestDevice(MyUtil.testDeviceId)
.build();
}
else {
adRequest = new AdRequest.Builder().build();
}
interstitial.loadAd(adRequest);
adView.loadAd(adRequest);
}

public void displayInterstitial() {
if (interstitial == null) return;
if (interstitial.isLoaded()) {
interstitial.show();
}
}

2016년 2월 13일 토요일

안드로이드 파일에 로깅하기

안드로이드에서 서비스, 브로드캐스트리시버 등을 활용한 프로그래밍을 할 때에는
정확한 동작을 확인하기 위해서 로깅을 할 필요가 있다.

뿐만 아니라, LogCat으로 보기에는 불편한 점도 많기 때문에 나만의 파일 로깅을 통해서
동작을 명확히 알고 싶은 경우가 많다.

그래서 몇 가지 알아보다가
microlog4android 를 사용해 봤다.
근데 앱을 시작할때마다 파일이 새롭게 작성이 되어서;;;분명히 내가 잘못 설정한 거 같긴
한데 원인 파악하기 귀찮아서 다른 걸로 해봤다.

android log4j 가 내가 원하는 데로 동작함을 확인할 수 있었다.

1)  android studio gradle에서 dependency 추가하기

compile 'de.mindpipe.android:android-logging-log4j:1.0.2@jar'



public class ConfigureLog4J {
    public static void configure() {
        final LogConfigurator logConfigurator = new LogConfigurator();

        logConfigurator.setFileName(Environment.getExternalStorageDirectory() + File.separator + "logfile.log");
        logConfigurator.setRootLevel(Level.DEBUG);
        // Set log level of a specific logger
        logConfigurator.setLevel("org.apache", Level.ERROR);
        logConfigurator.configure();
    }
}

ConfigureLog4J 라는 클래스 생성하고 로깅 하기 원하는 클래스에서



 private static final Logger logger = Logger.getLogger(MainActivity.class);

 static {
    ConfigureLog4J.configure();
 }


와 같이 전역으로 선언하고

logger.info(" 메시지 \n");

와 같이 사용하면 된다.

추가적으로 생성한 로깅 파일을 읽어들이는 코드.



public class LogWatchActivity extends AppCompatActivity {

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

        File sdcard = Environment.getExternalStorageDirectory();
        File file = new File(scared,"logfile.log");

        StringBuilder text = new StringBuilder();

        try {
            BufferedReader br = new BufferedReader(new FileReader(file));
            String line;

            while ((line = br.readLine()) != null) {
                text.append(line);
                text.append('\n\n\n');
            }
            br.close();
        }
        catch (IOException e) {
        }

        EditText et = (EditText)findViewById(R.id.et);
        et.setText(text.toString());
    }
}



EditText 하나 두고 로깅한 파일을 출력해주고 있다.


ScreenLock 의 구현

휴대폰을 키자 마자 어떤 화면을 띄우게 만들고 싶을 때는
Intent.ACTION_SCREEN_OFF
를 받아들이는 BroadcastReceiver를 등록(registerReceiver)하면 된다.

얼핏 생각하기에
AndroidManifest.xml에

<receiver    
   android:name=".ScreenOffReceiver"    
   android:enabled="true"   
   android:exported="true">
    <intent-filter>       
        <action android:name="android.intent.action.ACTION_SCREEN_OFF"/>    
    </intent-filter>
</receiver>

위와 같이 등록하면 될 거라 생각할 수 있지만 안드로이드 프레임워크 내부적으로
몇 가지 필터링은 manifest에서 등록해서 사용할 수가 없다.

성능상의 문제때문에 그런 거 같다.

그래서 이를 우회적으로 구현을 해야만 한다.

AlarmManager를 통해서 Service를 반복적으로 실행시켜야 한다.
그리고 그 Service에서 동적으로 registerReceiver() 함수를 호출해서
ACTION_SREEN_OFF 에 대한 통지를 받고 호출되는 Receiver를 등록해준다.

얼만큼의 주기로 Service를 반복 호출할지는 개발자가 정해야 하는데,
메모리를 자주 정리하며 스맛폰을 사용하는 사용자가 아닌 이상
그리 자주 할 필요는 없다.

Service가 호출이 되어야 하는 시점은
우리의 앱이 kill 이 되었을 때이다.
kill 되지 않았으면 굳이 호출하지 않아도 크게 문제가 없다.

예를 들어 5초 주기로 호출한다면 앱을 아무리 수동으로 kill 하고 바로
화면을 껐다 켜도 대부분 원하는 화면이 바로 뜨는 것을 확인할 수가 있다.

어쨌든 구현 순서는 다음과 같다.

AlarmManager 을 통해서  Service를 호출하는 Receiver를 호출하도록 한다.



AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);

Intent myIntent = new Intent(MainActivity.this, RegisterReceiverServiceAlamReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(MainActivity.this, 0, myIntent, 0);

long period = 1000 * 5;
long after = 1000 * 5;
long t = SystemClock.elapsedRealtime();
alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, t + after, period, pendingIntent);


RegisterReceiverServiceAlamReceiver 는 AlarmManager에 의해 호출되어
RegisterReceiverService를 호출할 뿐이다.


public class RegisterReceiverServiceAlamReceiver extends BroadcastReceiver {

    public RegisterReceiverServiceAlamReceiver() {
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        Intent service1 = new Intent(context, RegisterReceiverService.class);
        context.startService(service1);
    }
}

RegisterReceiverService 는 실제 SCREEN_OFF를 BroadCast받아서 Activity를 띄워주는
녀석을
registerReceiver  해준다.


public class RegisterReceiverService extends Service {
    public RegisterReceiverService() {
    }

    private ScreenOffReceiver mReceiver = null;

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();

        mReceiver = new ScreenOffReceiver();
        IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
        registerReceiver(mReceiver, filter);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId){
        super.onStartCommand(intent, flags, startId);

        if(intent != null){
            if(intent.getAction()==null){
                if(mReceiver==null) {
                    mReceiver = new ScreenOffReceiver();
                    IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
                    registerReceiver(mReceiver, filter);
                }
            }
        }
        return START_REDELIVER_INTENT;
    }

    @Override
    public void onDestroy(){
        super.onDestroy();

        if(mReceiver != null){
            unregisterReceiver(mReceiver);
        }
    }
}



ScreenOffReceiver 는 실제로 SCREEN_OFF 를 받아서 Activity 를 띄워주는 BroadCastReceiver이다.


public class ScreenOffReceiver extends BroadcastReceiver {
    private TelephonyManager telephonyManager = null;
    private boolean isPhoneIdle = true;

    public ScreenOffReceiver() {
    }

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

        if(telephonyManager == null){
            telephonyManager = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);
            telephonyManager.listen(phoneListener, PhoneStateListener.LISTEN_CALL_STATE);
        }

        if(isPhoneIdle){
            if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
                Intent i = new Intent(context, ScreenOnActivity.class);
                i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                context.startActivity(i);
            }
        }
    }

    private PhoneStateListener phoneListener = new PhoneStateListener(){
        @Override
        public void onCallStateChanged(int state, String incomingNumber){
            switch(state){
                case TelephonyManager.CALL_STATE_IDLE :
                    isPhoneIdle = true;
                    break;
                case TelephonyManager.CALL_STATE_RINGING :
                    isPhoneIdle = false;
                    break;
                case TelephonyManager.CALL_STATE_OFFHOOK :
                    isPhoneIdle = false;
                    break;
            }
        }
    };
}

여기서 다소 주의할 점은 전화 받는 상황에서 화면이 꺼지는 경우에는 동작하지 않도록
isPhoneIdle이라는 flag 로 구분하였다.

이상의 기능을 구현하기 위해 총 2개의 permission 을 더해줘야 한다.

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

안드로이드) AlarmManager와 BroadcastReceiver 를 이용한 주기적인 작업 수행

예를 들어서 주기적으로 사용자에게 물을 마시라고 notification을 날리는 앱을 만들어보자.

먼저 AlarmManager 라는 녀석을 얻어온다.

 AlarmManager alarmManager = (AlarmManager)getSystemService(ALARM_SERVICE);

주기를 생각하자. 5초 이후부터 5초에 한번씩이라고 하면


long period = 1000 * 5;
long after = 1000 * 5;

그러면 AlarmManager의 호출을 받아줄 BroadcastReceiver 를 만들자.



public class MyReceiver extends BroadcastReceiver {
    public MyReceiver() { }

    private NotificationManager mManager;

    @Override
    public void onReceive(Context context, Intent intent) {
        NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);

        Notification.Builder mBuilder = new Notification.Builder(context);
        mBuilder.setSmallIcon(R.mipmap.ic_launcher);
        mBuilder.setTicker("hi");
        mBuilder.setContentTitle("hi1");
        mBuilder.setContentText("hi3");

        mBuilder.setDefaults(Notification.DEFAULT_SOUND | Notification.DEFAULT_VIBRATE);
        //mBuilder.setContentIntent(pendingIntent);
        mBuilder.setAutoCancel(true);

        nm.notify(111, mBuilder.build());

    }
}

이 BroadcastReceiver 는 alramManager에 의해 5초 주기로 호출이 될 것이다.
호출이 되면 무엇을 할 것인지를onReceive에서 정의해준다.
위 코드에서는 간단한 notification 을 구현하였다.

그럼, AlarmManager에 작성한 BroadCastReceiver를 등록시키자.


AlarmManager alarmManager = (AlarmManager)getSystemService(ALARM_SERVICE);

Intent receiverIntent = new Intent(MainActivity.this, MyReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(MainActivity.this, 0, receiverIntent, 0);

long period = 1000 * 5;
long after = 1000 * 5;
long t = SystemClock.elapsedRealtime();

alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, t + after, period, pendingIntent);


AlarmManager.setRepeating() 을 통해 등록이 되어

이후부터 5초 주기로 notifcation이 발생한다.

notifcation 발생을 취소하고 싶으면 cancel() 함수를 호출해주면 된다.



Intent myIntent = new Intent(MainActivity.this, MyReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(MainActivity.this, 0, myIntent, 0);
alarmManager.cancel(pendingIntent);


그런데 이렇게 하면 리붓하고 나서는 알람이 발생하지 않는다.

리붓이 되었다는 메시지를 받아서 위 알람을 다시 등록해줘야 껏다 켜도 다시 알람이

발생하게 된다.

그러면 boot 이벤트를 받아들이는 BroadCastReceiver를 만들어야 한다.

먼저 권한이 있어야 boot  이벤트를 받아들일 수 있으므로

AndroidManifest.xml 에 등록해주자.

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

<receiver    android:name=".BootReceiver"    android:enabled="true"    android:exported="false" >    <intent-filter>        <action android:name="android.intent.action.BOOT_COMPLETED" />    </intent-filter></receiver>


BootReceiver 는 인텐트 필터링을 통해 BOOT_COMPLETED 에 의해 호출이 되도록 한다.


public class BootReceiver extends BroadcastReceiver {
    public BootReceiver() {
    }

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

        AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);

        Intent myIntent = new Intent(context, MyReceiver.class);
        PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, myIntent, 0);

        long period = 1000 * 5;
        long after = 1000 * 5;
        long t = SystemClock.elapsedRealtime();
        alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, t + after, period, pendingIntent);
    }
}

여끼까지 하고 나면 껐다 켜도 다시 알람에 의해서 notification 이 발생하게 된다.