728x90
MySQL 로 된 데이터를 SQLite 로 변환하기 위해서 assets 를 이용할 까 해봤는데, 데이터 크기가 5MB가 넘어서 어플의 용량이 커지는 문제가 발생하고 변환하는데 시간도 걸리고 해서 포기했다.

결국에는 양력/음력 변환을 제대로 해주는 메소드를 잘 만들거나, 기존에 만들어진 다른 코드를 변환해서 이용하는 방법이 최선인 거 같다.
제대로 된 Java 라이브러리가 어디에 있는데 못찾는 것인지, 공개를 안해서 못 구하는 것인지는 모르겠다.


자바에서 제공하는 라이브러리를 이용하여 변환하는 방법을 시도했다.
인터넷 검색하면 가장 많이 나오는 방법이다.


1. https://github.com/unicode-org/icu/releases/tag/release-4-8-2 에서 jar 파일을 다운로드 한다.

http://site.icu-project.org/download/48#ICU4J-Download 페이지의 중간 정도에 Version: 4.8.2
Release Date: 2019-04-11 라고 나온다.


2. 다운로드 받은 파일 icu4j-4_8_2.jar 를 libs 폴더로 복사하거나 옮긴다.


3. dependencies 에 추가한다.

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    implementation 'androidx.recyclerview:recyclerview:1.0.0'
    implementation 'androidx.cardview:cardview:1.0.0'
    implementation 'gun0912.ted:tedpermission:2.0.0'
    implementation 'com.github.bumptech.glide:glide:3.8.0' // 이미지 라이브러리
    implementation files('libs/icu4j-4_8_2.jar')
}

* 자동 추가 방법은 Android Studio 왼쪽 메뉴에서 Project 로 선택하고 해당 파일에서 마우스 우클릭을 하면 Add As Library... 를 선택하여 추가하면 된다. 직접 dependencies 에 적어주고 Sync Now를 해도 된다.


4. 음력/양력 변환 코드

import com.ibm.icu.util.ChineseCalendar;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;

public class LunarCalendar {
    /**
     * 음력날짜를 양력날짜로 변환
     * @param 음력날짜 (yyyyMMdd)
     * @return 양력날짜 (yyyyMMdd)
     */
    public static String Lunar2Solar(String yyyymmdd) {
        ChineseCalendar cc = new ChineseCalendar();
        Calendar cal = Calendar.getInstance();

        if (yyyymmdd == null)
            return "";

        String date = yyyymmdd.trim();
        if (date.length() != 8) {
            if (date.length() == 4)
                date = date + "0101";
            else if (date.length() == 6)
                date = date + "01";
            else if (date.length() > 8)
                date = date.substring(0, 8);
            else
                return "";
        }

        cc.set(ChineseCalendar.EXTENDED_YEAR, Integer.parseInt(date.substring(0, 4)) + 2637);
        cc.set(ChineseCalendar.MONTH, Integer.parseInt(date.substring(4, 6)) - 1);
        cc.set(ChineseCalendar.DAY_OF_MONTH, Integer.parseInt(date.substring(6)));

        cal.setTimeInMillis(cc.getTimeInMillis());

        int y = cal.get(Calendar.YEAR);
        int m = cal.get(Calendar.MONTH) + 1;
        int d = cal.get(Calendar.DAY_OF_MONTH);

        StringBuffer ret = new StringBuffer();
        ret.append(String.format("%04d", y));
        ret.append(String.format("%02d", m));
        ret.append(String.format("%02d", d));

        return ret.toString();
    }

    /**
     * 양력날짜를 음력날짜로 변환
     * @param 양력날짜 (yyyyMMdd)
     * @return 음력날짜 (yyyyMMdd)
     */
    public static String Solar2Lunar(String yyyymmdd) {
        ChineseCalendar cc = new ChineseCalendar();
        Calendar cal = Calendar.getInstance();

        if (yyyymmdd == null)
            return "";

        String date = yyyymmdd.trim() ;
        if( date.length() != 8 ) {
            if( date.length() == 4 )
                date = date + "0101" ;
            else if( date.length() == 6 )
                date = date + "01" ;
            else if( date.length() > 8 )
                date = date.substring(0,8) ;
            else
                return "" ;
        }

        cal.set( Calendar.YEAR, Integer.parseInt(date.substring(0,4)) ) ;
        cal.set( Calendar.MONTH, Integer.parseInt(date.substring(4,6))-1 ) ;
        cal.set( Calendar.DAY_OF_MONTH, Integer.parseInt(date.substring(6)) ) ;

        cc.setTimeInMillis( cal.getTimeInMillis() ) ;

        // ChinessCalendar.YEAR 는 1~60 까지의 값만 가지고 ,
        // ChinessCalendar.EXTENDED_YEAR 는 Calendar.YEAR 값과 2637 만큼의 차이를 가진다.
        int y = cc.get(ChineseCalendar.EXTENDED_YEAR)-2637 ;
        int m = cc.get(ChineseCalendar.MONTH)+1 ;
        int d = cc.get(ChineseCalendar.DAY_OF_MONTH) ;

        StringBuffer ret = new StringBuffer() ;
        if( y < 1000 )          ret.append( "0" ) ;
        else if( y < 100 )      ret.append( "00" ) ;
        else if( y < 10 )       ret.append( "000" ) ;
        ret.append( y ) ;

        if( m < 10 ) ret.append( "0" ) ;
        ret.append( m ) ;

        if( d < 10 ) ret.append( "0" ) ;
        ret.append( d ) ;

        return ret.toString() ;
    }
}


5. 샘플 코드

import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import com.tistory.link2me.util.LunarCalendar;
import java.util.Calendar;

public class CalendarActivity extends AppCompatActivity {

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

        System.out.println( LunarCalendar.Solar2Lunar("20010715") ) ; // 양력을 음력으로 바꾸기
        System.out.println( LunarCalendar.Lunar2Solar("20010527") ) ; // 음력을 양력으로 바꾸기
    }


이 라이브러리를 이용하는 방법은 간단하기는 한데 정교한 맛이 없는 거 같다.

윤달인지 여부, 간지 등을 표현할 수가 없는 점이 좀 아쉽다.


그래서 구글 검색을 하여 Java Lunar Calendar (Lunar Calendar) Tools 를 찾아봤더니

https://www.cnblogs.com/harry335/p/4861598.html 가 검색된다.

이걸 테스트해봤더니 음력일자 결과가 제대로 반환되지 않고 하루가 틀리게 나온다.

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;

public class Ca03 {
    private int year;
    private int month;
    private int day;
    private boolean leap;
    final static String chineseNumber[] = { "1", "2", "3", "4", "5", "6", "7","8", "9", "10", "11", "12" };
    static SimpleDateFormat chineseDateFormat = new SimpleDateFormat("yyyy年MM月dd日");
    final static long[] lunarInfo = new long[] { 0x04bd8, 0x04ae0, 0x0a570,
            0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2,
            0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0,
            0x0ada2, 0x095b0, 0x14977, 0x04970, 0x0a4b0, 0x0b4b5, 0x06a50,
            0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970, 0x06566,
            0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0,
            0x1c8d7, 0x0c950, 0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4,
            0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557, 0x06ca0, 0x0b550,
            0x15355, 0x04da0, 0x0a5d0, 0x14573, 0x052d0, 0x0a9a8, 0x0e950,
            0x06aa0, 0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260,
            0x0f263, 0x0d950, 0x05b57, 0x056a0, 0x096d0, 0x04dd5, 0x04ad0,
            0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b5a0, 0x195a6,
            0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40,
            0x0af46, 0x0ab60, 0x09570, 0x04af5, 0x04970, 0x064b0, 0x074a3,
            0x0ea50, 0x06b58, 0x055c0, 0x0ab60, 0x096d5, 0x092e0, 0x0c960,
            0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0,
            0x092d0, 0x0cab5, 0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9,
            0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930, 0x07954, 0x06aa0,
            0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65,
            0x0d530, 0x05aa0, 0x076a3, 0x096d0, 0x04bd7, 0x04ad0, 0x0a4d0,
            0x1d0b6, 0x0d250, 0x0d520, 0x0dd45, 0x0b5a0, 0x056d0, 0x055b2,
            0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0 };

    // ====== 传回农历 y年的总天数
    final private static int yearDays(int y) {
        int i, sum = 348;
        for (i = 0x8000; i > 0x8; i >>= 1) {
            if ((lunarInfo[y - 1900] & i) != 0)
                sum += 1;
        }
        return (sum + leapDays(y));
    }

    // ====== 传回农历 y年闰月的天数
    final private static int leapDays(int y) {
        if (leapMonth(y) != 0) {
            if ((lunarInfo[y - 1900] & 0x10000) != 0)
                return 30;
            else
                return 29;
        } else
            return 0;
    }

    // ====== 传回农历 y年闰哪个月 1-12 , 没闰传回 0
    final private static int leapMonth(int y) {
        return (int) (lunarInfo[y - 1900] & 0xf);
    }

    // ====== 传回农历 y年m月的总天数
    final private static int monthDays(int y, int m) {
        if ((lunarInfo[y - 1900] & (0x10000 >> m)) == 0)
            return 29;
        else
            return 30;
    }

