안드로이드 UI를 개발할때 Activity 기반에서 Fragment기반으로 개발한지도 상당한 시간이 흘렀습니다..

지금까진 Fragment를 Activity에 붙이기 위해선 FragmentManager 를 이용하여 replace 또는 add를 해야만 했습니다.

예를 들면 아래와 같습니다.

FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.fragment_id, CustomFragment(), "tag_name");
ft.commitAllowingStateLoss();
fm.executePendingTransactions();

시간이 흐르면서 안드로이드도 많은 발전을 했는데, 그중 하나가 NavHostFragment를 통한 네비게이션 입니다.

 

안드로이드 스튜디오를 통해서 NavHostFragment 코드를 보면 Fragment를 상속하고 있고 NavHost 인터페이스를 구현하고 있습니다.

NavHost 는 내부적으로 아래와 같은 인터페이스 함수를 가지고 있습니다.

public interface NavHost {

    /**
     * Returns the {@link NavController navigation controller} for this navigation host.
     *
     * @return this host's navigation controller
     */
    @NonNull
    NavController getNavController();
}

그럼 실전에서 어떻게 구현이 가능한지 확인해 보겠습니다.

자세한 사항은 아래 링크를 통해서 확인하시면 됩니다.

developer.android.com/guide/navigation/navigation-getting-started?hl=ko

 

1. xml을 통하여 fragment 또는 FragmentContainerView 선언

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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"
    app:layout_behavior="@string/appbar_scrolling_view_behavior">

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true"
        app:navGraph="@navigation/nav_graph" />
</FrameLayout>

fragment와 FragmentContainerView 의 차이는 z-ordering를 처리할수 있느냐에 있습니다.

자세한 내용은 아래 링크를 통해서 확인하시면 됩니다.

developer.android.com/reference/androidx/fragment/app/FragmentContainerView

 

FragmentContainerView  |  Android 개발자  |  Android Developers

From class android.view.ViewGroup void addChildrenForAccessibility(ArrayList arg0) void addExtraDataToAccessibilityNodeInfo(AccessibilityNodeInfo arg0, String arg1, Bundle arg2) void addFocusables(ArrayList arg0, int arg1, int arg2) void addKeyboardNavigat

developer.android.com

2. FragmentContainerView 에서 선언한 nav_graph 작성

<?xml version="1.0" encoding="utf-8"?>
<navigation android:id="@+id/nav_graph"
    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"
    app:startDestination="@+id/nav_xxx_start">

    <fragment
        android:id="@+id/nav_xxx_start"
        android:name="com.aaa.bbb.ccc.CustomFragment"
        android:label="main fragment"
        tools:layout="@layout/fragment_start_layout" />

    <fragment
        android:id="@+id/nav_webview"
        android:name="com.aaa.bbb.ccc..WebViewFragment"
        android:label="webview fragment"
        tools:layout="@layout/fragment_webview" />
</navigation>

위에서 보면 navigation tag의 app:startDestination 속성이 있는데 이걸 처음 실행할 fragment id로 설정해 줍니다.

각 fragment에는 name속성에 : Fragment 이름, layout속성에는 해당 프레그먼트의 layout id를 넣어줍니다.

 

3. 실제 네비게이션

findNavController(R.id.nav_host_fragment).navigate(R.id.nav_xxx_start)

findNavController의 id는 NavHostFragment 의 id를 사용하고 뒤의 navigate에서는 실제로 움직일 프레그먼트의 id를 넣어줍니다.

만약 argument를 같이 넣어줄 필요가 있으면 아래와 같이 Bundle을 추가해 줍니다.

val bundle = Bundle().apply {
	putInt(EXTRA_TYPE, position)
}

findNavController(R.id.nav_host_fragment).navigate(R.id.id_want_to_go, bundle)

findNavController 를 사용하면 기본적으로 fragment가 중복 적재됩니다. 다시 말하면 같은 프레그먼트를 호출하면 같은 프레그먼트가 적재되고, backkey를 통해서 하나씩 빠지게 됩니다. 만약 이런 처리를 방어하기 위해서는 아래와 같이 navigation option 처리를 하면 됩니다.

