[Android]开源中国源码分析之一---启动界面
开源中国android端版本号:2.4
启动界面:
在AndroidManifest.xml中找到程序的入口,
<activity android:name=".AppStart" android:label="@string/app_name" android:screenOrientation="portrait" android:theme="@style/Theme.AppStartLoad" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity>
屏幕方向设置为竖屏,主题为AppStartLoad,关于它的定义如下:
<style name="Theme.AppStartLoad" parent="android:Theme.Black.NoTitleBar.Fullscreen"> <item name="android:windowBackground">@drawable/welcome</item> <item name="android:windowNoTitle">true</item> </style>
welcome.png就是启动页显示的图片,填充了整个屏幕。
AppStart.java文件:
package net.oschina.app; import java.io.File; import net.oschina.app.ui.MainActivity; import net.oschina.app.util.TDevice; import org.kymjs.kjframe.http.KJAsyncTask; import org.kymjs.kjframe.utils.FileUtils; import org.kymjs.kjframe.utils.PreferenceHelper; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.view.animation.Animation.AnimationListener; /** * 应用启动界面 * * @author FireAnt(http://my.oschina.net/LittleDY) * @created 2014年12月22日 上午11:51:56 * */ public class AppStart extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 防止第三方跳转时出现双实例 Activity aty = AppManager.getActivity(MainActivity.class); if (aty != null && !aty.isFinishing()) { finish(); } // SystemTool.gc(this); //针对性能好的手机使用,加快应用相应速度 final View view = View.inflate(this, R.layout.app_start, null); setContentView(view); // 渐变展示启动屏 AlphaAnimation aa = new AlphaAnimation(0.5f, 1.0f); aa.setDuration(800); view.startAnimation(aa); aa.setAnimationListener(new AnimationListener() { @Override public void onAnimationEnd(Animation arg0) { redirectTo(); } @Override public void onAnimationRepeat(Animation animation) {} @Override public void onAnimationStart(Animation animation) {} }); } @Override protected void onResume() { super.onResume(); int cacheVersion = PreferenceHelper.readInt(this, "first_install", "first_install", -1); int currentVersion = TDevice.getVersionCode(); if (cacheVersion < currentVersion) { PreferenceHelper.write(this, "first_install", "first_install", currentVersion); cleanImageCache(); } } private void cleanImageCache() { final File folder = FileUtils.getSaveFolder("OSChina/imagecache"); KJAsyncTask.execute(new Runnable() { @Override public void run() { for (File file : folder.listFiles()) { file.delete(); } } }); } /** * 跳转到... */ private void redirectTo() { Intent uploadLog = new Intent(this, LogUploadService.class); startService(uploadLog); Intent intent = new Intent(this, MainActivity.class); startActivity(intent); finish(); } }
AppManager.java文件:
package net.oschina.app; import java.util.Stack; import android.app.Activity; import android.content.Context; /** * activity堆栈式管理 * * @author FireAnt(http://my.oschina.net/LittleDY) * @created 2014年10月30日 下午6:22:05 * */ public class AppManager { private static Stack<Activity> activityStack; private static AppManager instance; private AppManager() {} /** * 单一实例 */ public static AppManager getAppManager() { if (instance == null) { instance = new AppManager(); } return instance; } /** * 添加Activity到堆栈 */ public void addActivity(Activity activity) { if (activityStack == null) { activityStack = new Stack<Activity>(); } activityStack.add(activity); } /** * 获取当前Activity(堆栈中最后一个压入的) */ public Activity currentActivity() { Activity activity = activityStack.lastElement(); return activity; } /** * 结束当前Activity(堆栈中最后一个压入的) */ public void finishActivity() { Activity activity = activityStack.lastElement(); finishActivity(activity); } /** * 结束指定的Activity */ public void finishActivity(Activity activity) { if (activity != null && !activity.isFinishing()) { activityStack.remove(activity); activity.finish(); activity = null; } } /** * 结束指定类名的Activity */ public void finishActivity(Class<?> cls) { for (Activity activity : activityStack) { if (activity.getClass().equals(cls)) { finishActivity(activity); break; } } } /** * 结束所有Activity */ public void finishAllActivity() { for (int i = 0, size = activityStack.size(); i < size; i++) { if (null != activityStack.get(i)) { //finishActivity方法中的activity.isFinishing()方法会导致某些activity无法销毁 //貌似跳转的时候最后一个activity 是finishing状态,所以没有执行 //内部实现不是很清楚,但是实测结果如此,使用下面代码则没有问题 // find by TopJohn //finishActivity(activityStack.get(i)); activityStack.get(i).finish(); //break; } } activityStack.clear(); } /** * 获取指定的Activity * * @author kymjs */ public static Activity getActivity(Class<?> cls) { if (activityStack != null) for (Activity activity : activityStack) { if (activity.getClass().equals(cls)) { return activity; } } return null; } /** * 退出应用程序 */ public void AppExit(Context context) { try { finishAllActivity(); // 杀死该应用进程 android.os.Process.killProcess(android.os.Process.myPid()); System.exit(0); } catch (Exception e) { } } }
在上面onResume方法中,(这里单独拿出来),逻辑是更新版本号,查看当前安装的apk的版本号,与shared_prefs目录下first_install.xml文件中key为“first_install”对应的value的值的关系。
@Override protected void onResume() { super.onResume(); int cacheVersion = PreferenceHelper.readInt(this, "first_install", "first_install", -1); int currentVersion = TDevice.getVersionCode(); if (cacheVersion < currentVersion) { PreferenceHelper.write(this, "first_install", "first_install", currentVersion); cleanImageCache(); } }
first_install.xml
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<int name="first_install" value="48" />
</map>
TDevice文件下的getVersionCode()方法:
public static int getVersionCode() { int versionCode = 0; try { versionCode = BaseApplication .context() .getPackageManager() .getPackageInfo(BaseApplication.context().getPackageName(), 0).versionCode; } catch (PackageManager.NameNotFoundException ex) { versionCode = 0; } return versionCode; }
private void cleanImageCache() { final File folder = FileUtils.getSaveFolder("OSChina/imagecache"); KJAsyncTask.execute(new Runnable() { @Override public void run() { for (File file : folder.listFiles()) { file.delete(); } } }); }
getSaveFolder方法:
/** * 获取文件夹对象 * * @return 返回SD卡下的指定文件夹对象,若文件夹不存在则创建 */ public static File getSaveFolder(String folderName) { File file = new File(getSDCardPath() + File.separator + folderName + File.separator); file.mkdirs(); return file; }
在AppStart
中开启了一个服务LogUploadService
用来上传应用程序的日志。
在服务LogUploadService
被开启后,根据情况进行如下几种操作:
- 读取osc本地文件夹下的日志信息
- 如果日志信息为空,服务停止——
LogUploadService.this.stopSelf()
- 如果日志信息不位空,上传日志;
当某一个组件比如Activity,通过调用startService()方法来开启一个服务时,系统会调用onStartCommand()方法。一旦这个方法执行之后,服务就会被开启并在后台独立的运行。如果你实现了这个方法,你必须在任务完成后通过调用stopSelf()
或者stopService()
来停止该服务。(如果你仅仅只想提供绑定,你不需要实现这个方法)。
日志上传的操作,封装在了OSChinaApi中,并且通过report来区分是bug还是反馈意见。
/** * BUG上报 * * @param data * @param handler */ public static void uploadLog(String data, AsyncHttpResponseHandler handler) { uploadLog(data, "1", handler); } /** * 反馈意见 * * @param data * @param handler */ public static void feedback(String data, AsyncHttpResponseHandler handler) { uploadLog(data, "2", handler); }
uploadLog方法:
private static void uploadLog(String data, String report, AsyncHttpResponseHandler handler) { RequestParams params = new RequestParams(); params.put("app", "1"); params.put("report", report); params.put("msg", data); ApiHttpClient.post("action/api/user_report_to_admin", params, handler); }
ApiHttpClient的post方法:
public static void post(String partUrl, RequestParams params, AsyncHttpResponseHandler handler) { client.post(getAbsoluteApiUrl(partUrl), params, handler); log(new StringBuilder("POST ").append(partUrl).append("&") .append(params).toString()); }
getAbsoluteapiUrl方法:
public static String getAbsoluteApiUrl(String partUrl) { String url = partUrl; if (!partUrl.startsWith("http:") && !partUrl.startsWith("https:")) { url = String.format(API_URL, partUrl); } Log.d("BASE_CLIENT", "request:" + url); return url; }
API_URL的值:
private static String API_URL = "http://www.oschina.net/%s";