    // ====== 传回农历 y年的生肖
    final public String animalsYear() {
        final String[] Animals = new String[] { "쥐", "소", "호랑이", "토끼", "용", "뱀", "말", "양", "원숭이", "닭", "개", "돼지"};
        return Animals[(year - 4) % 12];
    }

    // ====== 传入 月日的offset 传回干支, 0=甲子
    final private static String cyclicalm(int num) {
        final String[] Gan = new String[] { "甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸" };
        final String[] Zhi = new String[] { "子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥" };
        return (Gan[num % 10] + Zhi[num % 12]);
    }

    // ====== 传入 offset 传回干支, 0=甲子
    final public String cyclical() {
        int num = year - 1900 + 36;
        return (cyclicalm(num));
    }

    public Ca03(Calendar cal) {
        int yearCyl, monCyl, dayCyl;
        int leapMonth = 0;
        Date baseDate = null;
        try {
            baseDate = chineseDateFormat.parse("1900年1月31日");
        } catch (ParseException e) {
            e.printStackTrace(); // To change body of catch statement use
                                    // Options | File Templates.
        }

        // 求出和1900年1月31日相差的天数
        int offset = (int) ((cal.getTime().getTime() - baseDate.getTime()) / 86400000L);
        dayCyl = offset + 40;
        monCyl = 14;

        // 用offset减去每农历年的天数
        // 计算当天是农历第几天
        // i最终结果是农历的年份
        // offset是当年的第几天
        int iYear, daysOfYear = 0;
        for (iYear = 1900; iYear < 2050 && offset > 0; iYear++) {
            daysOfYear = yearDays(iYear);
            offset -= daysOfYear;
            monCyl += 12;
        }
        if (offset < 0) {
            offset += daysOfYear;
            iYear--;
            monCyl -= 12;
        }
        // 农历年份
        year = iYear;

        yearCyl = iYear - 1864;
        leapMonth = leapMonth(iYear); // 闰哪个月,1-12
        leap = false;

        // 用当年的天数offset,逐个减去每月(农历)的天数,求出当天是本月的第几天
        int iMonth, daysOfMonth = 0;
        for (iMonth = 1; iMonth < 13 && offset > 0; iMonth++) {
            // 闰月
            if (leapMonth > 0 && iMonth == (leapMonth + 1) && !leap) {
                --iMonth;
                leap = true;
                daysOfMonth = leapDays(year);
            } else
                daysOfMonth = monthDays(year, iMonth);

            offset -= daysOfMonth;
            // 解除闰月
            if (leap && iMonth == (leapMonth + 1))
                leap = false;
            if (!leap)
                monCyl++;
        }
        // offset为0时,并且刚才计算的月份是闰月,要校正
        if (offset == 0 && leapMonth > 0 && iMonth == leapMonth + 1) {
            if (leap) {
                leap = false;
            } else {
                leap = true;
                --iMonth;
                --monCyl;
            }
        }
        // offset小于0时,也要校正
        if (offset < 0) {
            offset += daysOfMonth;
            --iMonth;
            --monCyl;
        }
        month = iMonth;
        day = offset + 1;
    }

    public static String getChinaDayString(int day) {
        String chineseTen[] = { "初", "十", "廿", "卅" };
        int n = day % 10 == 0 ? 9 : day % 10 - 1;
        if (day > 30)
            return "";
        if (day == 10)
            return "初十";
        else
            return chineseTen[day / 10] + chineseNumber[n];
    }

    public String toString() {
        return year + "年 " + (leap ? "闰" : "") + chineseNumber[month - 1] + "月 " + day +"日";
    }
   
    public static void main(String[] args) {
        Calendar cal = Calendar.getInstance();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日");
        cal.setTimeZone(TimeZone.getDefault());
        System.out.println("양력(오늘):" + sdf.format(cal.getTime()));
       
        int year = 2001;
        int month = 1;
        int date = 25;
        cal.set(year, month-1, date);

        Ca03 lunar = new Ca03(cal);
        System.out.print("음력(오늘):");
        System.out.print(lunar.year + "年 ");
        System.out.print(lunar.month + "月 ");
        System.out.print(lunar.day+"日");

        System.out.println("\n*************\n" + lunar.animalsYear());
        System.out.println(lunar.cyclical());
        System.out.println(lunar);
    }

}



PHP 로 된 만세력 변환 코드가 올려져 있는 거 같다.

https://github.com/OOPS-ORG-PHP/Lunar/releases

이 코드를 변환해 보지는 않았다.


다음에는 PHP 에서 음력/양력 변환하는 코드를 자바로 변환해본 것을 올릴 것이다.

728x90
블로그 이미지

Link2Me

,
728x90

Android Memo 어플 소스를 RecyclerView 를 이용하여 수정했다.

SQLite 함수 일부분을 수정한 것은 같이 포함시켰다.


DBHelper.java 수정

public class DbHelper extends SQLiteOpenHelper {
    public static final String DATABASE_NAME = "data2";
    public static final String TABLE_NAME = "comments_table";
    public static final String C_ID = "_id";
    public static final String TITLE = "title";
    public static final String TYPE = "type";
    public static final String DETAIL = "description";
    public static final String TIME = "time";
    public static final String DATE = "date";
    public static final int DATABASE_VERSION = 2;

    private final String createDB = "create table if not exists " + TABLE_NAME + " ( "
            + C_ID + " integer primary key autoincrement, "
            + TITLE + " text, "
            + DETAIL + " text, "
            + TYPE + " text, "
            + TIME + " text, "
            + DATE + " text)";

    public DbHelper(Context context){
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(createDB);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        db.execSQL("drop table " + TABLE_NAME);
    }

    public Cursor LoadSQLiteDBCursor() {
        SQLiteDatabase db = this.getReadableDatabase();
        db.beginTransaction();
        // Select All Query
        String selectQuery = "SELECT _id,title,type,description,time,date FROM " + TABLE_NAME;
        Cursor cursor = null;
        try {
            cursor = db.rawQuery(selectQuery, null);
            db.setTransactionSuccessful();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            db.endTransaction();
        }
        return cursor;
    }

}


MainActivity.java 수정

import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;

import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {
    Context context;

    SQLiteDatabase db;
    DbHelper mDbHelper;

    private RecyclerView listView;
    private ArrayList<Memo_Item> memoItemList= new ArrayList<>(); // SQLite에서 가져온 원본 데이터 리스트
    RecyclerView.Adapter listViewAdapter; // ListViewAdapter 대신 RecyclerView.Adapter
    RecyclerView.LayoutManager layoutManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        context = this.getBaseContext();

        listView = (RecyclerView)findViewById(R.id.commentslist);
        listView.setHasFixedSize(true);

        memoItemList.clear(); // 데이터 초기화
        mDbHelper = new DbHelper(this);
        db= mDbHelper.getReadableDatabase();
        db.beginTransaction();

        Cursor cursor = mDbHelper.LoadSQLiteDBCursor();
        try {
            cursor.moveToFirst();
            System.out.println("SQLiteDB 개수 = " + cursor.getCount());
            while (!cursor.isAfterLast()) {
                addGroupItem(cursor.getLong(0),cursor.getString(1),cursor.getString(2),
                        cursor.getString(3),cursor.getString(4),cursor.getString(5));
                cursor.moveToNext();
            }
            db.setTransactionSuccessful();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (cursor != null) {
                cursor.close();
                db.endTransaction();
            }
        }

        // Set Layout Manager
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
        listView.setLayoutManager(layoutManager);

        listViewAdapter = new ListViewAdapter(memoItemList, this); // Adapter 생성
        listView.setAdapter(listViewAdapter); // 어댑터를 리스트뷰에 세팅
    }

    public void addGroupItem(Long uid, String title, String memo_type, String detail, String time, String date){
        Memo_Item item = new Memo_Item();
        item.setUid(uid);
        item.setTitle(title);
        item.setMemo_type(memo_type);
        item.setDetail(detail);
        item.setTime(time);
        item.setDate(date);
        memoItemList.add(item);
    }

    @Override
    public void onBackPressed() {
        moveTaskToBack(true);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {

        switch(item.getItemId()) {
            case R.id.action_new:
                Intent openCreateNote = new Intent(MainActivity.this, CreateNote.class);
                startActivity(openCreateNote);
                return true;

            default:
                return super.onOptionsItemSelected(item);
        }
    }

}


Memo_Item.java 클래스 만들기

public class Memo_Item {
    private Long uid;
    private String title; // 제목
    private String memo_type; // 메모 타입
    private String detail; // 내용
    private String time; // 시간
    private String date; // 작성일자

    public Memo_Item() {

    }

    public Memo_Item(Long uid, String title, String memo_type, String detail, String time, String date) {
        this.uid = uid;
        this.title = title;
        this.memo_type = memo_type;
        this.detail = detail;
        this.time = time;
        this.date = date;
    }

    public Long getUid() {
        return uid;
    }

