이번 포스팅은 특정한 시간이 되면 푸쉬 알람이 올 수 있도록 구현하는 방법입니다.



먼저 프로젝트를 하나 생성해야한다.


Application name은 Alarmtest라 작성했지만, 사용자가 편한 앱 이름을 설정해주면 된다.


다음은 Manifest이다.




<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.kwongyo.alarmtest" >
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme" >
<activity android:name=".MainActivity" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".Main2Activity"
android:label="@string/title_activity_main2"
android:theme="@style/AppTheme.NoActionBar" >
</activity>
<receiver android:name=".BroadcastD"></receiver>
</application>

</manifest>



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

은 안드로이드에서 서버가 있거나 테스트 할 경우 인터넷을 사용해야 한다. 인터넷 사용 환경에서 (인터넷을 사용하겠다는 권한을 주는것) 작성한다.

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

노티피케이션을 사용할 때 진동을 줄지 안 줄지 설정하는 것이다. 해당 폰에서는 permission을 줬기 때문에 노티피케이션(푸쉬)가 올 때 알람이 진동이 발생한다.

특정하게 알람을 줄 때 안줄때를 Client에서 결정하게 할 작정이라면, Java코드에서 notification.defaults=Notification.DEFAULT_ALL 혹은 Notification.DEFAULT_VIBRATE를 직접 주면 됩니다. 


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

다음 권한은 안드로이드가 화면이 어두워져 있을 때 ( 잠금상태 일 때 ) 화면을 깨울때 주는 권한이다.



다음은 MainActivity입니다.



import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

import java.util.Calendar;

public class MainActivity extends AppCompatActivity {
private static int ONE_MINUTE = 5626;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new AlarmHATT(getApplicationContext()).Alarm();

}
public class AlarmHATT {
private Context context;
public AlarmHATT(Context context) {
this.context=context;
}
public void Alarm() {
AlarmManager am = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(MainActivity.this, BroadcastD.class);

PendingIntent sender = PendingIntent.getBroadcast(MainActivity.this, 0, intent, 0);

Calendar calendar = Calendar.getInstance();
//알람시간 calendar에 set해주기

calendar.set(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DATE), 23, 12, 0);

//알람 예약
am.set(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), sender);
}
}

}



나중에 분리할 수 있는 코드로 작성하기 위해서 AlarmHATT와 MainActivity와 다른 클래스로 작성했습니다.( 실제로 개발한 HATT-스케쥴러 앱은 코드가 MVC패턴이 적용되어 분리되어 있습니다.)


AlarmManager am = (AlarmManager)getSystemService(Context.ALARM_SERVICE);

AlarmManager는 device에 미래에 대한 알림같은 행위를 등록할 때 사용합니다.

Intent intent = new Intent(MainActivity.this, BroadcastD.class);

알람이 발생했을 경우, BradcastD에게 방송을 해주기 위해서 명시적으로 알려줍니다.


PendingIntent sender = PendingIntent.getBroadcast(MainActivity.this, 0, intent, 0);
MainActivity.this => PendingIntent를 부르려는 컨텍스트
int requestCode =>Private request code 인데 현재는 사용하지 않아서 0으로
Intent intent => 앞으로 불려질 Intent
int flags => Intent에 대한 조건설정 플래그
   FLAG_ONE_SHOT : 한번만 사용하고 다음에 이 PendingIntent가 불려지면 fail 하겠다.
   FLAG_NO_CREATE : PendingIntent를 생성하지 않는다. PendingIntent가 실행중인것을 체크하기위함
   FLAG_CANCEL_CURRENT : 이미 실행중인 PendingIntent가 있다면 이를 취소하고 새로 만듬
   FLAG_UPDATE_CURRENT : 이미 실행중인 PendingIntent가 있다면 새로 만들지않고  extra data 만 교체하겠다.


calendar.set(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DATE), 23, 12, 0);

calendar에 시간을 set 해주는데요, year,month,date를 다 set 하고, 당장 test할 시간에 푸쉬를 받을 미래의 시간(약 1분정도)를 기재해 줍니다.


