There have been many ideas expressed here, but I think the simplest and most robust solution is using a submerged mass suspended by a string as suggested by liudr. If I was making my own beer (I wish I was) it's the method I would use. Here are a few of the benefits:
1. If you want to leave it there all the time, you can.
2. It's easy to move to another tank if desired.
3. It will be immune to foam on the surface or submerged particles.
4. If you suspect bubbles have formed on it, just lower it until it hits bottom to knock them off.
5. If you suspect the liquid is stratified, simply take measurements at different levels to verify.
6. The weight and volume of the mass is not important, as long as it's large compared to the string.
7. It requires exactly 1 calibration ever - by taking 1 measurement in plain water.
I noticed no one posted any equations to calculate specific gravity. Not even those who claimed to have an understanding... so I'll post equations for liudr's method only:
float mAir; // mass suspended in air (load cell measurement)
float mWater; // mass suspended in water (load cell measurement)
float mBeer; // mass suspended in beer (load cell measuremet)
float bfWater; // buoyant force in water
float bfBeer; // buoyant force in beer
float sgBeer; // specific gravity of beer
...
bfBeer = mAir - mBeer;
bfWater = mAir - mWater;
sgBeer = bfBeer / bfWater;