    public void setUid(Long uid) {
        this.uid = uid;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getMemo_type() {
        return memo_type;
    }

    public void setMemo_type(String memo_type) {
        this.memo_type = memo_type;
    }

    public String getDetail() {
        return detail;
    }

    public void setDetail(String detail) {
        this.detail = detail;
    }

    public String getTime() {
        return time;
    }

    public void setTime(String time) {
        this.time = time;
    }

    public String getDate() {
        return date;
    }

    public void setDate(String date) {
        this.date = date;
    }
}


ListViewAdapter 어댑터 클래스 만들기

import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.recyclerview.widget.RecyclerView;

import com.bumptech.glide.Glide;

import java.util.ArrayList;

public class ListViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    Context mContext;
    private ArrayList<Memo_Item> IvList;
    int Position;

    public ListViewAdapter(ArrayList<Memo_Item> items, Context context) {
        IvList = items;
        mContext = context;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_entry, parent, false);
        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        // ListView의 getView 부분을 담당하는 메소드
        ((ViewHolder) holder).onBind(IvList.get(position));
    }

    @Override
    public int getItemCount() {
        return IvList.size(); // 데이터 개수 리턴
    }

    public class ViewHolder extends RecyclerView.ViewHolder {
        public TextView mTitle;
        public TextView memoType;
        public TextView mDetail;
        public ImageView mImage;
        public TextView mTime;
        public TextView mDate;

        public ViewHolder(View itemView) {
            super(itemView);
            // 화면에 표시될 View 로부터 위젯에 대한 참조 획득
            mTitle = itemView.findViewById(R.id.title);
            memoType = itemView.findViewById(R.id.type);
            mDetail = itemView.findViewById(R.id.Detail);
            mImage = itemView.findViewById(R.id.alarmImage);
            mTime = itemView.findViewById(R.id.time);
            mDate = itemView.findViewById(R.id.date);

            itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Position = getAdapterPosition();
                    Intent intent = new Intent(mContext, View_Note.class);
                    intent.putExtra(mContext.getString(R.string.row_id),IvList.get(Position).getUid());
                    Log.e("rowID", String.valueOf(IvList.get(Position).getUid()));
                    mContext.startActivity(intent);
                }
            });
        }

        public void onBind(Memo_Item item) {
            mTitle.setText(item.getTitle());
            memoType.setText(item.getMemo_type());
            mDetail.setText(item.getDetail());
            int resourceId = R.drawable.ic_action_alarms;
            Glide.with(mContext).load(resourceId).into(mImage);
            mTime.setText(item.getTime());
            mDate.setText(item.getDate());
        }
    }
}


activity_main.xml 수정

<RelativeLayout 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">

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        >

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/commentslist"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>
    </LinearLayout>

</RelativeLayout>


build.gradle 수정

apply plugin: 'com.android.application'

android {
    compileSdkVersion 28


    defaultConfig {
        applicationId "com.tistory.link2me.memo"
        minSdkVersion 19
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }

}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])

    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    implementation 'androidx.recyclerview:recyclerview:1.0.0'
    implementation 'androidx.cardview:cardview:1.0.0'
    implementation 'gun0912.ted:tedpermission:2.0.0'
    implementation 'com.github.bumptech.glide:glide:3.8.0' // 이미지 라이브러리
}



나머지는 수정한 사항이 없다.

기존 소스코드를 이렇게 수정하면 된다.


memo_src.zip



728x90
블로그 이미지

Link2Me

,
728x90

서버가 죽을 수도 있다는 가정하에 코드를 구현했어야 하는데 이를 고려하지 못하니까 어플이 진입도 하지 못하고 죽는 현상이 발생했다.

초보 개발자의 로직 에러다.


이를 보완한 코드 예제다.

서버의 httpd 데몬을 죽여서 전달되는 메시지를 보고, 아래와 같이 failed 라는 글자가 포함되어 있으면, 버전 체크하지 말고 그냥 다음 단계를 실행하도록 했다.

version = Value.getVersionName(mContext);
version = version.replaceAll("[^0-9]", ""); // 버전에서 숫자만 추출

Log.d("WEB", "Response: " + response);
if(response.contains("failed")){ // 서버가 동작하지 않을 때의 메시지에 포함된 걸 체크하라.
    Log.e("WEB", "Server isConnected failed.");
    startActivity(new Intent(getApplication(), MainMenu.class));
    finish();
} else {
    Log.e("WEB", "Server Version : " + response);
    Response = response.replaceAll("[^0-9]", ""); // 버전에서 숫자만 추출
    if (Integer.parseInt(version) < Integer.parseInt(Response)) { // 서버 버전이 더 높으면
        UpgradeProcess();
    } else {
        startActivity(new Intent(getApplication(), MainMenu.class));
        finish();
    }
}


728x90
블로그 이미지

Link2Me

,
728x90

안드로이드 메모 어플을 https://github.com/DomHeal/Android-Memo-Application 에서 받아서 코드를 API 28 (Android 9) 에서 동작하도록 수정한 코드이다.

ListView 에 일렬로 보여주는 아주 간단한 메모 코드다. SQLite DB를 사용하여 데이터를 로컬 DB에 저장하고 보여주는 코드이므로 달력 형태로 보여주는 건 아니다.

간단하게 SQLite DB 사용법 익히는 용도로 좋은 예제인거 같다.


memo.zip


앱 build.gradle

apply plugin: 'com.android.application'

android {
    compileSdkVersion 28


    defaultConfig {
        applicationId "com.tistory.link2me.memo"
        minSdkVersion 19
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }

}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
}


AndroidManifest.xml

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

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

    <application
        android:allowBackup="false"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:usesCleartextTraffic="true" <!-- 서버와 http 통신할 경우 추가해야 정상동작 -->
        android:theme="@style/AppTheme">
        <activity android:name=".Alert"></activity>
        <activity android:name=".Edit_Note" />
        <activity android:name=".View_Note" />
        <activity android:name=".CreateNote" />
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

        <receiver
            android:name=".AlarmReceiver"
            android:enabled="true"
            android:exported="true" />
    </application>

</manifest>
 


MainActivity.java

import android.content.Intent;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.SimpleCursorAdapter;

import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {
    SQLiteDatabase db;
    DbHelper mDbHelper;
    ListView list;

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

        list = (ListView)findViewById(R.id.commentslist);
        mDbHelper = new DbHelper(this);
        db= mDbHelper.getWritableDatabase();
        final ImageView alarmImage = (ImageView) findViewById(R.id.alarmImage);

        String[] from = {mDbHelper.TITLE, mDbHelper.DETAIL, mDbHelper.TYPE, mDbHelper.TIME, mDbHelper.DATE};
        final String[] column = {mDbHelper.C_ID, mDbHelper.TITLE, mDbHelper.DETAIL, mDbHelper.TYPE, mDbHelper.TIME, mDbHelper.DATE};
        int[] to = {R.id.title, R.id.Detail, R.id.type, R.id.time, R.id.date};

        final Cursor cursor = db.query(mDbHelper.TABLE_NAME, column, null, null ,null, null, null);
        final SimpleCursorAdapter adapter = new SimpleCursorAdapter(this, R.layout.list_entry, cursor, from, to, 0);

        list.setAdapter(adapter);
        list.setOnItemClickListener(new AdapterView.OnItemClickListener(){
            public void onItemClick(AdapterView<?> listView, View view, int position,
                                    long id){
                Intent intent = new Intent(MainActivity.this, View_Note.class);
                intent.putExtra(getString(R.string.rodId), id);
                startActivity(intent);
            }

        });

    }

    @Override
    public void onBackPressed() {
        moveTaskToBack(true);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {

        switch(item.getItemId()) {
            case R.id.action_new:
                Intent openCreateNote = new Intent(MainActivity.this, CreateNote.class);
                startActivity(openCreateNote);
                return true;

            default:
                return super.onOptionsItemSelected(item);
        }
    }

}


나마저 코드와 xml 파일은 첨부파일을 참조해서 프로젝트 생성하고 모듈 추가해서 코드를 덮어쓰거나 변형하여 덮어쓰기 하면 동작되는 걸 확인할 수 있다.

ListView 를 사용한 코드인데 RecyclerView 로 변경하는 건 나중에 기회되면 수정해서 올려볼 생각이다.


참고자료

안드로이드 모듈 추가 방법 https://link2me.tistory.com/1369 참조



728x90
블로그 이미지

Link2Me

,
728x90
You can not use .equals() to compare int.
But you can use == also to compare Integer.
Integer and int are different. Integer is a class and int is a primitive type.

public class IntegerToString {
    public static void main(String[] args) {
        // declare the Integer object
        Integer intValue = new Integer(100);
        // Declare the Object
        Object obj = new Integer(100);
        // test for equality
        if(intValue.equals(obj)){
            System.out.println("Integer is equals to the object.");
        }
        else{
            System.out.println("Not equal, something is wrong.");
        }
    }
}


결과 : Integer is equals to the object.


난독화 가이드에 String 비교시 == 를 사용하지 말라고 되어 있어서