am.set(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), sender);

RTC_WAKEUP과 RTC가 있는데요.

AlarmManager.RTC - 실제 시간을 기준으로 합니다.

AlarmManager.RTC_WAKEUP - RTC와 동일하며, 대기 상태일 경우 단말기를 활성 상태로 전환한 후 작업을 수행합니다.


해당 코드는 RTC_WAKE을 줬습니다. 하지만 이 글을 읽으시는 분들은 이런 생각을 하겠지...("왜 퍼미션 줬는데 저렇게 코딩했지?")

답변->퍼미션을 줘서 RTC를 줘도 상관 없습니다.


BroadcastD.class



import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;

/**
* Created by GHKwon on 2016-02-17.
*/
public class BroadcastD extends BroadcastReceiver {
String INTENT_ACTION = Intent.ACTION_BOOT_COMPLETED;

@Override
public void onReceive(Context context, Intent intent) {//알람 시간이 되었을때 onReceive를 호출함
//NotificationManager 안드로이드 상태바에 메세지를 던지기위한 서비스 불러오고
NotificationManager notificationmanager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, new Intent(context, MainActivity.class), PendingIntent.FLAG_UPDATE_CURRENT);
Notification.Builder builder = new Notification.Builder(context);
builder.setSmallIcon(R.drawable.on).setTicker("HETT").setWhen(System.currentTimeMillis())
.setNumber(1).setContentTitle("푸쉬 제목").setContentText("푸쉬내용")
.setDefaults(Notification.DEFAULT_SOUND | Notification.DEFAULT_VIBRATE).setContentIntent(pendingIntent).setAutoCancel(true);

notificationmanager.notify(1, builder.build());
}

}



해당 코드는 안드로이드 4.1버전 이상(젤리빈? 명칭이 헷갈리네요) 부터 사용이 가능합니다.


해당 코드는 이전에 작성한 포스팅을 참고하시면 될 것 같습니다.

http://kwongyo.tistory.com/4


감사합니다.




참고

http://westwoodforever.blogspot.kr/2013/06/android-javalangsecurityexception.html

http://www.androidside.com/bbs/board.php?bo_table=b49&wr_id=57389



앱에 푸쉬기능을 사용하기 위해서 자체적으로 푸쉬기능을 사용해야 했다.

그래서 GCM종류를 찾아보다가 , 기껏 GCM으로 기능을 구현을 다 해놓으니 , 우리팀에서 서버를 안쓰기로 결정한것...또르르....


그래서 Notification Push를 다시 찾아보게 되었다.


일단 Android Project를 생성하자.



하단에 나와있는

A non-empty directory already exists at the specified project location. Existing files may be overwritten. Procceed with caution.

이라는 경고문은 내가 이미 구현을 마쳐놓은 후에 포스팅을 하기 때문에 발생하는 에러이다.

뭐.. 이미 있으니까 덮어쓰기 주의 하라네요.


Manifest입니다.

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

package="com.example.kwongyo.notificationtest" >
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme" >
<activity android:name="com.example.kwongyo.notificationtest.MainActivity" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />

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

</application>
<service android:name=".NotificationService"
android:enabled="true"/>
<receiver android:name=".NotificationReceiver"/>

</manifest>

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

은 디바이스가 활성화 되지 않았을떄( 켜져있지 않았을 때) 화면을 활성화 시켜주는 옵션이다.

service android:name=".NotificationService"는 service를 등록해주는데, 서비스를 실행시킬 서비스 클래스를 호출한다.

android:enable="true"는 default값이 true이다. 화면의 컴포넌트가 인스턴스화 될 지 안될지 선택하는 속성값이다.

receiver android:name=".NotificationReceiver"/>는 NotificationReceiver (리시버를 받을)클래스를 클래스를 명시적으로 작성해준것이다.


여기에선 service와 receiver는 구현한 부분에 대해선 설명하지 않았다.

service와 receiver는 특정한 시간에 알람을 주거나 특정 행위를 해야 할 때 AlarmManager와 사용한다.
layout입니다.

 <?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity"
