Search Engine
Saturday, July 31, 2010
Models and Eclipse EMF
Persisting EMF models via XMI
2.1. Overview
EMF allows to store the model content via the EMF persistence framework. EMF provides XMI and XML persistence provider. By default EMF uses XMI (XML Metadata Interchange). XMI is a standard for exchanging metadata information via Extensible Markup Language (XML).
The following demonstrates how you create a EMF model instance, save it and load it again.
The following is based on the EMF model created in Eclipse EMF Tutorials. Create a new plugin project "de.vogella.emf.webpage.instance". Add the following dependency to your "plugin.xml".
- org.eclipse.emf.ecore
- org.eclipse.emf.ecore.xmi
Tip
Objects which do not have a "contains relationship" must be added explicitly to the resource.getContents().add(). If objects are not added and not included in a contains relationship an exception is thrown when calling resource.save()
package writeWebpage;
import java.io.IOException;
import java.util.Collections;
import java.util.Map;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl;
import datamodel.website.MyWeb;
import datamodel.website.Webpage;
import datamodel.website.WebsiteFactory;
import datamodel.website.WebsitePackage;
import datamodel.website.impl.WebsitePackageImpl;
public class CreateSaveTester {
/**
* @param args
*/
public static void main(String[] args) {
// Initialize the model
WebsitePackage.eINSTANCE.eClass();
// Retrieve the default factory singleton
WebsiteFactory factory = WebsiteFactory.eINSTANCE;
// Create the content of the model via this program
MyWeb myWeb = factory.createMyWeb();
Webpage page = factory.createWebpage();
page.setName("index");
page.setDescription("Main webpage");
page.setKeywords("Eclipse, EMF");
page.setTitle("Eclipse EMF");
myWeb.getPages().add(page);
// As of here we preparing to save the model content
// Register the XMI resource factory for the .website extension
Resource.Factory.Registry reg = Resource.Factory.Registry.INSTANCE;
Mapm = reg.getExtensionToFactoryMap();
m.put("website", new XMIResourceFactoryImpl());
// Obtain a new resource set
ResourceSet resSet = new ResourceSetImpl();
// Create a resource
Resource resource = resSet.createResource(URI
.createURI("website/My2.website"));
// Get the first model element and cast it to the right type, in my
// example everything is hierarchical included in this first node
resource.getContents().add(myWeb);
// Now save the content.
try {
resource.save(Collections.EMPTY_MAP);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
The following coding can be used to load an existing model.
package writeWebpage;
import java.util.Map;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl;
import datamodel.website.MyWeb;
import datamodel.website.WebsitePackage;
import datamodel.website.impl.WebsitePackageImpl;
public class EMFModelLoad {
public MyWeb load() {
// Initialize the model
WebsitePackage.eINSTANCE.eClass();
// Register the XMI resource factory for the .website extension
Resource.Factory.Registry reg = Resource.Factory.Registry.INSTANCE;
Mapm = reg.getExtensionToFactoryMap();
m.put("website", new XMIResourceFactoryImpl());
// Obtain a new resource set
ResourceSet resSet = new ResourceSetImpl();
// Get the resource
Resource resource = resSet.getResource(URI
.createURI("website/My.website"), true);
// Get the first model element and cast it to the right type, in my
// example everything is hierarchical included in this first node
MyWeb myWeb = (MyWeb) resource.getContents().get(0);
return myWeb;
}
}
You can then access the model content via standard Java coding.
package writeWebpage;
import java.util.Iterator;
import datamodel.website.MyWeb;
import datamodel.website.Webpage;
public class LoadTest {
/**
* @param args
*/
public static void main(String[] args) {
// Loading the existing model
EMFModelLoad loader = new EMFModelLoad();
MyWeb myWeb = loader.load();
// Accessing the model information
System.out.println(myWeb.getDescription());
System.out.println(myWeb.getTitle());
// Lets see what info the webpage has
for (Iteratoriterator = myWeb.getPages().iterator(); iterator
.hasNext();) {
Webpage page = iterator.next();
System.out.println("Name : " + page.getName());
// We could also iterate over the Articles...
}
}
}
Appendix: Encryption
EMF has the possibility to encrypt the data model before writing it and to decrypt it before loading. The following demonstrates this.
Create an model based on the following interface.
package mymodel;
import org.eclipse.emf.ecore.EObject;
/**
* @model
*/
public interface IPerson extends EObject {
/**
* @model default="";
*/
public String getLastname();
}
Create the following factory which sets the option for encryption.
package factory;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.impl.AESCipherImpl;
import org.eclipse.emf.ecore.xmi.XMIResource;
import org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl;
public class MyXMIFactoryImpl extends XMIResourceFactoryImpl {
@Override
public Resource createResource(URI uri) {
XMIResourceFactoryImpl resFactory = new XMIResourceFactoryImpl();
XMIResource resource = (XMIResource) resFactory.createResource(uri);
try {
resource.getDefaultLoadOptions().put(Resource.OPTION_CIPHER,
new AESCipherImpl("12345"));
resource.getDefaultSaveOptions().put(Resource.OPTION_CIPHER,
new AESCipherImpl("12345"));
} catch (Exception e) {
e.printStackTrace();
}
return resource;
}
}
Create the following test class.
package load;
import java.io.IOException;
import java.util.Collections;
import java.util.Map;
import mymodel.IPerson;
import mymodel.MymodelFactory;
import mymodel.impl.MymodelPackageImpl;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import factory.MyXMIFactoryImpl;
public class Create {
public void create() {
MymodelPackageImpl.init();
// Retrieve the default factory singleton
MymodelFactory factory = MymodelFactory.eINSTANCE;
// Create the content of the model via this program
IPerson person = factory.createIPerson();
person.setLastname("Lars");
// Register the XMI resource factory for the .website extension
Resource.Factory.Registry reg = Resource.Factory.Registry.INSTANCE;
Mapm = reg.getExtensionToFactoryMap();
m.put("person", new MyXMIFactoryImpl());
// Obtain a new resource set
ResourceSet resSet = new ResourceSetImpl();
// Create a resource
Resource resource = resSet.createResource(URI
.createURI("mymodel.person"));
resource.getContents().add(person);
// Now save the content.
try {
resource.save(Collections.EMPTY_MAP);
} catch (IOException e) {
e.printStackTrace();
}
}
public void load() {
// Initialize the model
MymodelPackageImpl.init();
// Register the XMI resource factory for the .website extension
Resource.Factory.Registry reg = Resource.Factory.Registry.INSTANCE;
Mapm = reg.getExtensionToFactoryMap();
m.put("person", new MyXMIFactoryImpl());
ResourceSet resSet = new ResourceSetImpl();
Resource resource = resSet.getResource(URI
.createURI("mymodel.person"), true);
try {
IPerson person= (IPerson) resource.getContents().get(0);
System.out.println(person.getLastname());
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args){
Create test = new Create();
test.create();
test.load();
}
}
Thank you
Thank you for practicing with this tutorial.
I maintain this tutorial in my private time. If you like the information please help me by donating or bySunday, July 25, 2010
Building Open Source libraries with Android NDK
Having scrambled through a NDK documentation and a lot of hit and trials and experimentation, finally I could figure out how could one build (even though partially) Open Source libraries with Android NDK.
Some background on how Autotools work:
The way autootool and friends work is this:
You write a configure.ac file, which is read in by Autoreconf and generates a configure script. Configure script test the build system for headers, libraries etc. and generates a config.h which contains definitions like #define HAVE_XXX 1 and also generates Makefiles after reading in the Makefile.in files which in turn is generated using Automake by reading in Makefile.am.
configure.ac —> input to –> autoreconf –> generates -> configure script –> checks host system for headers, libraries etc. -> generates -> config.hAlso
Makefile.am –> input to automake –> generates –> Makefile.in –> input to –> configure script –> generates MakefileThe code uses the generated config.h in the following fashion:
#ifdef HAVE_XXXX #include#else Provide some other mechanism or report error to user or do whatever you want #endif
The problem?
Most Open Source libraries use GNU autotools and its friends for building.
The first problem is that the Autools generate some configuration headers based on build time probe of the system. By build time probe I mean checking for things like if a header, library or a tool is present or not. In cross compiling scenario some of these probe should be done on the target system and not on the build system.
Second, the build system for most cross compiler tools have their own quirks which need passing some extra flags.
Third, in case of Android, it provides its own build system which are essentially some Makefile definitions and rules. This is provided so that people can build their code easily without having to deal with usual cross compiling issues. Thus there is a gap that while your autotools would generate the Makefiles while Android build system requires its own styled Makefiles in the form of Android.mk. One cannot simply generate Android.mk files using autotools.
Fourth, even if one gets to write Android specific Makefiles, the build would most probably fail as during the build it would look for a file config.h included in the fashion shown below, while no such file would exist as it is generated by the configure script.
#ifdef HAVE_CONFIG_H #include “config.h” #endifSimply copying a config.h file from a run of configure script on another build system wouldn’t really work as the header files and other libraries present on Android may not match with the header and libraries present on the build system. Thus config.h would probably differ if it is somehow generated for Android with the one generated on a build system.
So how does one build an open source library for Android?
Solution:
The way I have managed to work around this trouble is to run the configure script with right cross compilation variables so that a config.h matching my Android system gets built and then writing Android.mk files which would simply use the Android build system.
The way I figured out the right flags was by building the Android source tree which displayed what flags are being used for building and then taking the cues from there, I passed the right flags to the configure script.
It looks something like this for building Android-3 target API on a linux host:
export ANDROID_ROOT=/home/divkis01/mydroid
The command above is sets the path where the Android sources are checked out from git respository.
NOTE: It is not necessary to check out the ANDROID sources and you can replace the ANDROID_ROOT with NDK_ROOT in all the commands below, along with proper path to the NDK cross compiler.
The command above is necessary so that configure can find out the path to the cross compiler which is needed to build some test programs during the configure run process. Please note that you can also set the path to the NDK compiler root
The command above has several points that should be well understood.
–host=arm-eabi –> This tells the configure script if the cross compilation is being done or not. It is also used as a prefix to some of the cross compiler tools like strip, nm etc.
CC=arm-eabi-gcc –> This tells the compiler that should be used for building
CPPFLAGS –> This tells the location where the header files should be searched which were specified in configure.ac with AC_CHECK_HEADER macro
CFLAGS=”-nostdlib” passes the option to build some test programs during configure process run. If you don’t pass this the compiler would link the standard C library of the host system which wouldn’t be compatible with the C library of the target system. You will end up getting error something like this, if you don’t pass this option:
/home/divkis01/mydroid/prebuilt/linux-x86/toolchain/arm-eabi-4.2.1/bin/../lib/gcc/arm-eabi/4.2.1/../../../../arm-eabi/bin/ld: crt0.o: No such file: No such file or directoryLIBS=”-lc” –> This option tells that it should explicitly link to a library called libc.so which is present in the location specified using the -L in the LDFLAGS option. If you are wondering that usually to build a C executable one doesn’t need to provide -lc as libc is automatically linked, then why do we need to specify this here? The answer lies in -nostdlib flag, which instructs not to link with the standard C library on the build system.
You will end up getting error something like this, if you don’t pass this option:
/home/divkis01/mydroid/prebuilt/linux-x86/toolchain/arm-eabi-4.2.1/bin/../lib/gcc/arm-eabi/4.2.1/../../../../arm-eabi/bin/ld: crt0.o: No such file: No such file or directorycollect2: ld returned 1 exit status
LDFLAGS = –> This option is also passed to build some test programs during configure process run.If you don’t pass the -Wl,-rpath-link option, then linker does not know where do the libraries dependent on the library specific using LIBS reside. If you don’t pass the -L option then the linker doesn’t know where do the libraries specified in LIBS reside.
You will end up getting error something like this, if you don’t pass the -Wl,-rpath-link option:
/home/divkis01/mydroid/prebuilt/linux-x86/toolchain/arm-eabi-4.2.1/bin/../lib/gcc/arm-eabi/4.2.1/../../../../arm-eabi/bin/ld: warning: libdl.so, needed by /home/divkis01/mydroid/development/ndk/build/platforms/android-3/arch-arm/usr/lib//libc.so, not found (try using -rpath or -rpath-link)/home/divkis01/mydroid/prebuilt/linux-x86/toolchain/arm-eabi-4.2.1/bin/../lib/gcc/arm-eabi/4.2.1/../../../../arm-eabi/bin/ld: warning: cannot find entry symbol _start; defaulting to 00008184
/home/divkis01/mydroid/development/ndk/build/platforms/android-3/arch-arm/usr/lib//libc.so: undefined reference to `dl_unwind_find_exidx’
You will end up getting error something like this, if you don’t pass the -L option:
/home/divkis01/mydroid/prebuilt/linux-x86/toolchain/arm-eabi-4.2.1/bin/../lib/gcc/arm-eabi/4.2.1/../../../../arm- eabi/bin/ld: cannot find -lc collect2: ld returned 1 exit statusOnce you run the configure script with these flags and options, it will generate the appropriate config.h which is compatible / in sync with your target system. Now you can go ahead and start writing the Android.mk files to build your sources.
Other troubles:
This solves only part of the problem as Autotools not only help in building but also in installing. Given it doesn’t make sense to install the build for a target system on host except for the headers. This can be done by augmenting the Android.mk files or writing some shell scripts to do this manually.
Conclusion:
Autotool is good only on GNU systems and using it for cross compiling can be really tedious, confusing, error prone or even impossible. The method described here is a hack and should be used at your own risk.
Let me know if this post was helpful for you.
Android MediaPlayer
First of all, copy a mp3 file in /res/raw folder.
main.xml
Click This Image And See In Large View
AndroidMediaPlayer.java
package com.exercise.AndroidMediaPlayer;
import android.app.Activity;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class AndroidMediaPlayer extends Activity {
MediaPlayer mediaPlayer;
Button buttonPlayPause, buttonQuit;
TextView textState;
private int stateMediaPlayer;
private final int stateMP_NotStarter = 0;
private final int stateMP_Playing = 1;
private final int stateMP_Pausing = 2;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
buttonPlayPause = (Button)findViewById(R.id.playpause);
buttonQuit = (Button)findViewById(R.id.quit);
textState = (TextView)findViewById(R.id.state);
buttonPlayPause.setOnClickListener(buttonPlayPauseOnClickListener);
buttonQuit.setOnClickListener(buttonQuitOnClickListener);
initMediaPlayer();
}
private void initMediaPlayer()
{
mediaPlayer = new MediaPlayer();
mediaPlayer = MediaPlayer.create(AndroidMediaPlayer.this, R.raw.music);
stateMediaPlayer = stateMP_NotStarter;
textState.setText("- IDLE -");
}
Button.OnClickListener buttonPlayPauseOnClickListener
= new Button.OnClickListener(){
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
switch(stateMediaPlayer){
case stateMP_NotStarter:
mediaPlayer.start();
buttonPlayPause.setText("Pause");
textState.setText("- PLAYING -");
stateMediaPlayer = stateMP_Playing;
break;
case stateMP_Playing:
mediaPlayer.pause();
buttonPlayPause.setText("Play");
textState.setText("- PAUSING -");
stateMediaPlayer = stateMP_Pausing;
break;
case stateMP_Pausing:
mediaPlayer.start();
buttonPlayPause.setText("Pause");
textState.setText("- PLAYING -");
stateMediaPlayer = stateMP_Playing;
break;
}
}
};
Button.OnClickListener buttonQuitOnClickListener
= new Button.OnClickListener(){
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
mediaPlayer.stop();
mediaPlayer.release();
finish();
}
};
}
Thursday, July 22, 2010
Models and Eclipse EMF
1.1. Data model
A data model, sometimes also called domain model, represents the data you want to work with. For example if you develop an online flight booking application you might model your domain model with objects like "Person", "Flight", "Booking" etc. The general recommendation is to model your data model independent of the application logic. This approach leads to classes with almost no logic and a lot of properties, e.g. Person would have the properties "firstName", "lastName", "Address", etc.
Eclipse EMF can be used to model your domain model. EMF has a distinction between the meta-model and the actual model. The meta-model describes the structure of the model. A model is then the instance of this meta-model.
EMF uses XMI (XML Metadata Interchange) to persists the model definition.
This EMF meta-model definition can be defined based on:
-
A XMI document, using an XML or text editor
-
Java annotations
-
UML
-
XML Schema
Once the EMF meta-model is specified you can generate the corresponding Java implementations classes from this model. EMF provides the possibility that the generated code can be safely extended by hand.
EMF is based on two meta-models; the Ecore and the Genmodel model. The Ecore metamodel contains the information about the defined classes.
-
EClass : represents a class, with zero or more attributes and zero or more references.
-
EAttribute : represents an attribute which has a name and a type.
-
EReference : represents one end of an association between two classes. It has flag to indicate if it represent a containment and a reference class to which it points.
-
EDataType : represents the type of an attribute, e.g. int, float or java.util.Date
The Genmodel contains additional information for the codegeneration, e.g. the path and file information. The genmodel contains also the control parameter how the coding should be generated.
The Ecore model shows a root object representing the whole model. This model has children which represents the packages, whose children represents the classes, while the children of the classes represents the attributes of these classes. EMF's meta model Ecore (roughly) corresponds to the EMOF (Essential MOF) subset of the MOF 2.0 standard.
With EMF you make your domain model explicit which helps to provide clear visibility of the model. EMF also provides change notification functionality to the model in case of model changes. EMF will generate interfaces and factory to create your objects; therefore it helps you to keep your application clean from the individual implementaiton classes.
Another advantages is that you can regenerate the Java code from the model at any point in time.
Installation
Install EMF via the Eclipse update manager . Select "Modeling" and install "EMF - Eclipse Modeling Framework SDK". Also select the "Ecore Tools"; these will allow you to create UML diagrams from your model.
Define EMF model
3.1. Create project
Create a new project "sawan.modi.emf.webpage.model" via File / New / Project... / Eclipse Modeling Framework /Empty EMF project
Select the folder "model". Create a Ecore diagram via right click on the folder "model" folder and select New / Other... / Ecore Tools / Ecore Diagram
Name your "Domain File Name" "webpage.ecore".
This should open a visual editor for creating EMF models.
Open the properties view via Window -> Show View -> Other -> Properties. This view will allow you to modify the attributes of your model elements.
Click on EClass and click into the editor to create the class.
Create the classes "MyWeb", "Webpage", "Category" and "Article".
Using the EAttribute node assign to each object the atttribute "name" of String "EString".
Add the attributes "title", "description" and "keywords" to "MyWeb" and "Webpage".
Select EReferences and create an arrow similar to the following picture. Make sure the upper bound is set to "*" and that the "Is Containment" property is flagged.
Close the dialog and open the file "webpage.ecore". The result should look like the following.
Tip
Right click on any element and select "Show Properties View" to see the additional attributes of the node.Select your "webpage.ecore" -> Select File -> New -> Other -> EMF Generator model and create "webpage.genmodel" based on your "Ecore model".
Select your model and press load.
Generating the domain classes
4.1. Generating Java code
You have created two models, the Ecore and the Genmodel model.
Once your model definition is complete you can generate Java code from it. Right-click on the root node of the genmodel and select "Generate Model Code".
This will create the Java implementation of the EMF model in the current project.
The generated code will consists out of the following:
-
model -- Interfaces and the Factory to create the Java classes
-
model.impl -- Concrete implementation of the interfaces defined in model
-
model.util -- The AdapterFactory
The central factory has methods for creating all defined objects via createObjectName() methods.
For each attribute the generated interface and its implementation contains getter and (if allowed in the model definition) setter methods.
Each interface extends the base interface EObject. EObject is the base of every EMF class and is the EMF equivalent of java.lang.Object. EObject and its corresponding implementation class EObjectImpl provide a lightweight base class that lets the generated interfaces and classes participate in the EMF notification and persistence frameworks.
Each setter has also a generated notification to observers of the model. This mean that other object can attach them to the model and react to changes in the model.
Every generated method is tagged with @generated. If you want to manually adjust the method you want to prevent that EMF overwrites the method during the next generation run you have to remove this tag.
Create EMF Editor plugins
EMF can generate plugins which provide wizards for creating new model instances and also an editor for you which allows you to maintain your model information. For more information about Eclipse plugin development please see Eclipse Plugin Development Tutorials.
Eclipse EMF allow you to create a editor for your model. Select your genmodel, right click on it and select "Create Edit Code" and then "Create Editor Code".
Two Eclipse Plugin projects have been created, "sawan.modi.emf.webpage.model.edit" and "sawan.modi.emf.webpage.model.editor".
Select the *.editor project and run the new plugin. Selecting the "plugin.xml", select the "Overview" tab and press then "Launch an Eclipse application". See Eclipse Plugin Tutorials for details.
This should start a new Eclipse runtime instance.
In new Eclipse instance create a new project "testing" and a new folder "website" ( Select this folder, right click on it, select New-> Other-> Example EMF Model Creation Wizards -> Webpage Model.
Name it "My.webpage".
Select as the Model Object "My Web" and press finish.
Using the model code
6.1. Overview
The generated model code is standard Java code and can be used as such. The following demonstrates how you create objects based on the generated code.
Create a new plugin project "sawan.modi.emf.webpage.usingmodel". Add the following dependency to your "plugin.xml".
- org.eclipse.emf.ecore
- sawan.modi.emf.webpage.model
Create the following class.
package sawan.modi.emf.webpage.usingmodel;
import sawan.modi.emf.webpage.model.webpage.MyWeb;
import sawan.modi.emf.webpage.model.webpage.Webpage;
import sawan.modi.emf.webpage.model.webpage.WebpageFactory;
import sawan.modi.emf.webpage.model.webpage.impl.WebpagePackageImpl;
public class UsingEMFModel {
public static void main(String[] args) {
WebpagePackageImpl.init();
// Retrieve the default factory singleton
WebpageFactory factory = WebpageFactory .eINSTANCE;
// Create an instance of myWeb
MyWeb myWeb = factory.createMyWeb();
myWeb.setName("Hallo");
myWeb.setDescription("This is a description");
// Create a page
Webpage webpage = factory.createWebpage();
webpage.setTitle("This is a title");
// Add the page to myWeb
myWeb.getPages().add(webpage);
// and so on, and so on
// as you can see the EMF model can be (more or less) used as standard Java
}
}
Extending an EMF Ecore model (inheritance)
7.1. Overview
EMF allows to define base models and extend them. The following will demonstrate this. It will also demonstrate how to work with EMF ecore models directly without using the ecore tools.
Create a new EMF project "sawan.modi.emf.inheritance". Create a new model by selecting File -> New -> "Eclipse Modeling Framework" -> "Ecore Model". Name the model "base.ecore".
Select "EPackage" as the basis and maintain the following properties for this package.
Right click on the package and select New Child -> EClass. Maintain the class "MyBaseClass" with two "EAttributes" of type "EString".
Create a new "Ecore" model "extendedmodel.ecore". Maintain "extended" as the package name. Right-click your model and select "Load resource".
Create a new class "MyExtendedClass" and press "ESuperType".
Add your "MyBaseClass".
Maintain a new EAtribute "detailedField" on "MyExtendedClass".
Create a new genmodel "extended.genmodel" based on extended.ecore. Generated Java code and you will see that the "MyExtendedClass" has extended "MyBaseClass".
Thank you
Thank you for practicing with this tutorial.
I maintain this tutorial in my private time. If you like the information please help me by donating or byTuesday, July 20, 2010
Rotate bitmap image, using Matrix
Here Rotating function is going to be added on the last exercise "Skew Bitmap Image Using Matrix" It's a SeekBar on the screen to change the degree to rotate the bitmap, by using of Matrix.postRotate().
main.xml
If U See This XML File Click On This Image.
AndroidBitmap.java
package com.exercise.AndroidBitmap;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.Spinner;
public class AndroidBitmap extends Activity {
private final String imageInSD = "/sdcard/er.PNG";
ImageView myImageView;
Spinner spinnerScale;
SeekBar seekbarRotate;
private static final String[] strScale
= {"0.2x", "0.5x", "1.0x", "2.0x", "5.0x"};
private static final Float[] floatScale
= {0.2F, 0.5F, 1F, 2F, 5F};
private final int defaultSpinnerScaleSelection = 2;
private ArrayAdapter adapterScale;
private float curScale = 1F;
private float curRotate = 0F;
Bitmap bitmap;
int bmpWidth, bmpHeight;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
myImageView = (ImageView)findViewById(R.id.imageview);
spinnerScale = (Spinner)findViewById(R.id.scale);
seekbarRotate = (SeekBar)findViewById(R.id.rotate);
adapterScale = new ArrayAdapter(this,
android.R.layout.simple_spinner_item, strScale);
adapterScale.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinnerScale.setAdapter(adapterScale);
spinnerScale.setSelection(defaultSpinnerScaleSelection);
curScale = floatScale[defaultSpinnerScaleSelection];
bitmap = BitmapFactory.decodeFile(imageInSD);
bmpWidth = bitmap.getWidth();
bmpHeight = bitmap.getHeight();
drawMatrix();
spinnerScale.setOnItemSelectedListener(spinnerScaleOnItemSelectedListener);
seekbarRotate.setOnSeekBarChangeListener(seekbarRotateSeekBarChangeListener);
}
private void drawMatrix(){
Matrix matrix = new Matrix();
matrix.postScale(curScale, curScale);
matrix.postRotate(curRotate);
Bitmap resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0, bmpWidth, bmpHeight, matrix, true);
myImageView.setImageBitmap(resizedBitmap);
}
private SeekBar.OnSeekBarChangeListener seekbarRotateSeekBarChangeListener
= new SeekBar.OnSeekBarChangeListener(){
@Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
// TODO Auto-generated method stub
curRotate = (float)progress;
drawMatrix();
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
// TODO Auto-generated method stub
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
// TODO Auto-generated method stub
}};
private Spinner.OnItemSelectedListener spinnerScaleOnItemSelectedListener
= new Spinner.OnItemSelectedListener(){
@Override
public void onItemSelected(AdapterView arg0, View arg1,
int arg2, long arg3) {
// TODO Auto-generated method stub
curScale = floatScale[arg2];
drawMatrix();
}
@Override
public void onNothingSelected(AdapterView arg0) {
// TODO Auto-generated method stub
spinnerScale.setSelection(defaultSpinnerScaleSelection);
curScale = floatScale[defaultSpinnerScaleSelection];
}};
}
Skew bitmap image, using Matrix
To generate a skewed bitmap, Matrix.postSkew() can be used.
Modify main.xml to add two SeekBar to set the skew for x and y.
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello"
/>
android:id="@+id/scale"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
android:id="@+id/rotate"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_margin="5px"
android:max="360"
android:progress="0"
/>
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
>
android:orientation="vertical"
android:layout_height="wrap_content"
android:layout_width="0dip"
android:layout_weight="1"
android:layout_margin="5px"
>
android:id="@+id/textskewx"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:textSize="10px"
android:text="Skew-X: 0"
/>
android:id="@+id/skewx"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:max="200"
android:progress="100"
/>
android:orientation="vertical"
android:layout_height="wrap_content"
android:layout_width="0dip"
android:layout_weight="1"
android:layout_margin="5px"
>
android:id="@+id/textskewy"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:textSize="10px"
android:text="Skew-Y: 0"
/>
android:id="@+id/skewy"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:max="200"
android:progress="100"
/>
android:id="@+id/imageview"
android:layout_gravity="center"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:scaleType="center"
/>
AndroidBitmap.java
package com.exercise.AndroidBitmap;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.Spinner;
import android.widget.TextView;
public class AndroidBitmap extends Activity {
private final String imageInSD = "/sdcard/er.PNG";
ImageView myImageView;
Spinner spinnerScale;
SeekBar seekbarRotate;
SeekBar seekbarSkewX, seekbarSkewY;
TextView textSkewX, textSkewY;
private static final String[] strScale
= {"0.2x", "0.5x", "1.0x", "2.0x", "5.0x"};
private static final Float[] floatScale
= {0.2F, 0.5F, 1F, 2F, 5F};
private final int defaultSpinnerScaleSelection = 2;
private ArrayAdapter adapterScale;
private float curScale = 1F;
private float curRotate = 0F;
private float curSkewX = 0F;
private float curSkewY = 0F;
Bitmap bitmap;
int bmpWidth, bmpHeight;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
myImageView = (ImageView)findViewById(R.id.imageview);
spinnerScale = (Spinner)findViewById(R.id.scale);
seekbarRotate = (SeekBar)findViewById(R.id.rotate);
seekbarSkewX = (SeekBar)findViewById(R.id.skewx);
seekbarSkewY = (SeekBar)findViewById(R.id.skewy);
textSkewX = (TextView)findViewById(R.id.textskewx);
textSkewY = (TextView)findViewById(R.id.textskewy);
adapterScale = new ArrayAdapter(this,
android.R.layout.simple_spinner_item, strScale);
adapterScale.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinnerScale.setAdapter(adapterScale);
spinnerScale.setSelection(defaultSpinnerScaleSelection);
curScale = floatScale[defaultSpinnerScaleSelection];
bitmap = BitmapFactory.decodeFile(imageInSD);
bmpWidth = bitmap.getWidth();
bmpHeight = bitmap.getHeight();
drawMatrix();
spinnerScale.setOnItemSelectedListener(spinnerScaleOnItemSelectedListener);
seekbarRotate.setOnSeekBarChangeListener(seekbarRotateSeekBarChangeListener);
seekbarSkewX.setOnSeekBarChangeListener(seekbarSkewXSeekBarChangeListener);
seekbarSkewY.setOnSeekBarChangeListener(seekbarSkewYSeekBarChangeListener);
}
private void drawMatrix(){
Matrix matrix = new Matrix();
matrix.postScale(curScale, curScale);
matrix.postRotate(curRotate);
matrix.postSkew(curSkewX, curSkewY);
Bitmap resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0, bmpWidth, bmpHeight, matrix, true);
myImageView.setImageBitmap(resizedBitmap);
}
private SeekBar.OnSeekBarChangeListener seekbarSkewYSeekBarChangeListener
= new SeekBar.OnSeekBarChangeListener() {
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
// TODO Auto-generated method stub
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
// TODO Auto-generated method stub
}
@Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
// TODO Auto-generated method stub
curSkewY = (float)(progress-100)/100;
textSkewY.setText("Skew-Y: " + String.valueOf(curSkewY));
drawMatrix();
}
};
private SeekBar.OnSeekBarChangeListener seekbarSkewXSeekBarChangeListener
= new SeekBar.OnSeekBarChangeListener(){
@Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
// TODO Auto-generated method stub
curSkewX = (float)(progress-100)/100;
textSkewX.setText("Skew-X: " + String.valueOf(curSkewX));
drawMatrix();
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
// TODO Auto-generated method stub
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
// TODO Auto-generated method stub
}};
private SeekBar.OnSeekBarChangeListener seekbarRotateSeekBarChangeListener
= new SeekBar.OnSeekBarChangeListener(){
@Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
// TODO Auto-generated method stub
curRotate = (float)progress;
drawMatrix();
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
// TODO Auto-generated method stub
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
// TODO Auto-generated method stub
}};
private Spinner.OnItemSelectedListener spinnerScaleOnItemSelectedListener
= new Spinner.OnItemSelectedListener(){
@Override
public void onItemSelected(AdapterView arg0, View arg1,
int arg2, long arg3) {
// TODO Auto-generated method stub
curScale = floatScale[arg2];
drawMatrix();
}
@Override
public void onNothingSelected(AdapterView arg0) {
// TODO Auto-generated method stub
spinnerScale.setSelection(defaultSpinnerScaleSelection);
curScale = floatScale[defaultSpinnerScaleSelection];
}};
}
Sunday, July 18, 2010
OSGi
1.1. Overview
OSGi is a specification of a service and module platform in Java at runtime. The OSGi specification has several parts, the core of the OSGi specification defines a component and service model. This component model allows to activate, de-activate, update and de-install existing components and services and to install new components / services dynamically.
The smallest unit of modularization in OSGi is a bundle. OSGi defines a registry which bundles can use to publish services or register to other services.
The key features of OSGi can get summarized as:
-
Modularization
-
Runtime Dynamic
-
Service Orientation
In the authors personal opinion the strongest feature of OSGi is that you can define which package of your Java Projects should be visible to other Java projects. This way you can effectively control which Java classes in these projects can be used, e.g. define your API.
OSGi has several implementations, for example Knopflerfish OSGi or Apache Felix. Eclipse Equinox is currently the reference implementation of the OSGi specification.
Eclipse Equinox is the runtime environment on which the Eclipse IDE and Eclipse RCP application are based. In Eclipse the smallest unit of modularization is a plugin. The terms plugin and bundle are (almost) interchangable. An Eclipse plugin is also an OSGi bundle and vice versa.
The OSGi specification defines the OSGi bundle as the unit of modularization. A bundle is a cohesive, self-contained unit, which explicitly define its dependencies to other modules / services and explicitly defines its external API.
OSGi bundles are .jar files with additional meta information. This meta information is stored in the folder "META-INF" in the file "MANIFEST.MF". MANIFEST.MF is part of a standard jar specification. Any non-OSGI runtime will ignore the OSGI metadata. Therefore OSGi bundles can be used without restriction in non-OSGi Java environments.
Each bundle has a symbolic name which is defined via the property "Bundle-SymbolicName" in the MANIFEST.MF. Convention is that this name starts with the reverse domain name of the author of the bundle, e.g. "de.vogella.myfirstbundle".
Each bundle has also a version number in the property "Bundle-Version". This version number and the symbolic name uniquely identify a bundle in OSGi. The OSGi runtime can load the same bundle with different version numbers.
Via MANIFEST.MF a bundle can define its dependency to other bundles and services. A bundle can define that it depends on a certain version (or a range) of another bundle, e.g. bundle A can define that it depends on bundle C in version 2.0, while bundle B defines that it depends on version 1.0 of bundle C.
If a class wants to use a class from another bundles this dependency must be defined in the MANIFEST.MF, otherwise you will receive a ClassNotFound Exception. This restriction is enforced via a specific OSGi classloader.
MANIFEST.MF also defines the Java classes which should be available to other bundles as public API. This definition is done based on the packages name. Classes which are not exported via the MANIFEST.MF are not visible to other bundles. This restriction is enforced in OSGi via a special Java class loader. Access to the restricted classes is not possible, also not via reflection.
A bundles can register and use services in OSGi. OSGi provides therefore a central registry for this purpose. A service is defined by a Java interface (POJI - plain old Java interface).
Access to the service registry is performed via the class BundleContext. OSGi injects the BundleContext into each bundle during the startup of the bundle. A bundle can also register itself to the BundleContext ServiceEvents which are for example triggered if a new service is installed or de-installed.
OSGi is responsible for the dependency management between the bundles. These dependencies can be divided into:
-
Bundle (Package) Dependencies
-
Service Dependencies
OSGi reads the manifest.mf of a bundle during the installation of the plugin and ensures that all dependent bundles are also loaded if the bundle is activated. If the dependencies are not meet then the bundle is not loaded. Bundle / package dependencies are based on dependencies between standard Java objects and in case OSGi can not resolve all dependencies this results in a ClassNotFoundException.
As service in OSGi can be dynamically started and stopped, therefore the bundles must manage these dependencies themselves. The bundles can use the service listeners to get informed if a services is stared or stopped.
With the installation of a bundle in the OSGi runtime this bundle is persisted in a local bundle cache. The OSGi runtime is then trying to resolve all dependencies of the bundle. If all required dependencies are resolved the bundle is in the status "RESOLVED" otherwise it is in the status "INSTALLED". If several bundles exists which would satisfy the dependency then the bundle with the highest version is taking. If the version are the same then the bundle with the lowest ID is taken. If the bundle is started its status is "STARTING". Afterwards it is "ACTIVE".
The following is an example of a MANIFEST.MF.
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Popup Plug-in
Bundle-SymbolicName: de.vogella.rcp.intro.commands.popup; singleton:=true
Bundle-Version: 1.0.0
Bundle-Activator: de.vogella.rcp.intro.commands.popup.Activator
Require-Bundle: org.eclipse.ui,
org.eclipse.core.runtime
Bundle-ActivationPolicy: lazy
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Installation
You need to have Java installed. I recommended to use Java 1.6.
Download Eclipse 3.5 (Galileo) from the http://www.eclipse.org/ and unpack it to any directory. No installation procedure is required.
OSGI console
We will later use the OSGI console. This console is like a MS-DOS prompt. In this console you can type command to perform certain OSGI actions. For following is a reference of OSGi commands.
Use for example the command ss to get an overview of all bundles and their status.
Table 1. OSGi commands
Command | Desription |
---|---|
help | Lists the available commmands. |
ss | Gives you an overview of the installed bundles and their status. |
ss vogella | Gives you an overview of the bundles and their status if they have vogella within their name. |
start id | Starts the bundle with id |
stop id | Stops the bundle with id |
install URL | Installs a bundles from an URL |
uninstall id | Uninstalls the bundle with id |
services filter | Show all available services and their consumer. Filter is optional. |
Bundles can be identified via their id which is displayed by the command ss.
Your first OSGi bundle
The following will create a simple OSGi bundle and run it within Eclipse. At the end of this chapter you will also export your bundle to use it later in a standalone OSGi server.
Create the following thread class.
package de.vogella.osgi.firstbundle.internal;
public class MyThread extends Thread {
private volatile boolean active = true;
public void run() {
while (active) {
System.out.println("Hello OSGI console");
try {
Thread.sleep(5000);
} catch (Exception e) {
System.out.println("Thread interrupted " + e.getMessage());
}
}
}
public void stopThread() {
active = false;
}
}
Change the class Activator.java to the following.
package de.vogella.osgi.firstbundle;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import de.vogella.osgi.firstbundle.internal.MyThread;
public class Activator implements BundleActivator {
private MyThread myThread;
public void start(BundleContext context) throws Exception {
System.out.println("Starting de.vogella.osgi.firstbundle");
myThread = new MyThread();
myThread.start();
}
public void stop(BundleContext context) throws Exception {
System.out.println("Stopping de.vogella.osgi.firstbundle");
myThread.stopThread();
myThread.join();
}
}
Select your manifest.mf, right-click, select Run As-> Run Configuration. Create a OSGi Framework launch configuration. Deselect all bundles except your de.vogella.osgi.firstbundle. Press then "Add Required bundles".
Run this configuration. This should give you the following output. Every 5 second a new message should be written to the console.
Export your bundle. This will allow you to install it into a OSGi runtime. Select your bundle and choose File -> Export -> Plug-in Development -> "Deployable plug-ins and fragment".
Unflag the option to export the source.
Running a standalone OSGI server
After running and creating bundles in Eclipse this chapter will show how to run Equinox as a OSGI standalone runtime. .
In your Eclipse installation directory identify the file org.eclipse.osgi*.jar. This file should be in the "plugin" folder. Copy this jar file to a new place, e.g. c:\temp\osgi-server. Rename the file to "org.eclipse.osgi.jar".
Start your OSGI server via the following command.
java -jar org.eclipse.osgi.jar -console
You can use "install URL" to install a bundle from a certain URL. For example to install your bundle from "c:\temp\bundles" use:
install file:c:\temp\bundles\plugins\de.vogella.osgi.firstbundle_1.0.0.jar
Tip
You properly need to correct the path and the bundle name on your system.You can start then the bundle with start and the id.
Define a Service and service consumption
6.1. Overview
OSGi platform provides a flexible mechanism for provisioning functionality via services. In the following we will define and consume a service.
Our service will return "famous quote".
A service in OSGi is defined by a standard Java class or interface. It is common practice to define the service via a bundle which only contains the interface definition. This allows to change the implementation of the service via a different bundle.
Create a plugin project "de.vogella.osgi.quote". Do not use a template. You do not need an activator for this example.
Select the MANIFEST.MF and runtime tab. Add "de.vogella.osgi.quote" to the exported packages.
Create the following interface "IQuoteService".
package de.vogella.osgi.quote;
public interface IQuoteService {
String getQuote();
}
We will now define a bundle which will provide the service.
Create a plugin project "de.vogella.osgi.quoteservice". Do not use a template.
Select the MANIFEST.MF and dependecy tab. Add "de.vogella.osgi.quote" to the required plugins.
Create the following class "QuoteService".
package de.vogella.osgi.quoteservice.internal;
import java.util.Random;
import de.vogella.osgi.quote.IQuoteService;
public class QuoteService implements IQuoteService {
@Override
public String getQuote() {
Random random = new Random();
// Create a number between 0 and 2
int nextInt = random.nextInt(3);
switch (nextInt) {
case 0:
return "Tell them I said something";
case 1:
return "I feel better already";
default:
return "Hubba Bubba, Baby!";
}
}
}
Register the service in the class Activator.
package de.vogella.osgi.quoteservice;
import java.util.Hashtable;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import de.vogella.osgi.quote.IQuoteService;
import de.vogella.osgi.quoteservice.internal.QuoteService;
public class Activator implements BundleActivator {
public void start(BundleContext context) throws Exception {
IQuoteService service = new QuoteService();
// Third parameter is a hashmap which allows to configure the service
// Not required in this example
context.registerService(IQuoteService.class.getName(), service,
null);
System.out.println("IQuoteService is registered");
}
public void stop(BundleContext context) throws Exception {
}
}
Export your bundles and install them on your server. Start the service bundle.
Tip
Nothing fancy happens, as we are not yet consuming our service.Create a new plugin "de.vogella.osgi.quoteconsumer". Add also a dependency to the package "de.vogella.osgi.quote".
Tip
Please note that we have added the dependency against the package NOT against the plugin. This way we later replace the service with a different implementation.Lets register directly to the service and use it.
package de.vogella.osgi.quoteconsumer;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import de.vogella.osgi.quote.IQuoteService;
public class Activator implements BundleActivator {
private BundleContext context;
private IQuoteService service;
public void start(BundleContext context) throws Exception {
this.context = context;
// Register directly with the service
ServiceReference reference = context
.getServiceReference(IQuoteService.class.getName());
service = (IQuoteService) context.getService(reference);
System.out.println(service.getQuote());
}
public void stop(BundleContext context) throws Exception {
System.out.println(service.getQuote());
}
}
Export this bundle, install it and start and stop it. Everything work. But if you stop the service bundle then your receive an error.
The reason for this is that OSGi is a very dynamic environment and service may be registered and de-registered any time. The next chapter will use a service tracker to improve this.
OSGi provides a service to track service events (new service / stop services / started service).
Import for this the "org.osgi.util.tracker" package into the de.vogella.osgi.quoteconsumer project
To use this define the following class "MyQuoteServiceTrackerCustomizer"
package de.vogella.osgi.quoteconsumer;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.util.tracker.ServiceTrackerCustomizer;
import de.vogella.osgi.quote.IQuoteService;
public class MyQuoteServiceTrackerCustomizer implements
ServiceTrackerCustomizer {
private final BundleContext context;
public MyQuoteServiceTrackerCustomizer(BundleContext context) {
this.context = context;
}
private MyThread thread;
@Override
public Object addingService(ServiceReference reference) {
IQuoteService service = (IQuoteService) context.getService(reference);
thread = new MyThread(service);
thread.start();
return service;
}
@Override
public void modifiedService(ServiceReference reference, Object service) {
// removedService(reference, service);
// addingService(reference);
}
@Override
public void removedService(ServiceReference reference, Object service) {
context.ungetService(reference);
System.out.println("How sad. Service for quote is gone");
thread.stopThread();
}
public static class MyThread extends Thread {
private volatile boolean active = true;
private final IQuoteService service;
public MyThread(IQuoteService service) {
this.service = service;
}
public void run() {
while (active) {
System.out.println(service.getQuote());
try {
Thread.sleep(5000);
} catch (Exception e) {
System.out.println("Thread interrupted " + e.getMessage());
}
}
}
public void stopThread() {
active = false;
}
}
}
You also need to register a service tracker in your activator of your serviceconsumer.
package de.vogella.osgi.quoteconsumer;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.util.tracker.ServiceTracker;
import de.vogella.osgi.quote.IQuoteService;
public class Activator implements BundleActivator {
private ServiceTracker serviceTracker;
public void start(BundleContext context) throws Exception {
System.out.println("Starting quoteconsumer bundles");
// Register directly with the service
MyQuoteServiceTrackerCustomizer customer = new MyQuoteServiceTrackerCustomizer(
context);
serviceTracker = new ServiceTracker(context, IQuoteService.class
.getName(), customer);
serviceTracker.open();
}
public void stop(BundleContext context) throws Exception {
System.out.println("Stopping quoteconsumer bundles");
serviceTracker.close();
}
}
Export your bundle again. Start the OSGI console. Use the update command or the install command to get the new version of your bundle and start it. Once you start your service the tracker will be called and the consumer bundle will start writing messages to the console. Stop the service and verify that the consumer does not use the service anymore.
The problem with service trackers it they still obey Java rules. In case your service consumer keeps a reference of the service, this service can not get removed via the OSGi framework. The other disadvantage is that the service tracker require a lot of boilerplate code. To solve these issues declarative services were developed.