Java & Spring2019. 1. 24. 11:15
<context : annotation-config />
<context : component-scan />

두 가지는 유사하면서도 차이점이 존재한다.

둘 다 application-context에 정의해서 사용할 수 있는데 헷갈리던 부분이 있어 명확하게 다시 정리해보고자 한다.


<context : annotation-config />

이미 등록된 bean에 대해서만 Annotation을 활성화한다.

어딘가에 bean을 등록해놓으면 @Autowired와 @Qualifier Annotation을 해석해서 가져다 쓰겠다는 의미이다.

@Autowired와 @Qualifier 두 가지만 해결한다.

따라서 위 태그를 사용하더라도 xml에 bean을 반드시 선언해야 한다.

<context : component-scan/>

bean의 등록 여부와 관계없다. 스프링이 알아서 bean 스캔을 통해 Annotation을 해석하고 활성화한다.

@Autowired와 @Qualifier 뿐만 아니라 @Service, @Component, @Controller, @Repository 등 모든 클래스를 스캔하고

bean을 작성한다.

따라서 이 태그를 사용하면 위 태그는 사용할 필요가 없다.

다만 이 경우 base-package를 통해 스프링이 스캔할 패키지 위치를 지정해두는 것이 일반적이다.

<context : component-scan base-package="com.sample"/>


마지막으로 둘의 중요한 공통점은 의존 주입(DI)이다. 스프링에서 흔히 말하는 의존주입은 위 과정을 통해 일어난다.

그리고 이 의존 주입(또는 의존성 주입)은 스프링 IoC의 중요한 개념 중 하나이다.


- 이상 끗



Posted by 홍규홍규
안드로이드2018. 12. 18. 15:38

예전 구독 기능을 구현할 땐 일일이 필요한 것들을 붙여서 했는데 

현재는 인앱결제를 라이브러리로 만든 친절하고도 대단한 분이 계신다. 


이번에는 구독이 아닌 광고 제거를 위한 1회성 결제가 필요한데 라이브러리를 사용해서 구현해보고자 한다.

당연한 얘기지만, 실제 기능을 만들기 위해 내가 해야 할 일이 매우, 매우 많이 줄어든다.


우선 해당 라이브러리가 소개된 github는 아래에 링크를 남겨두자.

https://github.com/anjlab/android-inapp-billing-v3


그럼 쉽게 쉽게 간단하게 간단하게 편하게 편하게 구현해볼까?


1. build.gradle에 다음을 추가한다.

implementation 'com.anjlab.android.iab.v3:library:1.0.44'

2. AndroidManifest.xml에 다음 퍼미션을 추가한다.

<uses-permission android:name="com.android.vending.BILLING" />

3. 해당 라이브러리 기능을 구현할 클래스를 하나 작성한다.

  - 적어도 두 군데의 액티비티에서 사용할 것이므로 별도의 클래스로 만듦

public class BillingModule implements BillingProcessor.IBillingHandler {
  private Context context;
  private BillingProcessor mBillingProcessor;

  public BillingModule(Context context) {
    this.context = context;
  }

  public void initBillingProcessor() {
    mBillingProcessor = new BillingProcessor(context, "rsa_key", this);
    // 아래와 차이는 기트허브 페이지에서 확인할 수 있다. 상황에 맞게 사용하면 됨.
    // mBilling Processor = BillingProcessor.newBillingProcessor(context, "rsa_key", this); 
    // rsa_key는 개발자 콘솔에서 제공하는 id
  }

  public void purchaseProduct() { // 아이템 구매 요청
    if(mBillingProcessor.isPurchased(itemId)) {
      // 이미 광고 제거를 위한 결제를 완료했기 때문에 해당 처리를 해주면 된다.
      return;
    }
    mBillingProcessor.purchase((Activity)context, itemId);
  }

  public void releaseBillingProcessor() { 
    if(mBillingProcessor != null)
      mBillingProcessor.release();
  }
  
  public BillingProcessor getBillingProcessor() { 
    return mBillingProcessor;
  }

  @Override
  public void onProductPurchased(@NonNull String id, @Nullable TransactionDetails transactionDetails) {
    // 아이템 구매 성공 시 호출.
    // 따라서 보상을 지급하든(광고 제거) 혹은 해당 아이템을 소비하든 해당 기능을 작성 
  }

  @Override
  public void onPurchaseHistoryRestored() {
    // 구매 내역 복원 및 구매한 모든 PRODUCT ID 목록이 Google Play에서 로드 될 때 호출.
  }

  @Override
  public void onBillingError(int errCode, @Nullable Throwable throwable) {
    // 구매 시 에러가 발생했을 때 처리
    if(errCode != com.anjlab.android.iab.v3.Constants.BILLING_RESPONSE_RESULT_USER_CANCELED) { 
      // 사용자가 취소한 게 아니라면 에러 발생에 대해 사용자에게 고지하는 등의 처리
    }
  }

  @Override
  public void onBillingInitialized() {
    // 개발자 콘솔에서 등록한 아이템 아이디
    SkuDetails mProduct = mBillingProcessor.getPurchaseListingDetails("remove_ad"); 
    if(mProduct == null)
      return;
    itemId = mProduct.productId;
    mBillingProcessor.loadOwnedPurchasesFromGoogle(); // 소유하고 있는 구매 아이템 목록을 가져온다.
    if(mBillingProcessor.isPurchased(mProduct.productId) {
      // 이미 광고 제거를 구매했다면 다시 구매할 필요가 없으므로 
      // 해당 부분 처리. 또는 이미 구매 시 광고 제거 구매를 애초에 막는다.
    }
  }
}

4. 필요한 클래스에서 해당 기능을 수행.

public class SplashActivity extends Activity {
  BillingModule billingModule; 

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    ...
    billingModule = new BillingModule(this);
    billing.initBillingProcessor(); 
    // 광고 제거 구매 내역이 있는지 확인 후, 있다면 배너 광고나 전면 광고가 보이지 않도록 처리한다.
    ...
  }