val bundle = Bundle().apply {
	putInt(EXTRA_TYPE, position)
}
val navOption = NavOptions.Builder().setLaunchSingleTop(true).build()
findNavController(R.id.nav_host_fragment).navigate(R.id.id_want_to_go, bundle, navOption)

 잘만 활용하면 기존 방식보다 훨씬 짧은 코드로 쉽게 앱개발을 할수 있을것 같습니다.

'프로그래밍 > 안드로이드' 카테고리의 다른 글

app-ads.txt 처리하는 방법  (0) 2020.02.13
Custom view cycle.  (0) 2016.12.14
Stop watch  (0) 2016.11.29
안드로이드 광고 모듈  (0) 2016.11.25
Glide에서 디스크 캐쉬 사용하지 않게 하는 방법  (0) 2016.09.28
by Invincible Cooler 2020. 12. 1. 11:38

안녕하세요. 오늘은 app-ads.txt를 처리하는 방법에 대해서 적어 볼까 합니다.

 

개인 개발자 기준이며, Website를 운영하는 단체 및 회사와는 관련이 없습니다.

 

- 접속 : admob.com

1. 좌측 메뉴의 앱 -> 모든 앱 보기 -> APP-ADS.TXT 탭 누름 

이렇게 하면 상태가 회색이고 app-ads.txt URL에 아무것도 안나옵니다.

 

그러면 약간 오른쪽의 APP-ADS.TXT설정 방법을 눌러서 설정방법을 참고 합니다.

개인 개발자들은 보통 개인 Website를 구축하고 있지 않기 때문에 

https://www.app-ads-txt.com/ 의 도움을 받아 처리하면 됩니다. 로그인을 하고 하라는 대로 하면

 

오른쪽 Copy to clipboard위에 https url이 나옵니다.

그러면 이 url을 복사해서 구글 콘솔의 해당앱 선택 좌측 메뉴의 앱정보 -> 스토어 등록정보 -> 연락처 세부정보의 웹사이트에 해당 url을 넣어주시면 1~2일 후 admob에 가보면 녹색으로 변화된걸 볼 수 있을겁니다.

 

이런건 티스토리나 Naver블로그에서 지원해 주면 좋은데 아직은 안해주네요.

 

by Invincible Cooler 2020. 2. 13. 09:53

안드로이드 개발시 가장 많이 사용하는 Custom view


이때 customview가 어떻게 작동하는지 cycle을 아는게 너무 중요함





by Invincible Cooler 2016. 12. 14. 17:31

소스 공개합니다.


public class MainActivity extends Activity implements OnClickListener {

private static final int SEND_START_MESSAGE = 0;

private static final int SEND_STOP_MESSAGE = 1;

private static final int SEND_RESET_MESSAGE = 2;

private TextView mTimerTv;

private Button mStartBtn;

private Button mStopBtn;

private Button mResetBtn;

private Button mCheckBtn;

private int mCount = 1;

private long mStartTime = 0L;

private long mTimeInMillies = 0L;

/**

* stop을 눌렀을때, 지금까지 경과시간을 저장하기 위해서

*/

private long mElapsedTime = 0L;

private long mFinalTime = 0L;

private StopwatchHandler mHandler;

private boolean mIsRunning = false;

private ListView mListView;

private ListAdapter mAdapter;

private ArrayList<InfoData> mInfoList = new ArrayList<InfoData>();


@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

mTimerTv = (TextView)findViewById(R.id.timerTv);

mTimerTv.setText("00:00.000");

mStartBtn = (Button)findViewById(R.id.startBtn);

mStartBtn.setOnClickListener(this);

mStopBtn = (Button)findViewById(R.id.stopBtn);

mStopBtn.setOnClickListener(this);

mResetBtn = (Button)findViewById(R.id.resetBtn);

mResetBtn.setOnClickListener(this);

mCheckBtn = (Button)findViewById(R.id.checkBtn);

mCheckBtn.setOnClickListener(this);

mHandler = new StopwatchHandler();

mAdapter = new ListAdapter(this, R.layout.stopwatch_list_block, mInfoList);

mListView = (ListView) findViewById(R.id.listView);

mListView.setAdapter(mAdapter);

mListView.setScrollbarFadingEnabled(true);

}

