I've used plenty of cocoa demo apps that stop working after 3 days or a month or so. How do they do that? What's the best way? Also,开发者_JS百科 what are the limitations?
(Disclaimer: I've never made a trial version before, only read up on the subject and used a bunch of them.)
The limitations all come from the fact that anything on the user's system, the user can modify. So:
- Clever cheapskates may alter your app's executable to stub out or otherwise defeat any check you make.
- You have to store the amount of time used (or, more lazily but not as user-friendly, the date they started using your app) somewhere. Wherever you store it, the user must be able to change it (since your app runs as them), which means that if they find it, they can reset the clock.
- It isn't possible if you run in a sandbox, unless you store the aforementioned time-tracking data in the user defaults or the Keychain, either of which might as well be in plain sight, or request the temporary exception entitlement for writing anywhere on the file-system. Time-limited trials can't be in the App Store anyway, but if either the App Store or sandboxing becomes required in a future version of Mac OS X, your time limit will break, and we can only hope it doesn't prevent the user from using your application entirely.
- There's also the matter of handling payments. One way would be to sell the app in the App Store, without any trial-enforcement code, and distribute a separate build yourself that always enforces a time limit. If you do handle payments yourself, you need to store a record of the user's license on the user's system, and you need to check that license. This then becomes vulnerable to the same problem: The user may forge a license or “borrow” (e.g., download from a warez site) someone else's.
The upside, of course, is that the user has some amount of time to try the application for free without having to cough up any money, so at the end of that time (if your application is good and fills their needs), they'll be more likely to buy.
At the end of the trial period, you have a choice of what happens:
- Lock the user out of the application entirely.
- Cut out features. Acorn does this.
- Let them open documents, but not save or print. (You can block screenshots, but then good luck handling bug reports.)
- Let them save or (if applicable) print, but degrade part or all of the document in some way. For visual creations, such as images, a watermark may work. For audio, you can limit the sampling rate to something unpleasant like 20 kHz or less. (There's a case here for having your own proprietary format that you always handle losslessly, and only degrading exports to common formats such as TIFF, JFIF, or AIFF.) Fission does this.
- Just nag them. (Can be combined with any of the above.)
- Nag them and put a delay on the user's ability to dismiss it. You can even increase the delay the longer the user goes without paying.
One good alternative to the trial period is to have a separate “free” version with fewer features (or with ads). This is especially common on both App Stores.
Another consideration is whether the trial period is days used or days since first use. The latter is easier to implement, since you just record the date of first use and do subtraction. The former is more user-friendly, as it does not punish the user for launching the app once, playing with it for five minutes, and coming back to it for a real trial 31 days later.
You can also implement a limit on number of launches. It's as simple to implement as days-since-first-use, but doesn't punish only playing with the app once.
Some users just won't pay. Some users will do practically anything to not pay.
So you need to strike a balance. You need to provide a basic level of difficulty so that the laziest cheapskates cannot simply defaults write com.example.yourapp DaysSinceFirstUse -int 0
and keep using your app forever, while not making your app so onerous to try out (much less pay for) that they don't.
So here are some things not to do:
- Attempt to enforce equality between the name of the user on their license (entered at purchase) and their name on their account or in the Address Book. There are a dozen different ways to write any name, and some people have multiple names (through marriage, aliases, legal name-changes, multiple languages, Star Trek fandom, etc.), so this or anything like it is a bogus check that will upset more legitimate users than discourage pirates.
- Hold the user's data hostage. See my above point about the merit of a proprietary format that you always handle losslessly. If you do always degrade output during trial, make that absolutely clear up front in the app's on-launch “This is a trial version” dialog.
- Require an internet connection. Not everybody has one (that can connect to arbitrary servers), and not everybody has one all of the time. Learn from the games industry: Don't alienate your users.
- Install any sort of copyright enforcement software that runs in the background and/or is in a separate place from your application. Users will rightly hate you for this.
As for how to do it, here's what I recommend:
- Implement a “days of actual use” check. This can be a selling point. It warms my heart when a trial explicitly says it uses this kind of check.
- I'd say store it in hours. On launch, get the current number of hours from wherever you store it. Add two hours and write it back (so the user can't force-quit your app to defeat this). On quit, add the real number of hours since launch to the originally-read number and write the revised number back.
- Store it in an invisible file in Application Support. Encrypt it (again, you want to defeat casual piracy), but don't waste too much time bulletproofing it. Remember, your app must contain everything to both encrypt it (to keep track) and decrypt it (to perform the check), so a sufficiently determined (and educated) cheapskate can break this no matter what you do.
- On launch, get the current number of hours from wherever you store it and test whether it's over the limit. (If you take my add-two-hours-right-away suggestion, do that after you test against the limit.) 30 days is 30×24=720 hours. If it's over the limit, enact your trial-expired measures.
- If you sell the software yourself, use symmetric public-key encryption for the license file. I think AquaticPrime does this. You encrypt licenses with your private key, and distribute the public key in the app, which uses the public key to decrypt and check the license. Practically unbreakable. You send license files to customers by email using the email address they provide at purchase. (Tell them that they will receive the license by email so they don't enter a made-up address.)
- If you do this, make sure you test entering the license, both before the trial ends and after it ends.
- Do the trial check only if there is no license.
- If you can sell your app in the App Store, I recommend you just do that. If you want to also distribute a trial version yourself, do that with no licensing code in place, so the trial check simply happens unconditionally. The App Store version, of course, does not need (and must not have) a trial check.
- Watch this. (Note: It pre-dates both of Apple's App Stores.)
In general, they save a count of days/hours/whatever used somewhere, e.g. in the app's user defaults.
Since it's fairly easy to change an app's user defaults, some write a simple hash to the file that has to match the number of days used. If not, they expire the build right then and there because a user obviously just mucked with that setting. Others keep several copies of the number of days used counter. If one is missing, they restore it based on the lowest number in one of the other locations.
Good locations are invisible files in locations the user wouldn't expect, maybe named so they look similar to a file by another app or a system file. But be careful that you don't litter files across the file system that then give the other app they look like a bad name. Also worth considering is writing a resource into a file's resource fork, where most people don't look anymore these days (one of your files! Another app or the system may replace their file and strip your info, or may use the same resource type and cause a collision).
Chances are, a casual hacker will try to edit the user defaults and then give up. A dedicated hacker will keep going no matter how much effort is put in the protection scheme, so it's not worth spending too much time on protecting it.
Some app developers instead generate a license key that has an expiration date in it and make the app refuse to run without a valid license key. There's a nice article by Allan Odgaard on how to sign information using OpenSSL (make sure you use LibreSSL or CommonCrypto.framework these days, which are very similar) to get the expiration date to your user without them being able to edit it: http://sigpipe.macromates.com/2004/09/05/using-openssl-for-license-keys/
Based on your ideas I made a short proof of concept in Java using Elliptic Curve Cryptography to generate a UUID on start up and then sign that UUID with ECC to create a registration key. The code is here if anyone wants it.1
精彩评论