29 Jan 2017
|
Android
Event
안드로이드에서 어플리케이션(이하 App)을 설치하거나 삭제할 때는 그 이벤트가 Broadcast로
전달됩니다. 즉, BroadcastReceiver를 등록해놓은 각 App들의 설치/삭제 이벤트를 수신할 수 있습니다.
AndroidManifest.xml
manifest.xml에 다음과 같이 BroadcastReceiver를 추가해줍니다.
<receiver android:name=".PackageEventReceiver">
<intent-filter>
<action android:name="android.intent.action.PACKAGE_ADDED"/>
<action android:name="android.intent.action.PACKAGE_REMOVED"/>
<action android:name="android.intent.action.PACKAGE_REPLACED"/>
<data android:scheme="package"/>
</intent-filter>
</receiver>
PackageEventReceiver.java
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
public class PackageEventReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String packageName = intent.getData().getSchemeSpecificPart();
String action = intent.getAction();
if(action.equals(Intent.ACTION_PACKAGE_ADDED)) {
Log.d("", "[snowdeer] Package ADDED : " + packageName);
} else if(action.equals(Intent.ACTION_PACKAGE_REMOVED)) {
Log.d("", "[snowdeer] Package REMOVED : " + packageName);
}
}
}
Event Receiver 등록
private PackageEventReceiver mPackageEventReceiver = new PackageEventReceiver();
private void registerPackageEventReceiver() {
registerReceiver(mPackageEventReceiver, new IntentFilter(Intent.ACTION_PACKAGE_ADDED));
}
또는 다음과 같은 코드를 이용해서 등록하면 됩니다.
private PackageEventReceiver mPackageEventReceiver = new PackageEventReceiver();
private void registerPackageEventReceiver() {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
intentFilter.addAction(Intent.ACTION_PACKAGE_INSTALL);
intentFilter.addDataScheme("package");
registerReceiver(mPackageEventReceiver, intentFilter);
}
27 Jan 2017
|
Android
UX
Open Source
일반적인 AppIntro 화면은 대략 다음과 같은 형태를 하고 있습니다.

많은 사람들이 사용하고 있는 오픈소스가 있으며, 여기에서 확인할
수 있습니다.
Dependency 설정
Android Studio에서는 간단히 gradle에 다음 라인만 추가하면 AppIntro 컴포넌트를 사용할 수 있습니다.
dependencies {
compile 'com.github.paolorotolo:appintro:4.1.0'
}
사용하는 코드는 다음과 같습니다. AppIntro 클래스를 상속받은 Activity를 구현하면 됩니다.
그리고 중요한 점은 onCreate() 함수 내에서 setContentView() 함수는 지워야 한다는 점입니다.
각각의 Intro 화면들은 Fragment를 상속받아서 구현할 수 있습니다.
SplashActivity.java
import android.content.Intent;
import android.os.Build;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.os.Bundle;
import android.view.Window;
import android.view.WindowManager;
import com.github.paolorotolo.appintro.AppIntro;
public class SplashActivity extends AppIntro {
Fragment mSplash1 = new SplashFragment1();
Fragment mSplash2 = new SplashFragment2();
Fragment mSplash3 = new SplashFragment3();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.activity_splash);
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
Window w = getWindow(); // in Activity's onCreate() for instance
w.setFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
}
addSlide(mSplash1);
addSlide(mSplash2);
addSlide(mSplash3);
}
@Override
public void onSkipPressed(Fragment currentFragment) {
super.onSkipPressed(currentFragment);
startMainActivity();
}
@Override
public void onDonePressed(Fragment currentFragment) {
super.onDonePressed(currentFragment);
startMainActivity();
}
@Override
public void onSlideChanged(@Nullable Fragment oldFragment,
@Nullable Fragment newFragment) {
super.onSlideChanged(oldFragment, newFragment);
}
@Override
protected void onResume() {
super.onResume();
}
private void startMainActivity() {
Intent intent = new Intent(SplashActivity.this, MainActivity.class);
startActivity(intent);
finish();
}
}
26 Jan 2017
|
Android
UX
Open Source
Ken Burns Effect는
패닝(Panning)과 줌(Zooming) 기반으로 사진을 화려하게 보여줄 수 있는 효과(Effect)로
사진으로부터 동영상 등을 뽑아낼 때 많이 사용하고 있습니다.
Ken Burns Effect의 모습은 다음과 같습니다.