private class StopwatchHandler extends Handler {

@Override

        public void handleMessage(Message msg) {

            super.handleMessage(msg);

            

            switch(msg.what) {

           case SEND_START_MESSAGE:

            mTimeInMillies = System.currentTimeMillis() - mStartTime;

   

    mFinalTime = mElapsedTime + mTimeInMillies;


    int seconds = (int) (mFinalTime / 1000);

    int minutes = seconds / 60;

    seconds = seconds % 60;

    int milliseconds = (int) (mFinalTime % 1000);

    mTimerTv.setText("" + String.format("%02d", minutes) + ":" + String.format("%02d", seconds) + "." + String.format("%03d", milliseconds));

    mHandler.sendEmptyMessage(SEND_START_MESSAGE);

            break;

           

    case SEND_STOP_MESSAGE:

    mHandler.removeMessages(SEND_START_MESSAGE);

    mElapsedTime += mTimeInMillies;

    break;

   

    case SEND_RESET_MESSAGE:

    if(mIsRunning) {

    mHandler.removeMessages(SEND_START_MESSAGE);

    mIsRunning = false;

    }

   

    mStartTime = 0L;

    mTimeInMillies = 0L;

    mElapsedTime = 0L;

    mFinalTime = 0L;

   

    mTimerTv.setText("00:00.000");

    break;

            }

}

}


@Override

public boolean onCreateOptionsMenu(Menu menu) {


// Inflate the menu; this adds items to the action bar if it is present.

getMenuInflater().inflate(R.menu.main_menu, menu);

return true;

}


@Override

public boolean onOptionsItemSelected(MenuItem item) {

// Handle action bar item clicks here. The action bar will

// automatically handle clicks on the Home/Up button, so long

// as you specify a parent activity in AndroidManifest.xml.

int id = item.getItemId();

if (id == R.id.action_settings) {

return true;

}

return super.onOptionsItemSelected(item);

}


@Override

public void onClick(View v) {

switch(v.getId()) {

case R.id.startBtn:

if(!mIsRunning) {

mStartTime = System.currentTimeMillis(); 

mHandler.sendEmptyMessage(SEND_START_MESSAGE);

mIsRunning = true;

}

break;

case R.id.stopBtn:

if(mIsRunning) {

mHandler.sendEmptyMessage(SEND_STOP_MESSAGE);

mIsRunning = false;

}

break;

case R.id.checkBtn:

if(!mIsRunning) {

// if(mInfoList != null && mInfoList.size() > 0) {

// String tmp = mInfoList.get(mInfoList.size()-1).getTitle().trim();

// Logger.print("tmp : " + tmp);

// Logger.print("tmp : " + mTimerTv.getText().toString());

// if(tmp.equals(mTimerTv.getText().toString())) {

// return;

// }

// }

InfoData info = new InfoData();

info.setIndex(mCount);

info.setTitle(mTimerTv.getText().toString());

mInfoList.add(info);

mAdapter.notifyDataSetChanged();

mCount++;

}

break;

case R.id.resetBtn:

mHandler.sendEmptyMessage(SEND_RESET_MESSAGE);

break;

}

}

public class ListAdapter extends ArrayAdapter<InfoData>

{

private ArrayList<InfoData> mItems;

private LayoutInflater mInflater;


public ListAdapter(Context context, int nTextViewResourceId, ArrayList<InfoData> items)

{

super(context, nTextViewResourceId, items);


this.mItems = items;

mInflater = LayoutInflater.from(context);

}


@Override

public View getView(int position, View convertView, ViewGroup parent)

{

ViewHolder holder;

InfoData info = mItems.get(position);


if(convertView == null)

{

convertView = mInflater.inflate(R.layout.stopwatch_list_block, null);


holder = new ViewHolder();

holder.title = (TextView) convertView.findViewById(R.id.title);


convertView.setTag(holder);

}

else

{

holder = (ViewHolder) convertView.getTag();

}

holder.title.setText(info.getIndex() + ". " + info.getTitle());


return convertView;

}


class ViewHolder

{

TextView title;

}

}