android:orientation="vertical">

<TextView
android:id="@+id/textView"
android:text="Hello World!" android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Button
android:id="@+id/alarmBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="alarmBtn"/>
</LinearLayout>


기능이 중요하니 심플하게 작성하고,


MainActivity입니다.


코드는 Layout에 맞춰서 Button을 클릭할 때 마다 알람이 올 수 있도록 작성했습니다.

import android.app.AlarmManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

import java.util.Calendar;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

Button button = (Button)findViewById(R.id.alarmBtn);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
NotificationManager notificationManager= (NotificationManager)MainActivity.this.getSystemService(MainActivity.this.NOTIFICATION_SERVICE);
Intent intent1 = new Intent(MainActivity.this.getApplicationContext(),MainActivity.class); //인텐트 생성.



Notification.Builder builder = new Notification.Builder(getApplicationContext());
intent1.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP| Intent.FLAG_ACTIVITY_CLEAR_TOP);//현재 액티비티를 최상으로 올리고, 최상의 액티비티를 제외한 모든 액티비티를 

없앤다.

PendingIntent pendingNotificationIntent = PendingIntent.getActivity( MainActivity.this,0, intent1,PendingIntent.FLAG_UPDATE_CURRENT);
//PendingIntent는 일회용 인텐트 같은 개념입니다.
FLAG_UPDATE_CURRENT - > 만일 이미 생성된 PendingIntent가 존재 한다면, 해당 Intent의 내용을 변경함.

FLAG_CANCEL_CURRENT - .이전에 생성한 PendingIntent를 취소하고 새롭게 하나 만든다.

FLAG_NO_CREATE -> 현재 생성된 PendingIntent를 반환합니다.

FLAG_ONE_SHOT - >이 플래그를 사용해 생성된 PendingIntent는 단 한번밖에 사용할 수 없습니다
builder.setSmallIcon(R.drawable.on).setTicker("HETT").setWhen(System.currentTimeMillis())
.setNumber(1).setContentTitle("푸쉬 제목").setContentText("푸쉬내용")
.setDefaults(Notification.DEFAULT_SOUND | Notification.DEFAULT_VIBRATE).setContentIntent(pendingNotificationIntent).setAutoCancel(true).setOngoing(true);
//해당 부분은 API 4.1버전부터 작동합니다.

//setSmallIcon - > 작은 아이콘 이미지

//setTicker - > 알람이 출력될 때 상단에 나오는 문구.

//setWhen -> 알림 출력 시간.

//setContentTitle-> 알림 제목

//setConentText->푸쉬내용

notificationManager.notify(1, builder.build()); // Notification send
}
});
}

다음과 같이 하면, 버튼을 클릭할 때마다 노티피케이션이 발생합니다.




포스팅을 하는 도중 냉장고에서 물이 새는 대 참사가 발생했다..
ㅠㅠ정신 없어서 급하게 적은 감이 없지않아 있다.
특정한 시간에 알람을 주기 위해선 AlarmManager를 이용하면 된다.
이 부분에 대해서는 다음 포스팅( 강의시간 등등..)을 이용해서 포스팅 해야겠다!

알람 매니저 사용할때는 상세히 적어야지

모두 개강 힘내요~
저는 26학점 듣습니다~!!



요즘 디자이너와 함께 하는 개발 연합동아리 NEXTERS에서 앱을 만드는데 OOM에 대해서 공부하게 되었다.

JVM이 메모리 관리를 해주는데도 불구하고 OOM이 발생한다는 것,

그래서 개발자 세션을 진행하는 nc소프트 행님에게 말했더니 직접 세션을 하면서 발표 해보라는거...


부들부들...


PPT를 만들면서, Tistory에 포스팅 해야 겠다 생각해서 포스팅 하고 있다ㅋㅋ

보통 OOM은 

프로그램이 메모리를 할당 후 해제하지 않아서 생기는것으로 C언어 동적할당(malloc)후 free를 하지 않았을 경우에 발생한다.