이미 아주 많은 사람들이 사용하고 있는 오픈소스가 있으니 그걸 활용하도록 하겠습니다.
여기에서 소스 코드를 확인할 수 있으며,
Android Studio에서는 간단히 gradle에 다음 라인만 추가하면 KenBurnsView를 사용할 수 있습니다.
Dependency 설정
dependencies {
compile 'com.flaviofaria:kenburnsview:1.0.7'
}
Layout 코드
실제로 사용할 때는 다음과 같이 XML에 KenburnsView를 배치하기만 하면 됩니다.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.flaviofaria.kenburnsview.KenBurnsView
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/image" />
</RelativeLayout>
특별히 Java 코드를 따로 추가하지 않더라도 훌륭한 Ken Burns 효과를 보여주며, Java 코드를 통해 좀 더 다양하고 정교한 동작을 설정할 수 있습니다.
25 Jan 2017
|
Android
Apache
문제 해결
Apache’s Http Library와
최신 버전의 Android SDK와 충돌이 발생하는 현상이 있습니다. 이럴 경우 다음과 같은 에러가 발생할 것입니다.
Warning:WARNING: Dependency org.apache.httpcomponents:httpclient:4.3.5 is ignored for debug as it may be conflicting with the internal version provided by Android.
그리고 빌드는 경고만 뜨고 잘 되더라도, 실행을 하려고 하면 에러가 뜨면서 실행이 안되는
현상이 생기기도 합니다. 이런 경우에는 app의 build.gradle 파일에 다음과 같은 코드를
삽입해주면 해결이 됩니다.
build.gradle
...
android {
...
packagingOptions {
exclude 'META-INF/NOTICE'
exclude 'META-INF/LICENSE'
}
}
전체 코드는 다음과 같습니다.
apply plugin: 'com.android.application'
android {
compileSdkVersion 25
buildToolsVersion "25.0.0"
defaultConfig {
applicationId "com.datacafe.nestcamapi"
minSdkVersion 16
targetSdkVersion 25
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
packagingOptions {
exclude 'META-INF/NOTICE'
exclude 'META-INF/LICENSE'
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:25.0.1'
testCompile 'junit:junit:4.12'
}
24 Jan 2017
|
Android
Google API
Google Awareness API 사용 예제입니다. 일단 앞서 포스팅한
Google API Key 등록을
먼저 해야 합니다. Google API Key를 획득했다면 다음과 같이 AndroidManifest.xml
에 해당 키 정보를 기입해줍니다.
AndroidManifest.xml
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<meta-data
android:name="com.google.android.awareness.API_KEY"
android:value="API_KEY" />
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
Activity_main.xml
그리고 간단한 버튼 4개를 배치해서 테스트를 하기 위한 activity_main.xml 코드는 다음과 같습니다.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:orientation="vertical">
<Button
android:id="@+id/btn_get_location"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Get Location" />
<Button
android:id="@+id/btn_get_activity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Get Activity" />
<Button
android:id="@+id/btn_get_place"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Get Place" />
<Button
android:id="@+id/btn_get_weather"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Get Weather" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/textview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</ScrollView>
</LinearLayout>
MainActivity.java
마지막으로, MainActivity.java 코드입니다.
package com.datacafe.googleawarenessapi;
import android.content.pm.PackageManager;
import android.location.Location;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import com.google.android.gms.awareness.Awareness;
import com.google.android.gms.awareness.snapshot.DetectedActivityResult;
import com.google.android.gms.awareness.snapshot.LocationResult;
import com.google.android.gms.awareness.snapshot.PlacesResult;
import com.google.android.gms.awareness.snapshot.WeatherResult;
import com.google.android.gms.awareness.state.Weather;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.location.ActivityRecognitionResult;
import com.google.android.gms.location.DetectedActivity;
import com.google.android.gms.location.places.PlaceLikelihood;
import java.util.List;
public class MainActivity extends AppCompatActivity {
//Activities
//public static final int IN_VEHICLE = 0;
//public static final int ON_BICYCLE = 1;
//public static final int ON_FOOT = 2;
//public static final int STILL = 3;
//public static final int UNKNOWN = 4;
//public static final int TILTING = 5;
//public static final int WALKING = 7;
//public static final int RUNNING = 8;
private GoogleApiClient mGoogleApiClient;
private TextView mDebugLogView;
private StringBuilder mLogBuilder = new StringBuilder();
private void log(String message) {
mLogBuilder.append(message);
mLogBuilder.append("\n");
if(mDebugLogView != null) {
mDebugLogView.setText(mLogBuilder.toString());
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mDebugLogView = (TextView) findViewById(R.id.textview);
findViewById(R.id.btn_get_location).setOnClickListener(mOnClickListener);
findViewById(R.id.btn_get_activity).setOnClickListener(mOnClickListener);
findViewById(R.id.btn_get_place).setOnClickListener(mOnClickListener);
findViewById(R.id.btn_get_weather).setOnClickListener(mOnClickListener);
Log.i("snowdeer", "[snowdeer] start...");
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addApi(Awareness.API)
.build();
mGoogleApiClient.connect();
Log.i("snowdeer", "[snowdeer] GoogleApi connect..");
}
private View.OnClickListener mOnClickListener = new View.OnClickListener() {
@Override
public void onClick(View view) {
switch(view.getId()) {
case R.id.btn_get_location: {
if(ContextCompat.checkSelfPermission(
MainActivity.this,
android.Manifest.permission.ACCESS_FINE_LOCATION) !=
PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(
MainActivity.this,
new String[]{android.Manifest.permission.ACCESS_FINE_LOCATION},
100
);
return;
}
Awareness.SnapshotApi.getLocation(mGoogleApiClient)
.setResultCallback(new ResultCallback<LocationResult>() {
@Override
public void onResult(@NonNull LocationResult locationResult) {
if(!locationResult.getStatus().isSuccess()) {
log("Could not get location.");
return;
}
Location location = locationResult.getLocation();
log("Lat: " + location.getLatitude()
+ ", Lon: " + location.getLongitude());
}
});
break;
}
case R.id.btn_get_activity:
Awareness.SnapshotApi.getDetectedActivity(mGoogleApiClient)
.setResultCallback(new ResultCallback<DetectedActivityResult>() {
@Override
public void onResult(@NonNull DetectedActivityResult detectedActivityResult) {
if(!detectedActivityResult.getStatus().isSuccess()) {
log("Could not get the current activity.");
return;
}
ActivityRecognitionResult ar = detectedActivityResult
.getActivityRecognitionResult();
DetectedActivity probableActivity = ar.getMostProbableActivity();
log(probableActivity.toString());
}
});
break;
case R.id.btn_get_place: {
if(ContextCompat.checkSelfPermission(
MainActivity.this,
android.Manifest.permission.ACCESS_FINE_LOCATION) !=
PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(
MainActivity.this,
new String[]{android.Manifest.permission.ACCESS_FINE_LOCATION},
100
);
return;
}
Awareness.SnapshotApi.getPlaces(mGoogleApiClient)
.setResultCallback(new ResultCallback<PlacesResult>() {
@Override
public void onResult(@NonNull PlacesResult placesResult) {
if(!placesResult.getStatus().isSuccess()) {
log("Could not get places.");
return;
}
List<PlaceLikelihood> placeLikelihoodList = placesResult.getPlaceLikelihoods();
if(placeLikelihoodList == null) {
log("Result List is Null!!");
return;
}
// Show the top 5 possible location results.
for(int i = 0; i < placeLikelihoodList.size(); i++) {
PlaceLikelihood p = placeLikelihoodList.get(i);
log(p.getPlace().getName().toString()
+ ", likelihood: " + p.getLikelihood());
}
}
});
break;
}
case R.id.btn_get_weather: {
if(ContextCompat.checkSelfPermission(
MainActivity.this,
android.Manifest.permission.ACCESS_FINE_LOCATION) !=
PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(
MainActivity.this,
new String[]{android.Manifest.permission.ACCESS_FINE_LOCATION},
100
);
return;
}
Awareness.SnapshotApi.getWeather(mGoogleApiClient)
.setResultCallback(new ResultCallback<WeatherResult>() {
@Override
public void onResult(@NonNull WeatherResult weatherResult) {
if(!weatherResult.getStatus().isSuccess()) {
log("Could not get weather.");
return;
}
Weather weather = weatherResult.getWeather();
log("Weather: " + weather);
}
});
break;
}
}
}
};
}