안드로이드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 홍규홍규
안드로이드2018. 11. 27. 15:38

예외상황을 만들어두어 발생 시 토스트를 띄우도록 구현해놨는데 토스트가 안 뜨는 문제가 발생했다.

로그캣을 보니 아래와 같은 메시지를 발견할 수 있었다.


Suppressing toast from package [packagename] by user request.


애플리케이션 관리에서 알림을 차단해버렸기 때문에 발생한 거였다.

누구야 이거 한 사람 ㅡㅡ+

Posted by 홍규홍규
안드로이드2018. 10. 26. 13:58

간단하게 다음과 같다.

...
String drawablePath = getURLForResource(R.drawable.test_01);
...
}

private String getURLForResource(int resId) {
  return Uri.parse("android.resource://" + R.class.getPackage().getName() + "/" + resId).toString();
}

Glide 같은 라이브러리를 쓸 때 웹 이미지가 아닌 내부 이미지를 사용할 경우가 있다.

그럴 때 사용하면 유용할 듯 싶다.

Posted by 홍규홍규
안드로이드2018. 8. 16. 15:36

API 26(Oreo)부터 Vibrator 클래스의 vibrate() 메소드가 deprecated 되었다.

레퍼런스 문서를 보면 대신 VibrationEffect가 추가되었다. 


다음과 같이 사용하면 되겠다.

Vibrator vibrator = (Vibrator)getSystemService(Context.VIBRATOR_SERVICE);

if(Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1)
  vibrator.vibrate(VibrationEffect.createOneShot(500, VibrationEffect.DEFAULT_AMPLITUDE));
  // vibrator.vibrate(VibrationEffect.createWaveform(pattern, VibrationEffect.DEFAULT_AMPLITUDE));
else
  vibrator.vibrate(500);
  // vibrator.vibrate(pattern, repeat); // 0은 무한반복, -1은 반복 없음.

이상 끗~!

Posted by 홍규홍규
안드로이드2018. 8. 1. 11:42

6. 각각의 게시글에 대한 레이아웃 작성하기 - listview.xml

<?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="50dp"
    android:layout_marginBottom="10dp">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal">

        <TextView
            android:id="@+id/title"
            android:layout_width="250dp"
            android:layout_height="match_parent"
            android:gravity="center_vertical"
            android:textColor="#666"
            android:paddingLeft="20dp"
            android:text=""
            android:textSize="15dp"/>

        <TextView
            android:id="@+id/date"
            android:layout_width="90dp"
            android:layout_height="match_parent"
            android:gravity="center|right"
            android:textColor="#999"
            android:text=""
            android:textSize="13dp"/>

    </LinearLayout>

</LinearLayout>


7. Adapter 클래스 생성

public class BoardAdapter extends BaseAdapter {
  private Context context;
  private List<Board> boardList;

  public BoardAdapter(List<Board> boardList) {
    this.boardList = boardList;
  }

  @Override
  public int getCount() {
    return boardList.size();
  }

  @Override
  public Object getItem(int i) {
    return boardList.get(i);
  }

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

  @Override
  public View getView(int i, View view, ViewGroup viewGroup) {
    final int pos = i; // ListView 위치(첫 번째 = 0)
    context = viewGroup.getContext();

    if(view == null) {
      LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
      if(inflater != null)
        view = inflater.inflate(R.layout.listview, viewGroup, false); 
    }

    TextView title = view.findViewById(R.id.title),
             date = view.findViewById(R.id.date);

    Board board = boardList.get(i);

    title.setText(board.getTitle());
    date.setText(board.getCreateDate());

    view.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View view) {
        // 게시글 터치 시 내용 보여주는 다이얼로그 띄우기
        showContent(boardList.get(pos).getTitle(), boardList.get(pos).getContent()); 
      }
    });

    return view;
  }

  private void showContent(String title, String content) {
    final Dialog dialog = Util.getCustomDialog(context, R.layout.content_dialog); 
    // 8-9번 참고
    dialog.setCancelable(false);
    dialog.show();
    TextView dTitle = dialog.findViewById(R.id.dialog_title),
             dContent = dialog.findViewById(R.id.dialog_content);
    ImageView dClose = dialog.findViewById(R.id.dialog_close_img);
    Button bClose = dialog.findViewById(R.id.dialog_close);
    dTitle.setText(title);
    dContent.setText(content);
    dContent.setMovementMethod(new ScrollingMovementMethod());
    dClose.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View view) {
        dialog.dismiss();
      }
    });
    bClose.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View view) {
        dialog.dismiss();
      }
    });
  }
}