  @Override
  protected void onDestroy() {
    super.onDestroy();
   billingModule.releaseBillingProcessor();
  }
}

광고 제거를 구매할 수 있는 액티비티에서는 다음을 수행해준다.

public class PurchaseActivity extends Activity {
  private BillingProcessor mBillingProcessor;
  private BillingModule billingModule;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    ...
    billingModule = new BillingModule(this);
    billingModule.initBillingProcessor();
    mBillingProcessor = billingModule.getBillingProcessor();
    ...
  }

  ...

  private void purchaseProduct() { // 아이템 구매 요청
    billingModule.purchaseProduct();
  }

  @Override
  protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if(mBillingProcessor.handleActivityResult(requestCode, resultCode, data)) {
      if(resultCode == RESULT_OK) {
        아이템 구매가 성공했을 경우 처리
      }
    }
  }
}

이 라이브러리를 만들어주신 분에게 진심으로 감사드리며..까먹지 말자 ^.,^

Posted by 홍규홍규
안드로이드2018. 12. 10. 16:09

파이어 베이스 가입 및 앱 추가 과정은 구글 검색하면 수도 없이 많아서 생략..귀찮은 건 안 비밀..

안드로이드 스튜디오에서 직접 하거나 아래 url에서 가입 및 세팅.

https://console.firebase.google.com/?hl=ko


기존 파이어베이스를 이용한 방식은 FirebaseInstanceIdService와 FirebaseMessagingService를 상속한

두 개의 클래스를 가지고 구현하였는데 현재는 FirebaseInstanceIdService가 deprecated되었다.


따라서 FirebaseMessagingService만을 활용하여 구현해보고자 한다.

프로젝트 단위 build.gradle에 다음을 추가한다.

buildscript {
  dependencies {
    ...
    classpath 'com.google.gms:google-services:4.0.1'
  }
}

앱 단위 build.gradle에 다음을 추가한다. 나의 경우 하단에 com.google.gms.google-services도 추가해야

정상적으로 구현이 되었다.

dependencies {
  ...
  implementation 'com.google.firebase:firebase-messaging:17.3.4'
  ...
}
apply plugin: 'com.google.gms.google-services'

주의할 것은 appli plugin: 'com.google.gms.google-services' 를 추가할 경우 play-services-ads를 사용하고 있다면

17.1.2 버전을 사용할 경우 오류가 발생한다. 따라서 버전을 17.1.1로 낮춰야 한다.

implementation 'com.google.android.gms:play-services-ads:17.1.1'

AndroidManifest.xml에도 추가해야 할 것이 있다.

<service
    android:name=".MyFirebaseMessagingService">
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT"/>
    </intent-filter>
</service>

이제 준비가 끝난 거 같다. 맞나? 


다음으로 FirebaseMessagingService를 상속받는 클래스를 만든다.

public class MyFirebaseMessagingService extends FirebaseMessagingService {
  @Override
  public void onMessageReceived(RemoteMessage remoteMessage) {
    if(remoteMessage.getData() == null)
      return;
    sendNotification(remoteMessage.getData().get("title"), remoteMessage.getData().get("content"));
  }
 
  private void sendNotification(String title, String content) {
    if(title == null)
      title = "기본 제목";

    Intent intent = new Intent(this, MainActivity.class);
    intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
    PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_ONE_SHOT);
    Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtonManager.TYPE_NOTIFICATION);

    // 오레오(8.0) 이상일 경우 채널을 반드시 생성해야 한다.
    final String CHANNEL_ID = "채널ID";
    NotificationManager mManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
    if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
      final String CHANNEL_NAME = "채널 이름";
      final String CHANNEL_DESCRIPTION = "채널 Description";
      final int importance = NotificationManager.IMPORTANCE_HIGH;

      // add in API level 26
      NotificationChannel mChannel = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, importance);
      mChannel.setDescription(CHANNEL_DESCRIPTION);
      mChannel.enableLights(true);
      mChannel.enableVibration(true);
      mChannel.setVibrationPattern(new long[]{100, 200, 100, 200});
      mChannel.setSound(defaultSoundUri, null);
      mChannel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
      mManager.createNotificationChannel(mChannel);
    }
  
    NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID);
    builder.setAutoCancel(true);
    builder.setDefaults(Notification.DEFAULT_ALL);
    builder.setWhen(System.currentTimeMillis());
    builder.setSmallIcon(R.mipmap.ic_launcher);
    builder.setContentText(content);
    if(Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
      // 아래 설정은 오레오부터 deprecated 되면서 NotificationChannel에서 동일 기능을 하는 메소드를 사용.
      builder.setContentTitle(title);
      builder.setSound(defaultSoundUri);
      builder.setVibrate(new long[]{500, 500});
    }

    mManager.notify(0, builder.build());
  }

  @Override
  public void onNewToken(string s) {
    super.onNewToken(s);
    /* 
     * 기존의 FirebaseInstanceIdService에서 수행하던 토큰 생성, 갱신 등의 역할은 이제부터 
     * FirebaseMessaging에 새롭게 추가된 위 메소드를 사용하면 된다. 
     */
  }
}


이정도면 대충 정리가 되었으려나. 급하게 작성하느라 제대로 했는지 모르겠다.

까먹지 말자 좀 ... !



Posted by 홍규홍규