if (deviceFound == null) {
대신에
if (!"".equals(deviceFound)) {
로 변경해서 난독화 하지 않은 상태로 시도했더니 에러가 발생한다.


즉, null 은 객체가 아니라서 equals 로 비교할 수가 없다.

!"".equals(deviceFound) 이 null 을 대신할 수는 없다는 것이다.


!"".equals(st)
형태로 많이 쓴다고 되어 있어서 테스트를 해봤는데 잘못된 정보다.

null 비교는 == 연산자를 사용하면 된다.


equals는 기본적으로 값 비교를 한다.
== 는 주소값을 비교한다.
String 객체를 == 연산자로 비교하면 '주소값' 을 비교한다.
비교 대상 중 primitive type(int) 의 변수가 하나라도 있다면, == 연산자는 값으로 비교한다.


private String IPSubnetMask(String msgArray){
    if(msgArray.contains("/")){
        String[] input_ipsubnet = msgArray.split("/"); // / 기호로 분리하라.
        String ipaddress = input_ipsubnet[0];
        String subnetMask = input_ipsubnet[1];
        if(IP_Validation.matcher(ipaddress).matches()) {
            if(Integer.parseInt(subnetMask)< 23) {
                return "3"; // 서브넷 마스크 확인 필요
            } else {
                return "1"; // 정상
            }
        } else {
            return "2"; // IP주소 비정상 입력
        }
    } else {
        return "0"; // 입력이 잘못되었다는 표시
    }
}


위 함수에서는 결과를 숫자가 아닌 문자 숫자로 반환하도록 했다.

String result = IPSubnetMask(ipsubnet);
if(result.equals("1")){

}

와 같이 equals 로 값 비교하면 된다.



728x90

'안드로이드 > Java 문법' 카테고리의 다른 글

[Java] Convert Month String to Integer  (0) 2019.10.14
Java ArrayList 함수 만들기  (0) 2019.10.02
공백으로 문자열 분리  (0) 2019.09.11
Java 제네릭(Generic)  (0) 2019.08.27
Java 추상 클래스  (0) 2019.08.19
블로그 이미지

Link2Me

,
728x90

http://www.jcraft.com/jsch/ 사이트에서 파일을 다운로드 받는다.



다운로드 받은 파일을 libs 폴더에 복사/이동한다.


앱 build.gradle 에 추가한다.


dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])

    implementation 'androidx.appcompat:appcompat:1.0.2'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    implementation 'gun0912.ted:tedpermission:2.0.0'
    implementation 'com.android.volley:volley:1.1.1'
    implementation 'com.google.android.material:material:1.0.0'
    implementation files('libs/jsch-0.1.55.jar')

}


Sync Now 를 해주면 라이브러리 추가가 완료된다.

728x90
블로그 이미지

Link2Me

,
728x90

안드로이드 배터리 잔량 정보를 표시하는 코드이다.


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/tv_battery"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:gravity="center"
        android:text="배터리 잔량"
        />

</LinearLayout>

import android.content.Intent;
import android.content.IntentFilter;
import android.os.BatteryManager;
import android.os.Bundle;
import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

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

        TextView textView = findViewById(R.id.tv_battery);

        // 안드로이드의 BatteryManager가 고정 인텐트를 사용하고 있기 때문에
        // 실제 BroadcastReceiver를 등록 할 필요가 없다.

        IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
        Intent batteryStatus = registerReceiver(null, ifilter);

        int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
        int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);

        float batteryPct = level / (float)scale;
        String value = String.valueOf((int)(batteryPct*100));

        textView.setText("Battery Level Remaining: " + value + "%");
    }
}


728x90
블로그 이미지

Link2Me

,
728x90

자바에서 공백으로 문자열 분리하는 예제로 MIH 다음에 나오는 IP주소를 얻고 싶은 거다.

split(정규식) 을 사용해서 문자열을 분리해서 얻는다.

먼저 MIH 가 반드시 들어가는 줄인지 체크한 다음에 들어간 줄이면, MIH 이후의 문자열을 공백으로 분리해서 추출한 것과 바로 공백으로 구분해서 값을 추출했다.


public class Split_ex_01 {

    public static void main(String[] args) {
        String str ="    MIH     10.2.224.84      255.255.224.0 (19)   10.2.224.1       Intf(MIH)      ";
        str= str.trim(); // 문자열 앞뒤 공백 제거
        int stlocation = str.indexOf("MIH");
        int endlocation = str.length();
        String temp = str.substring(stlocation+3, endlocation).trim();
        System.out.println(temp);
        String[] part = temp.split("\\s{1,}"); // 공백으로 문자열 분리
        System.out.println(part[0]);
       
        String[] parts = str.split("\\s{1,}");
// 공백으로 문자열 분리       

        System.out.println(parts[1]);
    }
}


728x90

'안드로이드 > Java 문법' 카테고리의 다른 글

Java ArrayList 함수 만들기  (0) 2019.10.02
자바 null 비교, String 비교, 정수 비교  (0) 2019.09.20
Java 제네릭(Generic)  (0) 2019.08.27
Java 추상 클래스  (0) 2019.08.19
[Java] 다형성  (0) 2019.06.30
블로그 이미지

Link2Me

,
728x90

유투브 강좌를 보고 따라서 코드 적어가며 테스트하고 적어둔다.

유투브 동영상 : https://www.youtube.com/watch?v=OTNuxe_ihtM&t=774s

 

 

각각 서로 다른 파일로 생성하여 테스트 해야 하는데 하나의 파일로 작성하여 테스트했다.

- 인터페이스 타입의 변수로 인터페이스를 구현한 클래스의 인스턴스를 참조할 수 있다.

- 인터페이스를 메소드의 매개변수 타입으로 지정할 수 있다.

interface OnFoundListener {
    // 1. Create a callback interface.
    void onFound(String result);
}
 