8. 내용을 보여줄 커스텀 다이얼로그 레이아웃 작성

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="300dp"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:gravity="center"
    android:background="@drawable/custom_dialog_bg"
    android:orientation="vertical" >

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

        <TextView
            android:id="@+id/dialog_title"
            android:layout_width="250dp"
            android:layout_height="wrap_content"
            android:layout_marginBottom="6dp"
            android:layout_marginLeft="5dp"
            android:layout_marginTop="6dp"
            android:padding="7dp"
            android:text=""
            android:textColor="#666"
            android:textSize="14dp" />

        <ImageView
            android:id="@+id/dialog_close_img"
            android:layout_width="22dp"
            android:layout_height="22dp"
            android:layout_gravity="center_vertical"
            android:layout_marginLeft="10dp"
            android:contentDescription="@null"
            android:src="@drawable/btn_close" />
        
    </LinearLayout>

    <View
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="#EEE" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <TextView
            android:id="@+id/dialog_content"
            android:layout_width="match_parent"
            android:layout_height="290dp"
            android:lineSpacingExtra="4dp"
            android:padding="10dp"
            android:scrollbars="vertical"
            android:scrollbarSize="3dp"
            android:text=""
            android:textColor="#555"
            android:textSize="14dp" />
        
    </LinearLayout>

    <LinearLayout
        android:id="@+id/layout_dialog_close"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:orientation="horizontal">

        <Button
            android:id="@+id/dialog_close"
            android:layout_width="match_parent"
            android:layout_height="45dp"
            android:layout_gravity="center"
            android:background="@drawable/btn_style_blue"
            android:text="@string/name_close"
            android:textColor="#FFF"
            android:textSize="16dp" />
        
    </LinearLayout>

</LinearLayout>


- 화면은 아래와 같다.



9. Util.getCustomDialog 작성

public class Util {
  // ...
  public static Dialog getCustomDialog(Context context, int layout) {
    Dialog dialog = new Dialog(context, R.style.FullHeightDialog);
    dialog.setContentView(layout);
    return dialog;
  }
  // ...
}


※ FullHeightDialog - styles.xml

  <!-- ... -->
  <style name="FullHeightDialog" parent="android:style/Theme.Dialog">
      <item name="android:windowNoTitle">true</item>
      <item name="android:windowBackground">@android:color/transparent</item>
  </style>
  <!-- ... -->


- 실제 화면


Posted by 홍규홍규
안드로이드2018. 8. 1. 11:17

- 안드로이드에서 해당 페이지로부터 데이터를 가져오는 작업.

3-1. build.gradle에 Volley 추가

implementation 'com.android.volley:volley:1.1.1'


3-2. 게시판 리스트를 출력할 Layout 작성 - activity_board.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#FAFAFA">

    <TextView
        android:id="@+id/title"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:gravity="center"
        android:background="#FFF"
        android:textSize="20dp"
        android:textColor="#666"
        android:text="게시글 목록"/>

    <ImageView
        android:id="@+id/img_close"
        android:layout_width="28dp"
        android:layout_height="28dp"
        android:src="@drawable/btn_close"
        android:layout_marginTop="11dp"
        android:layout_marginEnd="18dp"
        android:layout_alignParentEnd="true"/>

    <View
        android:id="@+id/viewline_01"
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:layout_below="@id/title"
        android:background="#EEE" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:layout_below="@id/viewline_01">

        <ListView
            android:id="@+id/listview"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:dividerHeight="1dp"
            android:divider="#EEE"
            android:background="@drawable/custom_bg_01"/>

    </LinearLayout>

    <ProgressBar
        android:id="@+id/progressBar"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:alpha="0.3"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:visibility="gone" />