private class InfoData

{

private int index;

private String title;

public int getIndex()

{

return index;

}

public void setIndex(int index)

{

this.index = index;

}

public String getTitle()

{

return title;

}

public void setTitle(String title)

{

this.title = title;

}

}

}

'프로그래밍 > 안드로이드' 카테고리의 다른 글

app-ads.txt 처리하는 방법  (0) 2020.02.13
Custom view cycle.  (0) 2016.12.14
안드로이드 광고 모듈  (0) 2016.11.25
Glide에서 디스크 캐쉬 사용하지 않게 하는 방법  (0) 2016.09.28
Toast  (0) 2015.09.11
by Invincible Cooler 2016. 11. 29. 22:18

안녕하세요. 오랜만에 블로그 작성합니다.


오늘은 안드로이드 광고 모듈에 대해서 여기다 적어 보도록 하겠습니다.


보통 무료앱을 런칭할때 광고 모듈을 앱의 적당한 위치에 삽입을 하는데요, 클릭 단가와 효율에 대해서 여기 적어 보려고 합니다.


먼저 모바일 광고는 수입은 크게 CPC방식과 CPI방식으로 볼수 있습니다. 


다른 여타 방식이 더 있는데, 모바일에서는 거의 적용이 안되는것 같습니다.


CPC방식은 클릭당 과금이고 CPI방식은 설치당 과금입니다.


물론 CPC, CPI 에 대해서 각 광고 모듈에서 무효클릭 불법클릭 체크를 하기 때문에 100번 클릭했다고 100번의 수익이 발생하는건 아닙니다. 회사의 영업 비밀은 저도 모르기 때문에 패스 하겠습니다.


CPC는 클릭을 했을때 수익을 얻을수 있고, CPI는 설치했을때 수익을 얻을수 있습니다. 만약 CPI광고가 많이 나오면 거의 망합니다. 왜냐면 CPI 광고는 사용자가 실수로 클릭을 했더라도, 설치할 일이 없기 때문이죠. 단 CPI는 만약 사용자가 설치를 해준다면 CPC보다 10~20배 이상의 수익을 얻을수 있을겁니다.


여기선 adfit, 카울리, admob 비교를 해보겠습니다. 제가 썼던 플랫폼입니다. 다른 플랫폼 알고 계신분은 알려주시기 바랍니다.


장단점 알려드립니다.


admob

google에서 운영하고 있습니다. 굉장히 공정한 동시에 엄격합니다. 클릭당 과금이 광고에 따라 다르지만 나쁘지 않는것 같네요 (10~30센트 정도 인것 같습니다.), 제가 가장선호하는 광고 모듈입니다. 문제는 계좌를 연결하기가 까다롭고, 수익을 받을때 수수료를 거의 만원(or higher)가까지 가져갑니다. standard chartered에서는 수수료가 없다고 하는데, standard chartered은행 계좌 개설해서 해보세요. (꼭 수수료 확인 해보시고요, 잘못된 정보일수도 있으니까요.)


카울리 : 클릭당 무조건 20원 입니다. 수익률로 따졌을땐 크게 도움이 되지 않는것 같습니다. 좀더 운영해 보고 유지를 할지 말지 판단해야겠습니다.


adfit : 클릭당 10원 ~ 50원 정도 인것 같습니다.  10원짜리 광고는 광고 풀이 없어서 파란색 웹 광고가 나오는데, 그거 같고 나머지는 30원 이상인것 같네요.


fill rate : admob 99% 이상, 카울리 99% 이상, adfit : 80% 이상 - 단 adfit은 수주된 광고가 없으면 파란색 허접한 광고가 나오는 단점이 있음.


추천순 : admob > adfit > 카울리