class ClassA {
    // 2. Create a class as a worker.
    public void findAGirl(OnFoundListener onFoundListener ) {
        for(int i=0; i < 5; i++) {
            try {
                Thread.sleep(100);
                System.out.println("A is trying find a girl for you " + i);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        onFoundListener.onFound("A found a girl for you.");
        // A found nothing, so that assign this job for B.
        //ClassB classB = new ClassB();
        //classB.findACuteGirl(onFoundListener);
    }
 
}
 
public class InerfaceWorld {
    public static void main(String[] args) {
        ClassA classA = new ClassA();
        classA.findAGirl(new OnFoundListener() {
            @Override
            public void onFound(String result) {
                System.out.println(result);
            }
        });
    }
}
 

메소드를 호출하는 쪽에서는 메소드의 내용에 관계없이 선언부만 알면 되기 때문에 이를 이용하여 프로그램을 작성할 수 있다. 동시에 다른 한 쪽에서는 인터페이스를 구현하는 클래스를 작성하도록 하여, 인터페이스를 구현하는 클래스가 작성될 때까지 기다리지 않고도 양쪽에서 동시에 개발을 진행할 수 있다.

 

 

 

 

 

interface OnFoundListener {
    // 1. Create a callback interface.
    void onFound(String result);
}
 
class ClassA {
    // 2. Create a class as a worker.
    public void findAGirl(OnFoundListener onFoundListener ) {
        for(int i=0; i < 5; i++) {
            try {
                Thread.sleep(100);
                System.out.println("A is trying find a girl for you " + i);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //onFoundListener.onFound("A found a girl for you.");
        // A found nothing, so that assign this job for B.
        ClassB classB = new ClassB();
        classB.findACuteGirl(onFoundListener);
    }
}
 
class ClassB {
    public void findACuteGirl(OnFoundListener onFoundListener ) {
        for(int i=0; i < 5; i++) {
            try {
                Thread.sleep(100);
                //System.out.println("A is trying find a girl for you " + i);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        onFoundListener.onFound("A found a cute girl for you.");
    }
}
 
public class InterfaceWorld2 {
    public static void main(String[] args) {
        ClassA classA = new ClassA();
        classA.findAGirl(new OnFoundListener() {
            @Override
            public void onFound(String result) {
                System.out.println(result);
            }
        });
    }
}
 
 

 

 

728x90

'안드로이드 > Interface' 카테고리의 다른 글

Java Interface 예제  (0) 2019.11.18
Android Interface AsyncTask 예제  (0) 2019.11.05
Java 인터페이스(interface) 개요  (0) 2019.08.20
Android Interface 예제 ★★★  (0) 2018.08.22
Android Interface 예제 1  (0) 2018.08.20
블로그 이미지

Link2Me

,
728x90

FCM 기능을 추가하기 위한 코드 변경을 하다보니 잔뜩 에러가 발생한다.


<provider
  android:name="android.support.v4.content.FileProvider" 에러가 표시하고 컴파일시 에러 메시지를 출력한다.


<provider
    android:name="androidx.core.content.FileProvider"
로 변경해준다.


AndroidX를 사용하려면 컴파일 SDK를 Android 9.0(API 레벨 28) 이상으로 설정하고, gradle.properties 파일에서 다음 두 Android Gradle 플러그인 플래그를 true로 설정해야 한다.

android.useAndroidX: true로 설정하면 Android 플러그인에서 지원 라이브러리 대신 적절한 AndroidX 라이브러리를 사용한다. 지정하지 않으면 플래그는 기본적으로 false이다.
android.enableJetifier: true로 설정하면 Android 플러그인에서 자동으로 기존 타사 라이브러리를 이전하여 바이너리를 다시 작성해 AndroidX를 사용한다. 지정하지 않으면 플래그는 기본적으로 false이다.


참조 : https://developer.android.com/jetpack/androidx?hl=ko

728x90
블로그 이미지

Link2Me

,
728x90

안드로이드 WebView 를 이용하여 MDB 기반 Web 화면을 읽어서 처리하는 코드이다.

LG G5폰(안드로이드 6.0)에서는 잘 동작한다.

삼성 갤럭시 S10(안드로이드 9.0)에서 실행을 시켰더니 로그인 자체부터 막힌다.

구글링해보니 HTTPS 통신이어야만 정상 동작된다고 나온다.

HTTP 통신에서 동작하도록 하는 방법은 아래와 같이 코드를 수정해주면 된다.


AndroidManifest.xml 파일 수정사항

    <application
        android:allowBackup="false"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:networkSecurityConfig="@xml/network_security_config"
        android:theme="@style/AppTheme.NoActionBar">


/res/xml/network_security_config.xml 추가

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true" />
</network-security-config>


로 해주거나, 아래와 같이 한줄을 추가해주라고 나온다.

    <application
        android:allowBackup="false"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:usesCleartextTraffic="true"


일단 여기까지 하면 로그인까지는 문제없이 잘 동작되더라.

activity_main.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="match_parent"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="5"
        android:orientation="vertical"
        >
        <WebView
            android:id="@+id/webview"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scrollbars="none"
            android:focusable="true"
            android:focusableInTouchMode="true"
            android:adjustViewBounds="true"
            android:scaleType="fitStart"
            />

    </LinearLayout>

</LinearLayout>


WebView 는 아래와 같이 처리하면 삼성 갤럭시 S10 에서도 이상없이 동작되는 걸 확인했다.

    private void myWebView(){
        String web_url = getIntent().getExtras().getString("url");
        try {
            url = URLDecoder.decode(web_url,"UTF-8"); // target
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        System.out.println("web url : " + web_url);

        mWebView = (WebView) findViewById(R.id.webview); // Layout 와의 연결

        mWebSettings = mWebView.getSettings(); // 세부 세팅 등록
        mWebSettings.setJavaScriptEnabled(true); // 자바스크립트 사용 허용
        mWebSettings.setCacheMode(WebSettings.LOAD_NO_CACHE);
        mWebSettings.setBuiltInZoomControls(false); // 화면 확대 축소 허용 여부
        mWebSettings.setDisplayZoomControls(false);
        mWebSettings.setSupportZoom(true); // 줌 사용 여부
        //mWebSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN); // 컨텐츠 사이즈 맞추기
        mWebSettings.setUserAgentString("Android");
        if(Build.VERSION.SDK_INT >= 16){
            mWebSettings.setAllowFileAccessFromFileURLs(true);
            mWebSettings.setAllowUniversalAccessFromFileURLs(true);
        }
        if(Build.VERSION.SDK_INT >= 21){
            mWebSettings.setMixedContentMode(mWebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
        }
        mWebView.setWebChromeClient(new WebChromeClient());
        mWebView.setWebViewClient(new WebViewClient() {
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                if (url.startsWith("tel:")) {
                    Intent call_phone = new Intent(Intent.ACTION_CALL);
                    call_phone.setData(Uri.parse(url));
                    startActivity(call_phone); // 권한 설정은 Loing.java에서 처리했음
                } else if (url.startsWith("sms:")) {
                    Intent i = new Intent(Intent.ACTION_SENDTO, Uri.parse(url));
                    startActivity(i);
                } else if (url.startsWith("intent:")) {
                    try {
                        Intent intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
                        Intent existPackage = getPackageManager().getLaunchIntentForPackage(intent.getPackage());
                        if (existPackage != null) {
                            startActivity(intent);
                        } else {
                            Intent marketIntent = new Intent(Intent.ACTION_VIEW);
                            marketIntent.setData(Uri.parse("market://details?id=" + intent.getPackage()));
                            startActivity(marketIntent);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                } else {
                    view.loadUrl(url);
                }
                return true;
            }

        });

        mWebView.loadUrl(url);
    }


내가 사용했던 build.gradle, webview 등 파일 일부를 첨부한다.


WebView.zip



728x90
블로그 이미지

Link2Me

,
728x90

JDK 1.5 이전에서는 여러 타입을 사용하는 대부분의 클래스나 메소드에서 인수나 반환값으로 Object 타입을 사용했다.
이 경우에는 반환된 Object 객체를 다시 원하는 타입으로 타입 변환해야 한다.

그리고 원하지 않는 자료형이 입력되었을 때의 오류를 컴파일 시점에서 잡아낼 수 없다.

(Object 클래스는 모든 클래스의 조상(부모)이므로 모든 타입이 들어갈 수 있다.)


자바에서 제네릭(Generic)이란 데이터의 타입(Data Type)을 일반화한다(Generalize)는 것을 의미한다.
제네릭은 클래스나 메소드에서 사용할 내부 데이터 타입을 컴파일 시에 미리 지정하는 방법이다.
이렇게 컴파일 시에 미리 타입 검사(Type Check)를 수행하면 다음과 같은 장점을 가진다.


1. 프로그램 성능저하를 유발하는 캐스팅(강제 데이터타입 변환)을 제거한다.

   - 매번 데이터 타입을 강제로 캐스팅하면, 프로그램 성능 저하가 발생한다.

2. 코드절약 및 코드 재사용성을 증진시켜 유지보수를 편하게 한다.

   - Generic 이 없다면, 매번 필요한 데이터 타입에 맞는 새로운 클래스 또는 메소드를 작성해야 한다.

3. 컴파일시 타입오류를 체크하여, 사전에 엄격한 데이터타입 체크를 가능케한다.



예제 1 

package ex_Generic;
//Object 타입을 사용하면 모든 종류의 자바 객체를 저장할 수 있다는 장점은 있지만
//저장할 때 타입 변환이 발생하고, 읽어올 때도 타입 변환이 발생한다.
//이러한 캐스팅이 빈번해지면 전체 프로그램 성능에 좋지 못한 결과가 발생할 수 있다.
// 제네릭은 클래스를 설계할 때 구체적인 타입을 명시하지 않고,
// 타입 파라미터로 대체했다가, 실제 클래스가 사용될 때 구체적인 타입을 지정함으로써 타입 변환을 최소화한다.
public class Box<T> { // T라는 이름이 매개변수화된 자료형임을 나타냄.
    private T item; // T에 해당하는 자료형의 이름은 인스턴스를 생성하는 순간에 결정된다.
    // T는 상징적인 문자
   
    public Box() { // 생성자
       
    }
   
    public Box(T item) { // 매개변수가 있는 생성자
        this.item = item;
    }
   
    public void setData(T item) { // T[] arr
        this.item = item;
    }
    public T getData() {
        return item;
    }
}

package ex_Generic;

import java.util.ArrayList;
import java.util.List;

abstract class Animal {
    public void go() {
        System.out.println("Animal");
    }
}
class Dog extends Animal {
    public void go() {
        System.out.println("Dog");
    }
}
class Cat extends Animal {
    public void go() {
        System.out.println("Cat");
    }
}

public class Main {
    @SuppressWarnings("unchecked")
    public static <T extends Animal> void doAction(List<T> animals) {
        animals.add((T) new Cat());
        for (Animal animal: animals) {
            animal.go();
        }
    }
       
    public static void main(String[] args) {
        // 한정된 타입을 사용하도록 유도함으로써 타입 안전성 보장
        // 인스턴스 생성시 자료형을 지정한다. Integer
        Box<Integer> box1 = new Box<>();
        box1.setData(10);
        System.out.println(box1.getData());
       
        // 인스턴스 생성시 자료형을 지정한다. String
        Box<String> box2 = new Box<>();
        box2.setData("오랜지");
        System.out.println(box2.getData());
       
        // 컬렉션 프레임워크(Collections Framework)
        // Collection : 객체를 수집해서 저장하는 역할
        // Framework : 사용방법을 미리 정해 놓은 라이브러리
        // List, Set, Map 인터페이스 구현 클래스가 존재
        // List : ArrayList, Vector, LinkedList
        // Set : HashSet, TreeSet
        // Map : HashMap, Hashtable, TreeMap, Properties
        List<String> list = new ArrayList<String>();
        list.add("사과");
       
        String str = list.get(0); // Type 을 변환하지 않는다.
        System.out.println(str);
       
        List<Dog> animals = new ArrayList<Dog>();
        animals.add(new Dog());
        animals.add(new Dog());
        //animals.add(new Cat()); // Cat을 넣으려고 하면 컴파일 시점에 타입 체크를 해주기 때문에 에러 발생
        doAction(animals);
   
    }

}


여러 블로그 자료를 참조하고 동영상 강좌를 들으면서 정리하고 있는데 아직 완벽한 이해는 못한 상태다.

기본 개념은 이 자료로 이해는 된다. 하지만 깊이 있게 들어가는 사항은 좀 더 테스트를 하고 이해해야 한다.

728x90

'안드로이드 > Java 문법' 카테고리의 다른 글

자바 null 비교, String 비교, 정수 비교  (0) 2019.09.20
공백으로 문자열 분리  (0) 2019.09.11
Java 추상 클래스  (0) 2019.08.19
[Java] 다형성  (0) 2019.06.30
How to validate IP address with regular expression  (0) 2019.06.17
블로그 이미지

Link2Me

,
728x90

Java는 다중상속을 지원하지 않는다. (C++은 다중 상속을 지원한다.)

두 조상으로부터 상속받는 멤버 중에서 멤버변수의 이름이 같거나 메서드의 선언부가 일치하고 구현 내용이 다르다면 이 두 조상으로부터 상속받는 자손클래스는 어느 조상의 것을 상속받게 되는 것인지 알 수 없다. 어느 한 쪽으로부터의 상속을 포기하던가, 이름이 충돌하지 않도록 조상클래스를 변경하는 수 밖에는 없다.
자바에서는 이러한 충돌문제를 해결하기 위해서 단일 상속만을 허용하고, 인터페이스를 이용해서 단일 상속의 단점을 보완하도록 하였다.

 

Java 인터페이스
- 인터페이스는 추상메서드, 상수 만을 가질 수 있으며, 이를 직접 구현하지 않는다.
- 인터페이스는 필드(변수)를 포함할 수 없다.
- 정의부분만 표현하고, 구현 부분은 상속받은 클래스에서 구현한다. (구현이라 함은 {}가 포함된 거)
- 클래스가 인터페이스를 가지는 경우 해당 인터페이스의 모든 멤버에 대한 구현(implementation)을 제공해야 한다.
- 여러개의 인터페이스를 부모로 둘 수 있다. (다중 상속 가능)
- 인터페이스로 객체를 생성할 수는 없다.
- 접근 제어자로 public 또는 default를 사용한다. (private 접근제한자는 사용할 수 없다.)
- 모든 멤버 변수는 public static final 이어야 하며, 이를 생략할 수 있다.
- 모든 메서드는 public abstract 이어야 하며, 이를 생략할 수 있다.

- 인터페이스의 구현은 extends가 아닌 implements를 사용한다.

- 인터페이스는 둘 이상을 동시에 구현 가능하다.(다중 상속 효과)

- 인터페이스간 상속이 가능한데 이때는 extends를 사용한다.

 

Android에서는 한 프로세스가 다른 프로세스의 메모리에 정상적으로 액세스할 수 없다.

 

interface 인터페이스이름 {
      public static final 타입 상수이름 = 값;
      public abstract 메서드이름(매개변수목록);
}



- interface 내에 선언된 변수는 public static final 로 선언되고 이를 생략할 수 있다.
- interface 내에 선언된 메소드는 public abstract 로 선언되고 이를 생략할 수 있다.
- interface 도 참조변수 선언 가능하고, 메소드 오버라이딩 원칙도 그대로 적용된다.

 

인터페이스에 정의된 모든 멤버 변수는 예외없이 적용되는 사항이기 때문에 제어자를 생략할 수 있는 것이며, 편의상 생략하는 경우가 많다. 생략된 제어자는 컴파일 시에 컴파일러가 자동으로 추가해준다.
 
 

예제

 

import java.util.Scanner;
 
interface IEmployee {
    // 멤버 앞에 접근제한자 생략 가능
    // 컴파일러에 의해 public abstract 가 붙음
    String getName();
    void setName(String n);
    int getCounter();
}
 
class Staff implements IEmployee {
 
    private String name;
    private int counter;
    public static int NOofEmployees;
 
    public Staff() { // 생성자
        counter = ++counter + NOofEmployees;
    }
 
    @Override
    public String getName() {
        return name;
    }
 
    @Override
    public void setName(String name) {
        this.name = name;
    }
 
    @Override
    public int getCounter() {
        return counter;
    }
}
 
public class IEmployeeEx {
    private static Scanner kb;
 
    public static void main(String[] args) {
        String name;
        kb = new Scanner(System.in);
        System.out.print("직원 수 입력: ");
        Staff.NOofEmployees = Integer.parseInt(kb.next());
 
        Staff my = new Staff();
        System.out.print("신입 직원의 이름을 입력하세요: ");
        name = kb.next();
        my.setName(name);
 
        System.out.println("▷ 직원 정보 ◁");
        System.out.println(String.format("직원 번호 : %d, 직원이름 : %s", my.getCounter(),my.getName()));
 
    }
}
 
 

 

본 예제는 C# 강의를 들으면서 정리한 예제를 Java 로 구현한 것이다.

인터페이스에 대한 개념 이해가 부족해서 Android 에서 인터페이스 구현을 쉽게 못했는데 이제는 할 수 있을 거 같다.

C# 인터페이스 예제와 비교해서 보면 좀 더 이해가 빠를 수도 있다.

좀 더 심도있는 것은 구현해보면서 예제를 추가해볼 생각이다.

 

참고하면 좋은 자료

https://blog.naver.com/sensate1024/221232506713

 

728x90
블로그 이미지

Link2Me

,
728x90

추상 클래스
추상 클래스(abstract class)란 하나 이상의 추상 메소드(abstract method)를 포함하는 클래스이다.
- 추상 메소드는 선언만 있고 구현이 없는 메서드이며 선언부에 'abstract' 라는 키워드를 붙인다.
- 추상 메소드가 포함되었다면 클래스도 추상 클래스이므로 클래스명 앞에도 'abstract' 키워드를 붙여야 한다.
- 추상 클래스는 추상 메서드를 포함하고 객체화 할 수 없다는 점만 제외하고 일반 클래스와 다르지 않으며 생성자, 멤버변수와 일반 메서드를 가질 수 있다.
- 추상 클래스는 다른 클래스들에게서 공통으로 가져야하는 메소드들의 원형을 정의하고 그것을 상속받아서 구현토록 하는데 사용된다.
- 해당 추상클래스를 상속받는 자식 클래스들은 반드시 추상메서드를 상속받아 재정의(오버라이딩)해야 한다.


public abstract class 클래스명 {
  필드
  생성자
  메서드
  추상메서드
}


package ex_abstract;

public abstract class Animal {
    // 추상 클래스(abstract class)란 하나 이상의 추상 메소드(abstract method)를 포함하는 클래스
    // 추상 메소드가 포함되었다면 클래스도 추상 클래스이므로 클래스명 앞에도 'abstract' 키워드를 붙여야 한다.
    public String aType;
    public void move() {
        System.out.println("동물이 어슬렁 어슬렁 움직인다.");
    }
    abstract void sound(); // 추상(abstract) 메서드는 선언만 있고 구현이 없는 메서드
}

package ex_abstract;

public class Dog extends Animal {
    public void move() {
        System.out.println("노곤하개");
    }
   
    @Override
    void sound() {

        // 추상클래스를 상속받는 자식 클래스들은 반드시 추상메서드를 상속받아 재정의(오버라이딩)해야한다.

        System.out.println("노곤하개 컹컹");
    }
}

package ex_abstract;

public class Cat extends Animal  {
   
    public Cat() {
        this.aType ="고양이과 동물";
    }

    @Override
    void sound() {
        // 추상클래스를 상속받는 자식 클래스들은 반드시 추상메서드를 상속받아 재정의(오버라이딩)해야한다.
        System.out.println("고양이 냐옹");
    }

}

package ex_abstract;

public class Main {

    public static void main(String[] args) {
        Dog dog = new Dog();
        Cat cat = new Cat();
        dog.move(); // 오버라이드된 메소드 호출 ==> 노곤하개
        cat.move(); // 추상클래스 메소드 호출 ==> 동물이 어슬렁 어슬렁 거린다.
        dog.sound(); // 자식 클래스의 구현된 메소드 호출 ==> 노곤하개 컹컹
        cat.sound(); // 자식 클래스의 구현된 메소드 호출 ==> 고양이 냐옹
    }
}


ㅇ 부모 클래스의 생성자를 호출하려면 super 키워드를 사용해야 한다.

package ex_abstract;

public abstract class Animal {
    // 추상 클래스(abstract class)란 하나 이상의 추상 메소드(abstract method)를 포함하는 클래스
    // 추상 메소드가 포함되었다면 클래스도 추상 클래스이므로 클래스명 앞에도 'abstract' 키워드를 붙여야 한다.
    public String name;
   
    public Animal() {
        super();
    }
    public Animal(String name) {
        this.name = name;
    }
    public void move() {
        System.out.println("동물이 어슬렁 어슬렁 움직인다.");
    }
    abstract void sound(); // 추상(abstract) 메서드는 선언만 있고 구현이 없는 메서드
}

package ex_abstract;

public class Cat extends Animal  {
   
    public Cat(String name) {
        super(name); // 부모 클래스의 생성자를 호출하려면 super 키워드를 사용해야 한다.
    }

    @Override
    void sound() {
        // 추상클래스를 상속받는 자식 클래스들은 반드시 추상메서드를 상속받아 재정의(오버라이딩)해야한다.
        System.out.println("고양이 냐옹");
    }

}

package ex_abstract;

public class Dog extends Animal {
   
    public Dog() {
        super(); // 부모 클래스의 생성자를 호출하려면 super 키워드를 사용해야 한다.
    }

    public Dog(String name) {
        super(name); // 부모 클래스의 생성자를 호출하려면 super 키워드를 사용해야 한다.
    }

    public void move() {
        System.out.println("노곤하개");
    }
   
    @Override
    void sound() {
        // 추상클래스를 상속받는 자식 클래스들은 반드시 추상메서드를 상속받아 재정의(오버라이딩)해야한다.
        System.out.println("노곤하개 컹컹");
    }
}

package ex_abstract;

public class Main {

    public static void main(String[] args) {
        Dog dog = new Dog("송백구");
        Cat cat = new Cat("조나비");
        dog.move(); // 오버라이드된 메소드 호출
        cat.move(); // 추상클래스 메소드 호출
        dog.sound(); // 자식 클래스의 구현된 메소드 호출
        cat.sound(); // 자식 클래스의 구현된 메소드 호출
        System.out.println("고양이의 이름은 : " + cat.name);
        System.out.println("진돗개의 이름은 : " + dog.name);
    }
}


728x90

'안드로이드 > Java 문법' 카테고리의 다른 글

공백으로 문자열 분리  (0) 2019.09.11
Java 제네릭(Generic)  (0) 2019.08.27
[Java] 다형성  (0) 2019.06.30
How to validate IP address with regular expression  (0) 2019.06.17
자바 클래스 개념 이해 예제  (0) 2019.05.27
블로그 이미지

Link2Me

,
728x90

Android Studio 에서 타이틀 바가 안나오게 하는 방법은 두가지가 있다.

values 폴더 밑에 styles.xml 파일을 수정한다.


방법 1.

styles.xml 파일을 아래와 같이 수정한다.

<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

</resources> 


방법2.

styles.xml 파일을 아래와 같이 수정한다.

<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>
    <style name="AppTheme.NoActionBar">
        <item name="windowActionBar">false</item>
        <item name="windowNoTitle">true</item>
    </style>

</resources>


AndroidManifest.xml 파일 수정사항

    <application
        android:allowBackup="false"
        android:icon="@drawable/icon"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme.NoActionBar">
 



두번째 방법이 손 댈 곳이 좀 더 많다.

728x90
블로그 이미지

Link2Me

,
728x90

"자바로 배우는 자료구조" 동영상 강의를 듣고 있다.

"자료구조와 함께 배우는 알고리즘 입문" 자바편 책과는 무관하다.

처음에는 이 책의 동영상 강의인줄 알고 찾아서 듣기 시작했는데, 강의를 들으면서 내가 몰랐던 부분, 놓치고 있던 부분 등을 알 수 있어 많은 도움이 된다.


다형성이 자바의 핵심이라고 하면서 C++과 가장 다른 점이라고 강조를 한다.

동영상 강의를 들을 때는 알 거 같은데 막상 뒤돌아서면 머리속에 남는게 없다. 아직 내 것으로 만들지 못해서다.

자바의 정석 책을 꺼내서 다형성 부분을 살펴보니까 강의에서 했던 내용이랑 거의 비슷하게 설명이 잘 되어 있다.


- 수퍼클래스 타입의 참조변수가 서브클래스 타입의 객체를 참조할 수 있다.

   Computer test = new Notebook();



출처: https://link2me.tistory.com/1269?category=942905 [소소한 일상 및 업무TIP 다루기]

- 수퍼클래스 타입의 참조변수가 서브클래스 타입의 객체를 참조할 수 있다.

   Computer test = new Notebook();



출처: https://link2me.tistory.com/1269?category=942905 [소소한 일상 및 업무TIP 다루기]

다형성(Polymorphism)

- 수퍼클래스 타입의 참조변수가 서브클래스 타입의 객체를 참조할 수 있다.

   Computer test = new Notebook();



출처: https://link2me.tistory.com/1269?category=942905 [소소한 일상 및 업무TIP 다루기]

다형성(Polymorphism)

- 수퍼클래스 타입의 참조변수가 서브클래스 타입의 객체를 참조할 수 있다.

   Computer test = new Notebook();



출처: https://link2me.tistory.com/1269?category=942905 [소소한 일상 및 업무TIP 다루기]
지금까지 우리는 생성된 인스턴스를 다루기 위해서, 인스턴스의 타입과 일치하는 타입의 참조변수만을 사용했다.

Notebook notebook = new Notebook();

Computer computer = new Computer();



수퍼 클래스 타입의 참조변수로 서브 클래스 타입의 객체를 참조할 수 있다. (동영상 강의)

조상 클래스 타입의 참조변수로 자손 클래스의 인스턴스를 참조할 수 있도록 하였다. (자바의 정석)


클래스가 상속관계에 있는 경우 조상 클래스 타입의 참조변수로 자손 클래스의 인스턴스를 참조하는 것도 가능하다.

Computer test = new Notebook();


반대로 자손 타입의 참조변수로 조상 타입의 인스턴스를 참조하는 것은 불가능하다.

참조변수가 사용할 수 있는 멤버의 개수는 인스턴스의 멤버 개수보다 같거나 적어야 한다.


아래 예제는 생활코딩 강좌와 자바의 자료구조 강좌를 혼용하여 작성했다.

생활코딩 강좌가 개념을 좀 더 명확하게 해주는 거 같기는 하다. 자바 자료구조 강좌가 실제 코딩에 좀 더 접근한 설명으로 도움이 되는 부분도 있고 둘 다 들어보면 좋을 거 같다.


package section2;

class A {
    public String x() {    return "A.x"; }
}

class B extends A {
    String screensize ="1920";
    public String x() {
        String result = super.x() + "/" + screensize;
        return result;
    }

    public String y() { return "y"; }
}

class B2 extends A {
    public String x() { return "B2.x"; }
}

interface I1 {
    public String A();
}

interface I2 {
    public String B();
}

class C implements I1, I2 {

    @Override
    public String B() { return "B"; }

    @Override
    public String A() { return "A";    }
}

public class PolymorphismDemo1 {

    public static void main(String[] args) {
        A obj = new B();
        // 수퍼 클래스(조상) 타입의 참조변수로 서브 클래스(자손) 타입의 객체를 참조할 수 있다.
        // 반대로 자손 타입의 참조변수로 조상 타입의 인스턴스를 참조하는 것은 불가능하다.
        System.out.println(obj.x()); // 출력 : A.x/1920, dynamic binding
        // 클래스 B를 클래스 A의 데이터 타입으로 인스턴스화 했을 때,

        // 클래스 A에 존재하는 맴버만이 클래스 B의 맴버가 된다.
        // 동시에 클래스 B에서 오버라이딩한 맴버의 동작방식은 그대로 유지한다.
        //obj.y(); // 존재하지 않는 메소드처럼 실행되지 않는다.
       
        A obj2 = new B2();
        System.out.println(obj2.x()); // 출력 : B2.x, 두 인스턴스의 메소드 x를 호출한 결과는 서로 다르다.
       
        C obj3 = new C();
        I1 objI1 = new C();
        I2 objI2 = new C();

        System.out.println(obj3.A()); // 출력 : A
        System.out.println(obj3.B()); // 출력 : B
        System.out.println(objI1.A()); // 출력 : A
        System.out.println(objI2.B()); // 출력 : B
    }

}





728x90

'안드로이드 > Java 문법' 카테고리의 다른 글

Java 제네릭(Generic)  (0) 2019.08.27
Java 추상 클래스  (0) 2019.08.19
How to validate IP address with regular expression  (0) 2019.06.17
자바 클래스 개념 이해 예제  (0) 2019.05.27
자바 Arraylist  (0) 2019.05.26
블로그 이미지

Link2Me

,
728x90
안드로이드 팝업창에서 입력 정보를 받아서 처리하는 AlertDialog 코드 예시다.
IP 등록 함수 구현 내용은 적어두지 않는다.

AlertDialog.Builder alert_ipedit = new AlertDialog.Builder(MainActivity.this);
alert_ipedit.setTitle("XXX 스위치 IP주소 등록");
alert_ipedit.setMessage("(예시) 10.10.10.10/24");
final EditText etip = new EditText(MainActivity.this);
alert_ipedit.setView(etip);
if(ipsubnet != null){
    etip.setText(ipsubnet.trim()); // 한번 입력한 값이 있으면 표시한다.
}
// 확인 버튼 설정
alert_ipedit.setPositiveButton("등록", new DialogInterface.OnClickListener() {
    @Override
    public void onClick(DialogInterface dialog, int which) {
        ipsubnet = etip.getText().toString().trim();
        if(ipsubnet.length() == 0) {
            AlertDialog.Builder subnetmask_confirm = new AlertDialog.Builder(MainActivity.this);
            subnetmask_confirm.setMessage("입력된 정보가 없습니다.").setCancelable(false).setPositiveButton("확인",
                    new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) { // 'YES'
                            dialog.dismiss();
                        }
                    });
            AlertDialog alert1 = subnetmask_confirm.create();
            alert1.show();
            return;
        }
        String result = IPSubnetMask(ipsubnet);
        if(result.equals("1")){
            IPReg(ipsubnet); // IP 등록함수 실행
        } else if(result.equals("2")){
            AlertDialog.Builder subnetmask_confirm = new AlertDialog.Builder(MainActivity.this);
            subnetmask_confirm.setMessage("ip 주소 입력이 잘못되었습니다.\n입력값 :"+ipsubnet).setCancelable(false).setPositiveButton("확인",
                    new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) { // 'YES'
                            dialog.dismiss();
                        }
                    });
            AlertDialog alert1 = subnetmask_confirm.create();
            alert1.show();
        } else if(result.equals("3")){
            // 서브넷 마스크 맞는지 팝업창
            AlertDialog.Builder subnetmask_confirm = new AlertDialog.Builder(MainActivity.this);
            subnetmask_confirm.setMessage("서브넷 마스크가 맞습니까?\n입력값 :"+ipsubnet).setCancelable(false).setPositiveButton("확인",
                    new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) { // 'YES'
                            IPReg(ipsubnet); // IP 등록함수 실행
                        }
                    }).setNegativeButton("취소",
                    new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            dialog.dismiss();
                        }
                    });
            AlertDialog alert1 = subnetmask_confirm.create();
            alert1.show();
        } else {
            AlertDialog.Builder subnetmask_confirm = new AlertDialog.Builder(MainActivity.this);
            subnetmask_confirm.setMessage("IP 주소 입력 정보가 잘못되었습니다.\n입력값 :"+ipsubnet).setCancelable(false).setPositiveButton("확인",
                    new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) { // 'YES'
                            dialog.dismiss();
                        }
                    });
            AlertDialog alert1 = subnetmask_confirm.create();
            alert1.show();
        }
    }
});
alert_ipedit.setNegativeButton("취소", new DialogInterface.OnClickListener() {
    @Override
    public void onClick(DialogInterface dialog, int which) {
        dialog.dismiss();
    }
});
alert_ipedit.show();