-> 단 한번의 예제(학습) 프로그램에서는 상관 없지만 , 서버나 계속 돌아가야 되는 프로그램에서 누적된 Memory의 Leak은 프로그램을 사망하게 한다.


GC는! 동작방식에 따라 다양한 종류가 있다.(Java , C# , 등등등) 이 GC들은 공통적으로 2가지 작업을 수행하는데,


1. 힙 내의 객체들 중에서 가비지를 찾아낸다.

2. 찾아낸 가비지를 처리해서 힙의 메모리를 회수한다.


GC는 참조카운트가 0이 되면 메모리를 회수하는 걸로도 유명하죠.



Java의 GC는 객체(이하 인스턴스)에 대해서 Reachability 개념을 사용합니다. 

인스턴스에 대해서 참조카운트가 0이 아니라면 Reachable. 참조카운트가 0이라면 unReachable.


GC는 참조 카운트가 0인 인스턴스들 즉, unReachable에 대한 객체에 대해서만 메모리를 회수합니다.


Java 1.2버전부터는 개발자들이 Reachable한 인스턴스와 unReachable한 객체에 대해서 컨트롤 할 수 있게 클래스를 제공해줬는데요.


그 클래스들은 StrongReference,SoftReference , WeakReference , PhantomReference입니다.


보통 PhantomReference는 많이 사용하지 않고 , 스트롱 레퍼런스, 윜 레퍼런스, 소프트 레퍼런스를 많이 사용하는데요(LRU 구현 등)


일단 클래스들 설명에 앞서, 코드 조금 보고 갈게요.



해당하는 코드는 저희 HATT앱의 MainActivity의 코드 일부분 입니다.


대충 어디서 메모리 릭이 발생하는지 감이 오시나요??ㅋㅋ



보통 Android를 개발하는 개발자들은 어쩔 수 없이 this를 보내주는 경우가 있죠.

이럴 경우 메모리 릭이 발생하여 나중가면 프로그램이 사망하는 경우가 생깁니다.


그 이유는, this를 보내주는 객체에선 Context객체를 필요로 하는 상황이 발생했는데 this를 보내줌으로써, Context와 함께 

해당하는 Activity에 있는 멤버필드에 있는 객체들과 여러 참조들을 같이 보내주게 됨으로써 메모리 릭이 발생하는 것입니다.


이해가 되나요?

말이 어려웠으면 댓글 달아주세요. 자세히 설명해 드리겠습니다.


WeakReference<Sample> obj = new WeakReference<Sample>(new Sample());

Sample ex = obj.get();

ex = null;

위와 같은 코드로

인스턴스를 생성 시, WeakReference에 전달인자로 넘겨주어 인스턴스를 생성합니다.

그런 후, WeakReference의 get()메소드를 이용하여 객체 잠조를 가져와 필요한 작업을 한 뒤, 객체 참조를 가지고 있는 변수에 null을 대입해 줍니다.


이해를 돕기 위하여 이미지를 통하여 설명하면,


 

위 그림은 

WeakReference<Sample> obj = new WeakReference<Sample>(new Sample());

Sample ex = obj.get();

까지 작성한 코드입니다.


SampleObject는 StrongReference와 WeakReferenceObject에 연결되어 있는데요, 이 경우는 StrongReference가 더 강력하여, GC는 StrongReference로 취급합니다.


하지만


ex=null을 하게 되면




다음과 같이 SampleObject는 Weakly reachable형태가 됩니다.


다음과 같이 되면, GC는 SampleObject를 unReachable 인스턴스로 취급하여 메모리 회수를 하게 됩니다.




이런 방식을 사용하여, this를 보내줄 때도, Activity가 이동하게 되어서, 인스턴스를 사용하지 않게 된다면,

StartActivity or StartActivityForResult 메소드를 호출하기 전에, null을 대입해 주는것입니다.



그럼 OOM을 막을 수 있겟죠.



오늘은 너무 늦어서 여기까지 작성하고 주말에 다른 API도 마저 작성하겠습니다.



출처


라이언 이모티콘 : 카카오톡 이모티콘.

+ Recent posts