서비스는 항상 Background 에서 동작
Background에서 지속적으로 NW 통신을 하게 된다는건 불합리
날씨를 가져다가 갱신하는 앱 위젯인데
화면이 Off 된 상태에서는 필요 없는 통신이므로
screen on receiver 에 반응해서 통신을 시작 하도록 설계하는것이 중요
동적으로 manifest에 등록 하거나 등록 해제 하도록 설계해야 함
// IPlayService.aidlpackage com.example.ch3_aidl; // Declare any non-default types here with import statements interface IPlayService { int currentPosition(); int getMaxDuration(); void start(); void stop(); int getMediaStatus(); }
package com.example.ch3_aidl; import android.app.Service; import android.content.Intent; import android.os.IBinder; public class PlayService extends Service { public PlayService() { } @Override public IBinder onBind(Intent intent) { // TODO: Return the communication channel to the service. throw new UnsupportedOperationException("Not yet implemented"); } }
<service android:name=".PlayService"
android:enabled="true"
android:exported="true"> <intent-filter>
<action android:name="com.multi.ACTION_PLAY"/> </intent-filter> </service>
package com.example.ch3_aidl; public class CommonProperties { public static final int MEDIA_STATUS_STOP = 0; public static final int MEDIA_STATUS_RUNNING = 1; public static final int MEDIA_STATUS_COMPLETED = 2; }
package com.example.ch3_aidl; import android.app.Service; import android.content.Intent; import android.media.MediaPlayer; import android.os.IBinder; import android.os.RemoteException; public class PlayService extends Service { MediaPlayer player; int status = 0; public PlayService() { } @Override public void onCreate() { super.onCreate(); player = new MediaPlayer(); } @Override public void onDestroy() { super.onDestroy(); player.release(); } @Override public IBinder onBind(Intent intent) { // aidl 파일을 구현한 stub 객체(실 업무 객체가 넘어가는게 아니라)를 리턴 return new IPlayService.Stub(){// Make Module xxx 작업을 해서 에러가 발생하지 않음 @Override public int currentPosition() throws RemoteException { if(player.isPlaying()){ return player.getCurrentPosition(); }else{ return 0; } } @Override public int getMaxDuration() throws RemoteException { if(player.isPlaying()){ return player.getDuration(); }else{ return 0; } } @Override public void start() throws RemoteException { if(!player.isPlaying()){ player = MediaPlayer.create(PlayService.this, R.raw.music); player.start(); player.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { @Override public void onCompletion(MediaPlayer mp) { status = CommonProperties.MEDIA_STATUS_COMPLETED; } }); } status = CommonProperties.MEDIA_STATUS_RUNNING; } @Override public void stop() throws RemoteException { if(player.isPlaying()) player.stop(); status = CommonProperties.MEDIA_STATUS_STOP; } @Override public int getMediaStatus() throws RemoteException { return status; } }; } }
package com.example.ch3_aidl_client; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.RemoteException; import android.os.SystemClock; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.View; import android.widget.ImageButton; import android.widget.ProgressBar; import com.example.ch3_aidl.IPlayService; public class MainActivity extends AppCompatActivity implements View.OnClickListener { IPlayService pService; ImageButton start; ImageButton stop; ProgressBar mProgress; boolean isRunning = true; ProgressThread pt; Handler handler; Intent intent; //--add 1--------------- // bindService시 callback함수를 가지는 interface구현 클래스 ServiceConnection connection = new ServiceConnection() { // bindService에 의해 bind객체가 넘어온 순간 호출 // 두번째 객체가 bind 객체 stub 객체가 넘어 옴 @Override public void onServiceConnected(ComponentName name, IBinder service) { pService = IPlayService.Stub.asInterface(service); start.setEnabled(true); checkService(); } @Override public void onServiceDisconnected(ComponentName name) { pService = null; } }; //------------------------ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); start = (ImageButton) findViewById(R.id.start); stop = (ImageButton) findViewById(R.id.stop); mProgress = (ProgressBar) findViewById(R.id.pb); mProgress.setProgress(0); start.setOnClickListener(this); stop.setOnClickListener(this); start.setEnabled(false); stop.setEnabled(false); handler = new Handler() { // Activity ANR - Thread에서 sendMessage하는 순간 UI Thread에 의해 자동 호출 @Override public void handleMessage(Message msg) { switch(msg.what) { case 1: // stopped media start.setEnabled(true); stop.setEnabled(false); mProgress.setProgress(0); break; } super.handleMessage(msg); } }; //add2------------------------------------- // service 구동을 위한 intent 준비 intent = new Intent("com.multi.ACTION_PLAY"); // 이전버전에서는 그냥 intent를 실행. lollipop부터는 bindService의 경우 package명을 명시 intent.setPackage("com.example.ch3_aidl"); //-------------------------------------- } private void checkService() { if(pService != null) { try { if(pService.getMediaStatus() == CommonProperties.MEDIA_STATUS_STOP) { Log.d("kkang", "MEDIA_STATUS_STOP"); stop.setEnabled(false); } else if(pService.getMediaStatus() == CommonProperties.MEDIA_STATUS_RUNNING) { Log.d("kkang", "MEDIA_STATUS_RUNNING"); start.setEnabled(false); isRunning = true; pt = new ProgressThread(); pt.start(); } } catch (RemoteException e) { } } } //add3------------------------------- @Override protected void onResume() { super.onResume(); bindService(intent, connection, Context.BIND_AUTO_CREATE); checkService(); } @Override protected void onPause() { super.onPause(); unbindService(connection); isRunning = false; } //--------------------------------- @Override public void onClick(View v) { if(v == start) { try { pService.start(); mProgress.setMax(pService.getMaxDuration()); isRunning = true; pt = new ProgressThread(); pt.start(); start.setEnabled(false); stop.setEnabled(true); } catch (RemoteException e) { // TODO Auto-generated catch block e.printStackTrace(); } } else if(v == stop) { try { pService.stop(); isRunning = false; start.setEnabled(true); stop.setEnabled(false); } catch (RemoteException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } class ProgressThread extends Thread { @Override public void run() { while(isRunning) { try { if(pService.getMediaStatus() == CommonProperties.MEDIA_STATUS_COMPLETED) { handler.sendEmptyMessage(1); break; } else { mProgress.setProgress(pService.currentPosition()); SystemClock.sleep(1000); } } catch (RemoteException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } }
<android.support.design.widget.TabLayout
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_below="@id/toolbar"
android:layout_gravity="bottom"
app:layout_collapseMode="parallax"
app:tabGravity="center"
app:tabMode="scrollable">
</android.support.design.widget.TabLayout>
public class MainActivity extends AppCompatActivity
implements TabLayout.OnTabSelectedListener
@Overridepublic void onTabSelected(TabLayout.Tab tab) { viewPager.setCurrentItem(tab.getPosition()); } @Overridepublic void onTabUnselected(TabLayout.Tab tab) { } @Overridepublic void onTabReselected(TabLayout.Tab tab) { }
// TabLayout
TabLayout tabLayout = (TabLayout)findViewById(R.id.tabs); // 아래의 코드로 유저가 viewPager화면 조정시 tab도 자동조정
// 반대로는 개발자가 이벤트 처리로
// tab button의 문자열은 이곳에서 직접줄수도 있고
// 아래 코드에 의해 viewPager의 문자열을 그대로 적용할 수도 있다.
tabLayout.setupWithViewPager(viewPager); tabLayout.addOnTabSelectedListener(this);
class MyPagerAdapter extends FragmentStatePagerAdapter {
....
// viewPager를 위한 PagerTitleStrip, PagerTabStrip에 나올 문자열 // 우리의 경우에는 TabLayout에서 이용한다. String titles[] = new String[]{"TAB1", "TAB2", "TAB3"}; @Override public CharSequence getPageTitle(int position) { return titles[position]; }
....
}
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.4.0'
compile 'com.android.support:recyclerview-v7:24.1.1'
}
<?xml version="1.0" encoding="utf-8"?><android.support.v7.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="@+id/recyclerView" />
RecyclerView recyclerView;
// findViewId의 성능 이슈부분 솔루션
// 내부적으로 holder 객체는 메모리에 누적되어 한번만 view를 find
class MyViewHolder extends RecyclerView.ViewHolder{ public TextView title; // Adapter에서 획득하고자 하는 view
// 매개변수는 항목 layout.xml 초기화 view 계층의 root 객체이다.
// Adapter가 전달할 것임.
public MyViewHolder(View itemView){ super(itemView); title = (TextView)itemView.findViewById(android.R.id.text1); } }
class MyAdapter extends RecyclerView.Adapter<MyViewHolder>{ final List<String> list;
// 항목구성 집합 데이터.. activity가 전달할것임
public MyAdapter(List<String> list){ this.list = list; } // 하나의 항목을 위해 최초에 수행될 부분
// 항목 layout 초기화.. Holder 준비
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { // 항목 layout 초기화
View view = LayoutInflater.from(parent.getContext())
.inflate(android.R.layout.simple_list_item_1, parent, false); return new MyViewHolder(view);
// holder 리턴 시키면 내부적으로 holder 객체를 메모리에 유지
} // 항목 하나를 구성하기 위해서 호출
// 항목구성 데이터, 이벤트 알고리즘이 구성되는 곳
// onCreateViewHolder에서 리턴시킨 holder가 매개변수로
@Override
public void onBindViewHolder(MyViewHolder holder, int position) { String text = list.get(position); holder.title.setText(text); } @Override public int getItemCount() { return list.size(); } }
class MyDecoration extends RecyclerView.ItemDecoration{ // RecyclerView에 Adapter 작업에 의해 항목이 추가된 후에 호출
// 항목위에 추가 drawing 작업이 필요시 이용
@Override
public void onDrawOver(Canvas c, RecyclerView parent,
RecyclerView.State state) { super.onDrawOver(c, parent, state); int width = parent.getWidth(); int height = parent.getHeight(); Drawable dr = getActivity().getResources()
.getDrawable(R.drawable.kbo); int drWidth = dr.getIntrinsicWidth(); int drHeight = dr.getIntrinsicHeight(); int left = width/2 - drWidth/2; int top = height/2 - drHeight/2; c.drawBitmap( BitmapFactory.decodeResource(getActivity().getResources(), R.drawable.kbo), left, top, null); } // 항목 하나하나를 추가하기 위해서
@Override
public void getItemOffsets(Rect outRect, View view,
RecyclerView parent, RecyclerView.State state) { super.getItemOffsets(outRect, view, parent, state); // 항목의 index값
int index = parent.getChildAdapterPosition(view)+1; if(index % 3==0){ outRect.set(20,20,20,60); }else{ outRect.set(20,20,20,20); } view.setBackgroundColor(0xFFECE9E9); ViewCompat.setElevation(view, 20.0f);
// 하위버전 호완을 위해서
}
}
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { recyclerView = (RecyclerView)inflater.inflate(R.layout.fragment_one, container, false); // 항목구성 데이터 준비 List<String> list = new ArrayList<>(); for(int i=0; i<20; i++){ list.add("item="+i); } recyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); recyclerView.setAdapter(new MyAdapter(list)); recyclerView.addItemDecoration(new MyDecoration()); return recyclerView; }
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.4.0'
compile 'com.android.support:design:24.1.1'
}
<android.support.v4.widget.DrawerLayout>
블라블라~~</android.support.v4.widget.DrawerLayout>
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/drawer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
>
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/drawer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
>
<!-- navigation drawer에 의해서 나오는 부분 --><android.support.design.widget.NavigationView
android:id="@+id/main_drawer_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
app:headerLayout="@layout/navigation_header"
app:menu="@menu/main_drawer">
</android.support.design.widget.NavigationView>
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="172dp"
android:background="#FF0000"> <TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="i am header"
android:textColor="#FFFFFF"
android:gravity="bottom"
android:textSize="20dp"
android:layout_marginLeft="20dp"
android:layout_marginBottom="20dp"
/> </LinearLayout>
<?xml version="1.0" encoding="utf-8"?><menu xmlns:android="http://schemas.android.com/apk/res/android"> <group android:checkableBehavior="single"> <item
android:id="@+id/navigation_item_1"
android:icon="@android:drawable/ic_menu_manage"
android:title="Menu1"/> <item
android:id="@+id/navigation_item_2"
android:icon="@android:drawable/ic_menu_search"
android:title="menu2"/> </group> </menu>
DrawerLayout drawer; ActionBarDrawerToggle toggle; boolean isDrawerOpened;
// navigation drawerdrawer = (DrawerLayout) findViewById(R.id.drawer); // 생성자에 인자로 들어간 문자열은 화면 출력 문자열이 아니라// 상태 표현을 위한 접근성 차원에서 제공하는 문자열toggle = new ActionBarDrawerToggle(this, drawer, R.string.drawer_open, R.string.drawer_close) { // 이벤트 추가, 필수는 아님 @Override public void onDrawerOpened(View drawerView) { super.onDrawerOpened(drawerView); isDrawerOpened = true; } @Override public void onDrawerClosed(View drawerView) { super.onDrawerClosed(drawerView); isDrawerOpened = false; } }; drawer.addDrawerListener(toggle); // 이벤트 핸들러인 toggle을 적용// 액션바 아이콘을 업 네비게이션 형태로 표시getSupportActionBar().setDisplayHomeAsUpEnabled(true); NavigationView navigationView = (NavigationView) findViewById(R.id.main_drawer_view); navigationView.setNavigationItemSelectedListener( new NavigationView.OnNavigationItemSelectedListener() { // navigation view의 항목 선택 시 이벤트 콜백함수 @Override public boolean onNavigationItemSelected(MenuItem item) { Log.d("mulcam", "navigation view: " + item.getItemId()); return false; } });
@Overridepublic void onPostCreate(Bundle savedInstanceState, PersistableBundle persistentState) { super.onPostCreate(savedInstanceState, persistentState); toggle.syncState(); } @Overridepublic boolean onOptionsItemSelected(MenuItem item) { if(toggle.onOptionsItemSelected(item)){ // 발생한 메뉴 이벤트가 navigation view 에서의 이벤트인 경우 후속처리 x return true; } return super.onOptionsItemSelected(item); } // navigation drawer가 화면에 보이고있다.// 그 상황에서 유저가 back 버튼을 누르면// back 버튼 이벤트를 제어해서 drawer를 닫아준다.// 모든 key event는 onKeyDown(){}의 함수를 사용하는데// back button만을 제어할 목적이라면 onBackPressed 함수를 이용하면 편하다@Overridepublic void onBackPressed() { if(isDrawerOpened){ drawer.closeDrawers(); }else{ super.onBackPressed(); } }