728x90
블로그 이미지

Link2Me

,
728x90

How to alidate IP address with regular expression


IP 주소와 Subnet Mask 입력이 잘못된 것인지 체크하는 메소드를 작성해봤다.


import java.util.regex.Pattern;

public class Validation {

    public static void main(String[] args) {
       
        String inputValue = "10.10.10.256/21";
        String result = IPSubnetMask(inputValue);
        if(result.equals("1")){
            System.out.println("IP 주소와 서브넷 마스크를 정상적으로 입력했습니다.");
        } else if(result.equals("2")){
            System.out.println("ip 주소 입력이 잘못되었습니다.");
        } else if(result.equals("3")){
            System.out.println("서브넷 마스크가 맞나요?");
        } else {
            System.out.println("IP 주소와 서브넷 마스크 입력 정보를 확인하세요.");
        }       

    }
   
    private static String IPSubnetMask(String masArray){
        if(masArray.contains("/")){
            String[] input_ipsubnet = masArray.split("/"); // / 기호로 분리하라.
            String ipaddress = input_ipsubnet[0];
            String subnetMask = input_ipsubnet[1];
            System.out.println("ipaddress :" + ipaddress + ", subnetMask : " + subnetMask);
            if(IP_Validation.matcher(ipaddress).matches()) {
                if(Integer.parseInt(subnetMask)< 23) {
                    return "3"; // 서브넷 마스크 확인 필요
                } else {
                    return "1"; // 정상
                }               
            } else {
                return "2"; // IP주소 입력 체크
            }
        } else {
            return "0";
        }
    }
   