</RelativeLayout>


※ 화면은 다음과 같다.




4. Volley 객체를 생성한다. (싱글톤 패턴 적용)

public class MyVolley {
  private static MyVolley instance;
  private RequestQueue requestQueue;

  public static MyVolley getInstance(Context context) {
    if(instance == null)
      instance = new MyVolley(context);
    return instance;
  }

  private MyVolley(Context context) {
    requestQueue = Volley.newRequestQueue(context);
  }

  public RequestQueue getRequestQueue() {
    return requestQueue;
  }
}


4.서버사이드 VO와 동일한 Board VO를 생성해준다. (동일하므로 생략)


5. Volley 객체를 이용하여 게시판 데이터를 가져온다.

public class BoardActivity extends Activity {
  private Context mContext
  private ListView mListView;
  private ProgressBar progressBar;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mContext = this;
    setContentView(R.layout.activity_board);    

    mListView = findViewById(R.id.listview);
    progressBar = findViewById(R.id.progressBar);
    ImageView btnClose = findViewById(R.id.img_close);
    btnClose.setOnClickListener(new View.OnClickListener() { // X 버튼
      @Override
      public void onClick(View view) {
        finish();
      }
    });

    getBoardData(); // 게시판 데이터 가져오기
  }
  
  private void setProgressBar(int visibility) {
    progressBar.setVisibility(visibility);
  }

  private void getBoardData() {
    setProgressBar(View.VISIBLE);
    final String url = getString(R.string.url_get_board); // 서버사이드 페이지 웹 주소 
    RequestQueue queue = MyVolley.getInstance(mContext).getRequestQueue();
    JsonObjectRequest jsonRequest = new JsonObjectRequest(Request.Method.GET, url, new JSONObject(),
                                                          successListener(), errorListener());
    queue.add(jsonRequest);
  }
 
  private void parsingJSONData(String data) {
    List<Board> mList = new ArrayList<>();
    try {
      JSONArray jArray = new JSONArray(data);
      for(int i = 0; i < jArray.length(); i++) {
        Board board = new Board();
        JSONObject jObject = jArray.getJSONObject(i);
        board.setNno(Integer.parseInt(jObject.getString("nno")));
        board.setTitle(jObject.getString("title"));
        board.setContent(jObject.getString("content"));
        board.setCreateDate(jObject.getString("createDatet"));
        mList.add(board );
      }
      mListView.setAdapter(new BoardAdapter(mList));

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

  private Response.Listener<JSONObject> successListener() {
    return new Response.Listener<JSONObject>() {
      @Override
      public void onResponse(JSONObject response) {
        setProgressBar(View.GONE);
        String result = null;
        try {
          result = response.getString("data");
        } catch(JSONException e) {
          e.printStackTrace();
        }
        parsingJSONData(result);
      }
    };
  }

  private Response.ErrorListener errorListener() {
    return new Response.ErrorListener() {
      @Override
      public void onErrorResponse(VolleyError error) {
        // Util.showToast(mContext, getString(R.string.msg_network_error_01));
        // 에러 메시지 작성
      }
    };
  }
}


Posted by 홍규홍규
안드로이드2018. 8. 1. 10:57

※ 서버사이드 언어는 JSP로 작성


1. 게시글 각각의 데이터를 담을 VO를 작성한다. - Board.java

  게시판 구조는 게시글번호(nno), 제목(title), 내용(content), 작성일(createDate)로 구성.

public class Board {
  protected int nno;
  protected String title;
  protected String content;
  protected String createDate;
  
  public int getNno() {
    return nno;
  }
  public void setNno(int nno) {
    this.nno = nno;
  }
  public String getTitle() {
    return title;
  }
  public void setTitle(String title) {
    this.title = title;
  }
  public String getContent() {
    return content;
  }
  public void setContent(String content) {
    this.content = content;
  }
  public String getCreateDate() {
    return createDate;
  }
  public void setCreateDate(String createDate) {
    this.cre_dt = createDate;
  }
}


2. 서버로부터 게시글 리스트를 가져오는 쿼리를 작성한다. - get_list.jsp

  그리고 가져온 데이터를 json 형식으로 출력하는 코드를 작성한다.

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8" import="org.json.simple.*" %>
<%@ page import="java.sql.*" import="java.util.*" 
    import="VO작성한패키지.Board" %>
<%
Connection con = null;
Statement stmt = null;
List list = null;
try {
  Class.forName("com.mysql.cj.jdbc.Driver");
  con = DriverManager.getConnection("jdbc:서버주소?serverTimezone=UTC", "아이디", "비밀번호");
  if(con == null)
    throw new Exception("데이터베이스에 연결할 수 없습니다.");
 
  stmt = con.createStatement();
  String query = String.format("SELECT * FROM BOARD ORDER BY createDate DESC"); 

  list = new ArrayList(); // 리스트 생성
  Board board = null;
  ResultSet rs = stmt.executeQuery(query);

  JSONObject jObject = new JSONObject(); // 객체를 담을 JSON 오브젝트 생성
  JSONArray jArray = new JSONArray(); // 객체 리스트를 담을 JSON 배열 생성

  int jCount = 0; // JSON 형식 배열 index
  while(rs.next()) {
    JSONObject innerObj = new JSONObject();
    innerObj.put("nno", rs.getInt(1)); // 게시글 번호
    innerObj.put("title", rs.getString(2)); // 게시글 제목
    innerObj.put("content", rs.getString(3)); // 게시글 내용
    innerObj.put("createDate", rs.getString(4)); // 게시글 작성일
    jArray.add(jCount, innerObj);
    jCount++;
  }
  jObject.put("data", jArray);
  out.print(jObject);

  rs.close();
} catch(Exception e) {
  e.printStackTrace();
} finally {
  try {
    stmt.close();
    con.close();
  } catch(Exception ignored) {}
}
%>
Posted by 홍규홍규
안드로이드2018. 7. 24. 14:28

- 프래그먼트의 추가 또는 변경이 필요하면 그 때마다 beginTransaction() 메소드를 호출해야 한다.

private FragmentManager fm = getSupportFragmentManager();

위와 같이 FragmentManager 객체를 호출하고,

@Override
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setcontentView(R.layout.activity_main);
  
  FragmentTransaction ft = fm.beginTransaction();
  ft.add(R.id.fragment_01, new Fragment01());
  ft.commit();
}