제 앱에서 노출시 클릭율은 1%가 안됩니다. 100번 노출되면 1번 클릭이 될까 말까인거죠.


무료앱이 많아 질수록 경쟁은 심해지고, 광고회사에서 무효클릭 비율이 예전보다 많이 늘어서 광고모듈로 앱을 개발해 돈을 번다는건 정말 어려운 일이 되었습니다. 수익을 원하시면 왠만하면 아이폰 유료버전 앱을 하시기 바랍니다. 


광고모듈앱의 개발할때의 가장 열받는 점은 광고는 클릭하지도 않는 유저가 광고 있다고, 돈만 밝히는 개발자라고 욕할때죠. 그럴땐 욱하지만, 그때마다 멘탈은 정말 강해지는 장점(?)이 있습니다.


그래도 정말 고맙게 사용한다고 댓글주시거나 메일 주시는 분들이 있어서, 수익과는 상관없이 좋은 컨텐츠로 보답하는건 정말 보람있습니다.

'프로그래밍 > 안드로이드' 카테고리의 다른 글

Custom view cycle.  (0) 2016.12.14
Stop watch  (0) 2016.11.29
Glide에서 디스크 캐쉬 사용하지 않게 하는 방법  (0) 2016.09.28
Toast  (0) 2015.09.11
WeakReference 언제 사용하는가???  (0) 2015.07.28
by Invincible Cooler 2016. 11. 25. 11:28

Glide를 디폴트로 사용하면 메모리 캐시와 디스크 캐시를 자동으로 생성하여, 사용을 합니다.


그런데 가끔 디스크 캐시를 사용하지 않도록 필요할 때가 있습니다. 이때 방법은


GlideModule interface를 구현한후 applyOptions의 builder에 setDiskCache 를 DiskCache null을 리턴하도록 하면 됩니다.


예를 들면 GlideModule 에서


@Override

    public void applyOptions(Context context, GlideBuilder builder) {

        builder.setDiskCache(new NullCacheDiskCacheFactory());

    }

}


public static NullDiskCache sCache = new NullDiskCache();


    @Override

    public DiskCache build() {

        return sCache;

    }

}


public class NullDiskCache implements DiskCache {

    @Override

    public File get(Key key) {

        // no op, default for overriders

        return null;

    }


    @Override

    public void put(Key key, Writer writer) {

        // no op, default for overriders

    }


    @Override

    public void delete(Key key) {

        // no op, default for overriders

    }


    @Override

    public void clear() {

        // no op, default for overriders

    }

}

'프로그래밍 > 안드로이드' 카테고리의 다른 글

Stop watch  (0) 2016.11.29
안드로이드 광고 모듈  (0) 2016.11.25
Toast  (0) 2015.09.11
WeakReference 언제 사용하는가???  (0) 2015.07.28
Android Handler 취소  (0) 2015.04.17
by Invincible Cooler 2016. 9. 28. 17:39

그냥 여담인데,


안드로이드에서 Toast메시지가 혹시


토스트기에서 빵위 튀어나오고 다시 내려가는것을 보고, Toast라는 이름을 짓지 않았나 생각해 본다. 그냥 내상각.


오늘 영어공부중, toast가 나왔는데, 안드로이드 Toast생각이 났다. 이런 직업병


He was choking on a piece of toast

그는 토스트 조각이 목에 걸려 숨을 잘 못쉰다.

by Invincible Cooler 2015. 9. 11. 18:13

오랜만의 포스팅 입니다.

안드로이드를 개발하다 보면, WeakReference를 사용해야 하는 경우가 있습니다. 