    private static final Pattern IP_Validation =
            Pattern.compile("^((25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[0-9])\\.){0,3}"+
                    "((25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[0-9])){0,1}$");
   
}
 





728x90

'안드로이드 > Java 문법' 카테고리의 다른 글

Java 추상 클래스  (0) 2019.08.19
[Java] 다형성  (0) 2019.06.30
자바 클래스 개념 이해 예제  (0) 2019.05.27
자바 Arraylist  (0) 2019.05.26
Java BubbleSort  (0) 2019.05.13
블로그 이미지

Link2Me

,
728x90

Android 앱을 개발한 후 배포를 하기 위해 알아야 할 사항이다.


앱 build.gradle 에서

    defaultConfig {
        applicationId "com.android.USBController"
        minSdkVersion 19
        targetSdkVersion 27
        versionCode 1
        versionName "1.0"


모든 Android 앱은 저마다 com.example.myapp과 같이 자바 패키지 이름처럼 보이는 고유한 애플리케이션 ID가 있다.
이 ID 덕분에 기기와 Google Play Store에서 각각의 앱을 고유하게 식별할 수 있는 것이다.
앱의 새로운 버전을 업로드하려면 애플리케이션 ID와 로그인할 때 사용할 인증서가 원래의 APK와 같아야 한다.
애플리케이션 ID를 변경할 경우 Google Play Store에서는 APK를 완전히 다른 앱으로 취급하게 된다.
따라서 일단 앱을 게시한 후에는 절대로 애플리케이션 ID를 변경하지 마시라.

앱을 개발할 때 기능을 약간 다르게 비교하면서 개발할 경우 applicationId 인 com.android.USBController 를 같게 하면 동일한 앱으로 컴파일된다는 걸 알 수 있다.
이 applicationId 만 다르게 하여 컴파일하면 두개의 앱이 생성되는 걸 확인할 수 있을 것이다.

PlayStore에 등록된 앱은 VersionCode와 VersionName의 두가지 속성을 가지고 있다.

VersionCode는 정수값을 이용하는데, 플레이 스토어 내부적으로 상위 버전을 구분하는데 사용되고, VersionName은 플레이 스토어에서 사용자에게 보여주기 위한 값으로 사용된다.


versionCode : 정수이며, 내부 버전 번호로 사용된다.
하나의 버전이 다른 버전보다 최신인지 여부를 판단하는 데만 사용되며, 번호가 높을수록 최신 버전이다.


versionName : 문자열이며, 사용자에게 표시되는 버전 번호로 사용된다.
사용자에게 표시하는 것 이외에 다른 용도는 없다.
따라서 별도로 시스템상으로 강제하고 있지 않다.


기업 내부용으로 이용시에는 신뢰할 수 없는 앱이라는 경고 표시가 나온다.

앱을 새롭게 업데이트해서 배포할 때마다

versionCode 와 versionName 을 숫자를 증가시켜야 오류가 발생하지 않는다.


아래 코드는 앱에서 현재 사용하는 버전이 어떤 버전인지 UI상에 표시해주는 코드이다.

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_menu);
        mContext = this.getBaseContext();
        backPressHandler = new BackPressHandler(this); // 뒤로 가기 버튼 이벤트

        // type1이 버튼형식
        findViewById(R.id.menu1).setOnClickListener(this);
        findViewById(R.id.menu2).setOnClickListener(this);
        findViewById(R.id.menu3).setOnClickListener(this);
        findViewById(R.id.menu4).setOnClickListener(this);

        TextView tv_version = findViewById(R.id.version);

        PackageInfo pInfo = null;
        try {
            pInfo = getPackageManager().getPackageInfo(this.getPackageName(), 0);
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        int versionCode = pInfo.versionCode;
        String versionName = pInfo.versionName; // build.gradle 에 기록된 VersionName
        tv_version.setText("Version "+versionName);
    }