private void setFragment(int fragmentName) {
  // ...
  ft.replace(R.id.fragment_02, new Fragment02());
  ft.commit();
  // ...
}

- 위와 같이 하나의 트랜잭션에서 또 프래그먼트 전환 처리를 하면 다음과 같은 에러가 난다.

commit already called

그러므로 다른 프래그먼트를 호출해야 한다면 다음과 같이 트랜잭션 객체를 새롭게 생성해야 한다.

private void setFragment(int fragmentName) {
  // ...
  FragmentTransaction ft = fm.beginTransaction();
  ft.replace(R.id.fragment_02, new Fragment02());
  ft.commit();
  // ...
}


- 즉, 프래그먼트의 commit() 메소드는 호출이 필요할 때 마다 FragmentTransaction을 생성하면 된다.


이상 끗~!

Posted by 홍규홍규
안드로이드2018. 7. 11. 16:41
PackageManager pm = getPackageManager();
// PackageManager pm = getActivity().getPackageManager(); // 비 액티비티에서 구현할 경우

String packageName = "실행하려는 앱의 패키지명";
try {
  pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
  startActivity(pm.getLaunchIntentForPackage(packageName));

} catch(PackageManager.NameNotFoundException e) {
  // 해당 패키지 앱이 설치되어있지 않을 경우 NameNotFoundException 예외가 발생하면서 구글 플레이로 이동.
  startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + packageName)));
}
이렇게 간단하게 구현할 수 있다. 
까먹지 말자 좀. 

이상 끗~!


Posted by 홍규홍규