WeakReference를 왜 언제 사용해야 하느냐를 구글링을 해보면, 많은 이유가 나옵니다. 하지만 초보가 이해하기 복잡합니다. 그냥 간단히 말하면... Thread-safe하게 내가 사용하고 있는 변수를 가비지 컬렉션할수 있게 하고싶어서 사용한다 입니다. 다시 쉽게 말하면 예를 들어서 핸들러 or AsynTask로 동작하는 작업이 있다고 하면 (예를들어 리스트에서 이미지 로딩같은 것) 백키를 누르거나 다른 버튼을 눌러, 다른 화면으로 이동을 해야 하는 경우가 있습니다. 이때 이미지의 로딩은 프로세스가 아닌, 쓰레드에서 처리를 하고 있는 것이죠. 다시 말하면, 바로 종료가 안되다는 것입니다. 이때가 WeakReference를 사용하는 최적화 타이밍 입니다. 그래서 안드로이드에서 사용하는 이미지 로더들이 WeakReference를 사용하는 것입니다. 물론 핸들러에서 사용하는것도 권장합니다. 왜냐면 핸들러도 프로세스가 종료되더라도, 바로 종료가 되지 않기 때문입니다. 위에서 언급한 이유와 같습니다.

예를 들어서 핸들러를 아래와 같이 abstract로 선언하고 사용한다면, Thread-Safe 한 핸들러를 사용할수 있을것 같네요.


public abstract class ThreadSafeHandler<T> extends Handler {


private WeakReference<T> mReference;


public ThreadSafeHandler(T reference) {

mReference = new WeakReference<T>(reference);

}


@Override

public void handleMessage(Message msg) {

T reference = null;


if (mReference != null) {

reference = mReference.get();

}

else {

reference = null;

}


if (reference == null) {

return;

}


handleMessage(reference, msg);

}


protected abstract void handleMessage(T reference, Message msg);


}

이런식으로 추상핸들러를 작성해서, 사용하면 될것 같네요~~~ 필요한 메소드는 알아서 더 추가해 주면 될것 같고요...


그럼 즐프~~~

by Invincible Cooler 2015. 7. 28. 05:38

오늘은 핸들러를 통해서 핸들러를 취소하는 루틴을 알아보도록 하겠다.


보통 핸들러 사용은 Handler를 생성한후 postDelayed 를 사용하여 UI 업데이트나 시간 delay처리를 한다. 그런데 이렇게 postDelayed를 사용하여 실행된 Runnable 객체를 removeCallbacks를 통해서 제거하고 싶을 때가 있다. 그런데 여기서 문제


removeCallbacks는 pending된 객체만을 제거하는것이다. 다시 말하면 이미 실행되고 있는 Runnable 객체는 최종 끝마치게 된다. 헐~~~ 그러면


이미 실행되고 있는 놈도 날려 버리고 싶다면???


Handler를 통해서 postDelayed를 사용하는 대신 sendMessageDelayed를 사용하고, removeMessages를 통해서 객체를 제거해 보자. API에는 메시지 큐에 있는 pending 객체를 제거한다고 하지만, 실제로 sendMessageDelayed를 통해서 실행되는 놈은 Runnable이 아니여도 되기 때문에 실시간으로 날아가는 효과를 누릴수 있다.

코딩으로 보여주면

mHandler.postDelayed xxxxxxxx

mHandler.removeCallbacks xxxxxxxx

이렇게 호출하면 xxxxxx(Runnable객체) 이놈이 실행되지만

mHandler.removeMessages(xxxx);

mHandler.sendMessageDelayed(msg, 500);

뭐 이런식으로 실행하면 잘된다. ㅋㅋㅋㅋㅋ


그럼 즐프...



by Invincible Cooler 2015. 4. 17. 16:06

parseSdkContent failed

Could not initialize class android.graphics.Typeface


위와같은 에러가 나면 아래와 같이 하면 감쪽같이 사라진다.

1. Download the SDK platform for API 20 (4.4W)

2. Navigate to your sdk folder (should be like D:\EclipseWorkspace\adt-bundle-windows-x86_64-20140321\sdk)

-> Window -> Preferences -> 왼쪽 Android -> SDK Location 확인한다.

3. Go to platforms folder -> android-21 folder -> data folder

4. rename layoutlib.jar (for backup purpose) -> 대충 layoutlib_21.jar 이런식으로 바꾸고

5. copy the same file (layoutlib.jar) from your android-20 folder to this folder

6. restart Eclipse


굿이다 굿...

by Invincible Cooler 2014. 12. 3. 16:21
| 1 2 |