728x90
블로그 이미지

Link2Me

,
728x90

LG G5 개발용 폰에서는 테스트하면 정상적으로 잘 설치된다.

그런데 갤럭시노트 8 에서 테스트를 하니까 제대로 설치가 안된다.

"앱이 설치되지 않았습니다." 라면서 설치가 되지 않는다.

 

release 모드로 APK 파일을 생성할 때

Signature Versions : V1 ← OS가 7.0 미만일 때

V2 ← OS가 7.0 이상일 때 체크해서 생성하면 되는데 둘다 체크해서 만들면 된다.

APK 파일이 잘못 생성될 수도 있으므로 프로젝트를 clean 한 후 다시 build한 후 APK를 생성한다.

 

구글은 PHA(유해한 앱) 앱의 비율을 차례대로 줄여 나갔으며, 비공식 마켓 등에서 배포되는 PHA 앱은 여전히 남아 있다.
안드로이드 8.0인 오레오(oreo)부터는 플레이 스토어 를 제외한 비공식 마켓에서 배포되는  앱을 설치할때는 새로운 권한을 얻도록 하였다.

 

1. 이미 동일한 패키지명의 앱이 설치 되어 있는 경우.

   - 이 경우에는 기존 설치된 어플의 흔적을 제거해주고 다시 설치하면 설치가 된다.

 

2. 구글 플레이 프로텍트에서 막은 경우

   - 구글 플레이 스토어 실행

   - 상단 왼쪽에 메뉴 아이콘을 클릭 후 Play 프로텍트 선택

   - "기기에 보안 위협이 있는지 검색" 항목 체크 해제

   - "유해한 앱을 감지하는 기능 보완" 항목 체크 해제

 

안드로이드 8.0 오레오에서 부터 '출처를 알 수 없는 앱' 정책에 변경됐다. 
구글 정책 변경에 따라 기존 방식인 '설정-해제' 방식은 사라지고 각 '앱 별 관리' 방식으로 바뀌게 되었다.

728x90
블로그 이미지

Link2Me

,