August 12, 2012
Moving to a new domain
Hi,
Finally I have my own domain: www.leonardofischer.com. That is why I am moving to a new address. And this blog is moving also. If you usually follow my blog using a RSS reader, please update it with the new feeds URL.
I will add a link to each blog post to its new URL in my new blog. Also, I will block comments on my posts here, so if you have any questions for a given post, please go to my new site and leave a comment there.
Thank you for your feedback until now. And keep going with me on my new home. ツ
July 9, 2012
Introducing JSS: Java Simple Services
UPDATE: I've moved my whole blog to a new domain. That's why the comments section is closed here. The new URL for this post is http://www.leonardofischer.com/introducing-jss-java-simple-services/. If you have any question, post it there.
A few weeks ago, I needed to implement a service. Or a daemon. Or whatever you call a process that you start, and it runs in background until you call it to stop. I found the Java Service Wrapper, but I didn't like it: it has several OS dependent distributions, and a documentation that you take some time to learn. Other options include some forums recommending some scripts that depend on the operating system that allows you to run a java application in background. No one seems as easy as I thought that it could be.
So I decided to take the joy of writing my own service wrapper. I called it Java Simple Services. I focused on making it simple, small and only dependent on the JDK. The result is a small set of classes that manages the service for the user, making it relatively easy to start a background process. First, I'll present how to use it. If you're interested on how it works, just keep reading.
The Clock Application Example
I like the 'learn by example' approach, so I will present a very simple clock application. When started, it will run "forever", or until you give it a signal to stop. At each loop, it will update the last time read. You can think of it as a much more complex process, such as a web server or a database system that should run in background mode. It will serve as an example for the service that I will build using Java Simple Services.
package com.leonardofischer.jss.test; import java.util.Date; public class ClockApp { public boolean keepRunning = true; public String date = "Not executed yet"; public void run() { try { while (keepRunning) { date = (new Date()).toString(); System.out.println(date); Thread.sleep(1000); } } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { ClockApp clock = new ClockApp(); clock.run(); } }
The Service Class: Creating the Clock Service
I tried to build an API as simple as possible to create services. After some work, I got an API design that requires you to implement only one abstract class and that is it.
In the Java Simple Services, you need to extend the Service class. Although it has several methods you can override, you only need to know three methods:
start(String args[])
, stop(String args[])
and parseArgs(String args[])
. You can think this way:- The
start(String args[])
method is themain(String args[])
method of your service. You will put your code here, and you should return from it only after you processing is done or your service must stop; - The
stop(String args[])
method is where you put the code to stop your application. Although it can take as long as you need, you should use this method to only send a signal to your code in thestart(String args[])
method to make it stop running, and then return. Note that this method will be executed in a different thread from the one wherestart(String args[])
is running, so you may need some synchronization here; - Finally, the
parseArgs(String args[])
will take your command line arguments and will start, stop and do other things with your service.
package com.leonardofischer.jss.test; import com.leonardofischer.jss.Service; public class ClockService extends Service { ClockApp clock; public void start(String[] args) { clock = new ClockApp(); clock.run(); } public void stop(String[] args) { clock.keepRunning = false; } public static void main(String[] args) { ClockService clockService = new ClockService(); clockService.parseArgs(args); } }
Using Java Simple Services and the code above, you have an application that runs in background, can be started, and stopped and so on... Let's run it:
$> java com.leonardofischer.jss.test.ClockService Usage: java com.leonardofischer.jss.test.ClockService {start|stop|restart|sta tus} $> java com.leonardofischer.jss.test.ClockService start The service started $> java com.leonardofischer.jss.test.ClockService start The service is already running, or another process is using the port 6400. $> java com.leonardofischer.jss.test.ClockService status STATUS: the service is running $> java com.leonardofischer.jss.test.ClockService restart The service stoped The service started $> java com.leonardofischer.jss.test.ClockService status STATUS: the service is running $> java com.leonardofischer.jss.test.ClockService stop The service stoped $> java com.leonardofischer.jss.test.ClockService stop The service is not running $>
As you can see, it is very easy to execute, get status and stop the
ClockApp
in a background process. The commands above are already built into JSS, so all the code you needed was the new ClockService
class. But how you can change those generic default messages? How to return the current time using the status command? All that can be changed using service configuration.Service Customization
The code above may be too simple for what you need, so I developed several methods in the service class that, if you override, should allow you to customize your service. I'll present some methods, but please give a look at the class Documentation, so you can know exactly what you can do here.
public class ClockService extends Service { // same code as above, plus these methods public String status(String[] args) { return clock.date; } public void onServiceNotRunning() { printErrorMessage("The ClockService is not running, "+ "please start it with the 'iniciar' command"); } // new version of the main, with some customization public static void main(String[] args) { ClockService clockService = new ClockService(); // 'iniciar' is the portuguese word for 'start' clockService.setStartCommand("iniciar"); // Go to 'How Java Simple Services Work' if you want to know // why setting a port here clockService.getServiceController().setPort(9876); clockService.parseArgs(args); } }
And if we run again those commands...
$> java com.leonardofischer.jss.test.ClockService Usage: java com.leonardofischer.jss.test.ClockService {iniciar|stop|restart|s tatus} $> java com.leonardofischer.jss.test.ClockService start Usage: java com.leonardofischer.jss.test.ClockService {iniciar|stop|restart|s tatus} $> java com.leonardofischer.jss.test.ClockService iniciar The service started $> java com.leonardofischer.jss.test.ClockService iniciar The service is already running, or another process is using the port 9876. $> java com.leonardofischer.jss.test.ClockService status Wed Jul 11 21:53:51 BRT 2012 $> java com.leonardofischer.jss.test.ClockService restart The service stoped The service started $> java com.leonardofischer.jss.test.ClockService status Wed Jul 11 21:54:19 BRT 2012 $> java com.leonardofischer.jss.test.ClockService stop The service stoped $> java com.leonardofischer.jss.test.ClockService stop The ClockService is not running, please start it with the 'iniciar' command $>
I did not use here all the possible customization. I only wanted to show you some examples, such as returning the last read time with the status command, changing the command string used to start the service and change the error message if the service is not running.
Also, there is a ServiceController class that helps on the service execution. There are some customization points here too, but they are more related to the process that will be executed in background. Read carefully the documentation before use these methods, ok?
How Java Simple Services Work
This is the advanced topic on Java Simple Services, so not everyone needs to read from now on to use it. But I invite you to continue reading.
First of all, your service will run in a different process than the one that you executed it with the start command line parameter. If you use the 'run' parameter, then it will run in this process, but with start, another process will be started with the 'run' parameter, and the process that you started will finish normally. The configuration of the ServiceController class serves to customize how the JVM will be executed here.
When you execute the
run
parameter, the ServiceController also creates a thread that listens for future commands. It uses the Java Socket API, and that is why you can set a port to listen to. This socket will be opened until you return from your start(String args[])
method. Note that this thread will be blocked by the socket waiting for a connection, so it will not take any processor time until you try to connect to the service.If you run a command such as
stop
, status
or restart
, the process that executes this command will connect to the service through the socket. Thus, you need to use the same port that you used to run your service, or you will not be able to connect to it. When the running server receives a command through the socket, it understands it (as well as the command line parameters from the command line) and calls the corresponding service method.That's it!
That's all folks. I hope that Java Simple Services can be as helpful to you as it was for me. I am releasing the full source code, and I invite you to submit any bug fix or feature that you developed for it. Or just leave a comment with your thoughts about it.If you are interested in download Java Simple Services go to Git Hub, I'm pushing updates there. You can also download the latest build, so you can try it.
Update 1: I did several code updates days after publishing this post, so I did updated the output shown as well as some complement text to it.
UPDATE: I've moved my whole blog to a new domain. That's why the comments section is closed here. The new URL for this post is http://www.leonardofischer.com/introducing-jss-java-simple-services/. If you have any question, post it there.
January 30, 2012
Hosting Android Widgets - My AppWidgetHost Tutorial
UPDATE: I've moved my whole blog to a new domain. That's why the comments section is closed here. The new URL for this post is http://www.leonardofischer.com/hosting-android-widgets-my-appwidgethost-tutorial/. If you have any question, post it there.
Hi,
No, this isn't another tutorial on how to create Android Widgets. For this, I recommend you the Android SDK or Google. This post is on how to create a simple app that lets the user add and remove widgets, like the Android Home Screen does.
I decided to write this one because I couldn't find anything on the web saying how to do this. I found how to create this example looking at the Android Home Screen Source Code (AHSSC). So, if you already did this, you may find some variable names similar. You can use this as trails to look yourself on the AHSSC ツ
Initialization
You start by creating two objects. The first is an AppWidgetManager, which will give you the data you need about installed widgets. The second one is an AppWidgetHost, which will keep in memory your widget instances. Latter, your app will handle only the view that will draw the widget.
Selecting the Widget
You start by asking to the AppWidgetHost to allocate resources for a widget instance. It will return an ID for that. Then, you need to start an activity to let the user select which widget he wants to add to your app. You need to give this ID to the activity.
Unfortunately, any kind of software has bugs, and here is one of the Android SDK. The Widget API supports that you merge custom widgets of your application with the installed ones. But if you don't add anything, the Activity that shows the list of widgets to the user crashes with a NullPointerException. The
Configuring the Widget
If the user successfully selects a widget from the list (he didn't pressed "back"), it will return an OK to you as an activity result. The data for this result contains the widget ID. Use it to retrieve the AppWidgetProviderInfo to check if the widget requires any configuration (some widgets does need). If it requires, you need to launch the activity to configure the widget. If not, jump to the next step.
Creating and Adding it to Your Views
Now is time to create the widget itself. You will use the Widget ID and the AppWidgetProviderInfo to ask to the AppWidgetHost "could you please create a view of this widget for me?". It will return an AppWidgetHostView which is a derived class from View. This one you can handle as any other view from the Framework. But don't forget to set the Widget ID and Widget Info on the view (I don't know why the AppWidgetHost didn't when creating the view).
Updating
The widget is now working, but is not being updated by your app. If the widget is a clock, it will be stuck at the time you added it. To register the widget to receive the events it needs, call startListening() on the AppWidgetHost. To avoid wasting battery with unnecessary updates while your app is not visible, call it during the onStart() method of your activity, and call stopListening() during the onStop() method.
Releasing the Widget
The widget should be working now. But if you want to remove the widget, you need to ask to the AppWidgetHost to release it. If you do not release it, you'll get a memory leak (your app will consume unnecessary memory). Finally, remove it from your LayoutView.
Note that the widget ID is also deleted during the
I hope this can help you develop widget based apps. You can download the full source code for this post here or on GitHub. There is also an APK to install on your phone (just make sure you can install it).
UPDATE: I've moved my whole blog to a new domain. That's why the comments section is closed here. The new URL for this post is http://www.leonardofischer.com/hosting-android-widgets-my-appwidgethost-tutorial/. If you have any question, post it there.
Hi,
No, this isn't another tutorial on how to create Android Widgets. For this, I recommend you the Android SDK or Google. This post is on how to create a simple app that lets the user add and remove widgets, like the Android Home Screen does.
I decided to write this one because I couldn't find anything on the web saying how to do this. I found how to create this example looking at the Android Home Screen Source Code (AHSSC). So, if you already did this, you may find some variable names similar. You can use this as trails to look yourself on the AHSSC ツ
Initialization
You start by creating two objects. The first is an AppWidgetManager, which will give you the data you need about installed widgets. The second one is an AppWidgetHost, which will keep in memory your widget instances. Latter, your app will handle only the view that will draw the widget.
mAppWidgetManager = AppWidgetManager.getInstance(this); mAppWidgetHost = new AppWidgetHost(this, R.id.APPWIDGET_HOST_ID);
Selecting the Widget
You start by asking to the AppWidgetHost to allocate resources for a widget instance. It will return an ID for that. Then, you need to start an activity to let the user select which widget he wants to add to your app. You need to give this ID to the activity.
void selectWidget() { int appWidgetId = this.mAppWidgetHost.allocateAppWidgetId(); Intent pickIntent = new Intent(AppWidgetManager.ACTION_APPWIDGET_PICK); pickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); addEmptyData(pickIntent); startActivityForResult(pickIntent, R.id.REQUEST_PICK_APPWIDGET); } void addEmptyData(Intent pickIntent) { ArrayList<AppWidgetProviderInfo> customInfo = new ArrayList<AppWidgetProviderInfo>(); pickIntent.putParcelableArrayListExtra( AppWidgetManager.EXTRA_CUSTOM_INFO, customInfo); ArrayList<Bundle> customExtras = new ArrayList<Bundle>(); pickIntent.putParcelableArrayListExtra( AppWidgetManager.EXTRA_CUSTOM_EXTRAS, customExtras); };
Unfortunately, any kind of software has bugs, and here is one of the Android SDK. The Widget API supports that you merge custom widgets of your application with the installed ones. But if you don't add anything, the Activity that shows the list of widgets to the user crashes with a NullPointerException. The
addEmptyData()
method above adds some dummy data to avoid this bug. More on this bug here. If you want to add a custom widget, start looking at this point of the AHSSC.Configuring the Widget
If the user successfully selects a widget from the list (he didn't pressed "back"), it will return an OK to you as an activity result. The data for this result contains the widget ID. Use it to retrieve the AppWidgetProviderInfo to check if the widget requires any configuration (some widgets does need). If it requires, you need to launch the activity to configure the widget. If not, jump to the next step.
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (resultCode == RESULT_OK ) { if (requestCode == REQUEST_PICK_APPWIDGET) { configureWidget(data); } else if (requestCode == REQUEST_CREATE_APPWIDGET) { createWidget(data); } } else if (resultCode == RESULT_CANCELED && data != null) { int appWidgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1); if (appWidgetId != -1) { mAppWidgetHost.deleteAppWidgetId(appWidgetId); } } } private void configureWidget(Intent data) { Bundle extras = data.getExtras(); int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, -1); AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId); if (appWidgetInfo.configure != null) { Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE); intent.setComponent(appWidgetInfo.configure); intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); startActivityForResult(intent, REQUEST_CREATE_APPWIDGET); } else { createWidget(data); } }
Creating and Adding it to Your Views
Now is time to create the widget itself. You will use the Widget ID and the AppWidgetProviderInfo to ask to the AppWidgetHost "could you please create a view of this widget for me?". It will return an AppWidgetHostView which is a derived class from View. This one you can handle as any other view from the Framework. But don't forget to set the Widget ID and Widget Info on the view (I don't know why the AppWidgetHost didn't when creating the view).
public void createWidget(Intent data) { Bundle extras = data.getExtras(); int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, -1); AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId); AppWidgetHostView hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo); hostView.setAppWidget(appWidgetId, appWidgetInfo); layout.addView(hostView); }
Updating
The widget is now working, but is not being updated by your app. If the widget is a clock, it will be stuck at the time you added it. To register the widget to receive the events it needs, call startListening() on the AppWidgetHost. To avoid wasting battery with unnecessary updates while your app is not visible, call it during the onStart() method of your activity, and call stopListening() during the onStop() method.
@Override protected void onStart() { super.onStart(); mAppWidgetHost.startListening(); } @Override protected void onStop() { super.onStop(); mAppWidgetHost.stopListening(); }
Releasing the Widget
The widget should be working now. But if you want to remove the widget, you need to ask to the AppWidgetHost to release it. If you do not release it, you'll get a memory leak (your app will consume unnecessary memory). Finally, remove it from your LayoutView.
public void removeWidget(AppWidgetHostView hostView) { mAppWidgetHost.deleteAppWidgetId(hostView.getAppWidgetId()); layout.removeView(hostView); }
Note that the widget ID is also deleted during the
onActivityResult()
method if the user gave up selecting the widget. I hope this can help you develop widget based apps. You can download the full source code for this post here or on GitHub. There is also an APK to install on your phone (just make sure you can install it).
UPDATE: I've moved my whole blog to a new domain. That's why the comments section is closed here. The new URL for this post is http://www.leonardofischer.com/hosting-android-widgets-my-appwidgethost-tutorial/. If you have any question, post it there.
January 4, 2012
Blog update: recomendations
UPDATE: I've moved my whole blog to a new domain. That's why the comments section is closed here. The new URL for this post is http://www.leonardofischer.com/blog-update-recomendations/. If you have any question, post it there.
Happy New Year!
Ok, this is just a fast post. I've recently added a new Recomendations page. My intention with it is to share some of the feeds from blogs and news that I found to be valuable and have followed for some time. That page should be in constant update. Well, not so "constant", but from time to time I'll update it with new content.
Also, I've added a list of some blogs from friends. Although these blogs are also in my list of recommendations, I've put then on the right side of the blog (instead of the Recomendations page) to enforce how much I recommend then.
And, again, have a happy new year!
PS.: Thank you, @lfzawacki for reminding me to do this. I pushed this task for weeks... ツ
UPDATE: I've moved my whole blog to a new domain. That's why the comments section is closed here. The new URL for this post is http://www.leonardofischer.com/blog-update-recomendations/. If you have any question, post it there.
Tags:
About
Location:
Porto Alegre - Rio Grande do Sul, Brazil
Subscribe to:
Posts (Atom)