项目中 由于用户投诉, 新增了一个需求, 用户在看tv的时候, 关机重启后 仍然希望是进入tv界面下,而我们现在是不管上次关机是在tv还是launcher下, 重启以后都是进入launcher下。 想想这个需求是很正常的, 用户买回电视主要还是看tv的, 每次重启后都是进入launcher, 每次都需要手动切换到tv下, 用户不投诉才怪。
解决办法也比较简单,
一,
在系统属性中增加了一个属性, persist.sys.bootfromui, 进入launcher的时候 将persist.sys.bootfromui 设为true, 进入tv的时候将persist.sys.bootfromui设为false。 在ActivitStack.java 增加的代码如下:
final boolean resumeTopActivityLocked(ActivityRecord prev) {
1288 // Find the first activity that is not finishing.
1289 ActivityRecord next = topRunningActivityLocked(null);
1290
1291 // Remember how we'll process this pause/resume situation, and ensure
1292 // that the state is reset however we wind up proceeding.
1293 final boolean userLeaving = mUserLeaving;
1294 mUserLeaving = false;
1295
1296 if (next == null) {
1297 // There are no more activities! Let's just start up the
1298 // Launcher...
1299 if (mMainStack) {
1300 return mService.startHomeActivityLocked();
1301 }
1302 }
1303
1304 Slog.d(TAG,"resumeTopActivty");
1305 Slog.d(TAG, "------------liuwei-----------resumeTopActivty");
1306 if(next.packageName.equals("com.tcl.tv") || next.packageName.equals("com.tcl.common.bootsetwizard")){
1307 Slog.d(TAG, "set persist.sys.bootformui false ");
1308 SystemProperties.set("persist.sys.bootfromui","false");
1309 }else{
1310 Slog.d(TAG, "set persist.sys.bootformui true ");
1311 SystemProperties.set("persist.sys.bootfromui","true");
1312 }
二,每次在开机启动的时候根据 这个属性来判断是进入launcher 还是 TV, 在ActivityManagerService.java改动的代码如下
Slog.d(TAG, "----------liuwei------------startHomeActivityLocked");
boolean isStartUi = "true".equals(SystemProperties.get("persist.sys.bootfromui","false"));
if(isStartUi)
{
Slog.d(TAG, "----------zhuxiaolin------------isStartUi is true");
Intent intent = new Intent(
mTopAction,
mTopData != null ? Uri.parse(mTopData) : null);
intent.setComponent(mTopComponent);
if (mFactoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) {
intent.addCategory(Intent.CATEGORY_HOME);
}
ActivityInfo aInfo =
intent.resolveActivityInfo(mContext.getPackageManager(),
STOCK_PM_FLAGS);
if (aInfo != null) {
intent.setComponent(new ComponentName(
aInfo.applicationInfo.packageName, aInfo.name));
// Don't do this if the home app is currently being
// instrumented.
ProcessRecord app = getProcessRecordLocked(aInfo.processName,
aInfo.applicationInfo.uid);
if (app == null || app.instrumentationClass == null) {
intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
mMainStack.startActivityLocked(null, intent, null, null, 0, aInfo,
null, null, 0, 0, 0, false, false, null);
}
}
}else{
Slog.d(TAG, "----------liuwei------------isStartUi is false");
Intent mIntent = new Intent();
//mIntent.setClassName("com.tcl.cyberui","com.tcl.cyberui.MainActivity");
mIntent.setClassName("com.tcl.tv","com.tcl.tv.TVActivity");
mIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Slog.d(TAG, "----------zhuxiaolin------------ mContext.startActivity(mIntent)");
mContext.startActivity(mIntent);
}
三 引起的信源不一致问题开机记忆TV的问题解决了。但是后来测试却发现一个更严重的问题, 有时候开机前TV的信源是DTV, 开机后的信源却是ATV或其他。经过多次测试总结出了规律, 如果第一次关机前是在launcher 状态下, 第二次关机前是tv状态下,就会出现信源就会出现不一致的情况。也就是说只要连续两次关机前的状态不一致(一次是launcher, 一次是TV)就必定会出现这个问题。如果两次关机前都是在luancher下 或都是在TV下那么就不会出现信源不一致的问题。
有了这个规律,那么就大胆猜测是由于luancher启动的时候读取信源的位置和 tv启动读取信源的位置不一致。
关键就在与这个位置不知道保存在哪里, 因为底层代码是厂商的我们看不到,很可能是保存在某个数据库里的。
后来不停的测试,加打印log, 发现不管是launcher先启动,还是TV先启动, 都会调用TVCommonNative.getDefault的getService来获取TVCommonService,
static public ITVCommon getDefault(Context context) {
155 if (mContext == null && context !=null) {
156 mContext = context.getApplicationContext();
157 }
158
159 if (gDefault != null) {
160 return gDefault;
161 }
162 IBinder b = getService(TVCOMMON);
163 if (b == null) {
164 b = new TVCommonService();
165 TVLog(TVCOMMON
166 + "service is not exit in ServiceManager, try to add it to ServiceManager");
167 addService(TVCOMMON, b);
168 }
看到没, 第一次肯定都是走的红色代码,因为第一次都是空,先创建TVCommonService,在addService到ServiceManager。
再看看TVCommonService的构造函数:
88 TVCommonService() {
89
90 super();
91 tvMgr = TVManager.getInstance(getContext());
92 rawChSrv = (ChannelService) tvMgr
93 .getService(ChannelService.ChannelServiceName);
94 cfgSrv = (ConfigService)tvMgr
95 .getService(ConfigService.ConfigServiceName);
96 inputSrv = (InputService) tvMgr
97 .getService(InputService.InputServiceName);
98 brdcstSrv = (BroadcastService) tvMgr
99 .getService(BroadcastService.BrdcstServiceName);
100
101 mLooper = Looper.getMainLooper();
102 mQueue = Looper.myQueue();
103
104 mQueue.addIdleHandler(this);
105 handler = new Handler(mLooper, this);
106 storage = TVStorage.getInstance(getContext());
107 configurer = TVConfigurer.getInstance(getContext());
看到了,有个存储的东东叫TVStorage,
而且这个TVStorage是和context相关的!!!!!
也就是说, tv先启动的时候,这个TvStorage的context是tv 的, launcher先启动的时候context是launcher, 则也就导致了tv先启动和 luancher先启动 读取,存放信源的地方是不一致的。
我们来看看TVStorage的代码:
private TVStorage(Context context) {
pref = context.getSharedPreferences(TVCM_TABLE, Context.MODE_PRIVATE);
pref = tvContext.getSharedPreferences(TVCM_TABLE, Context.MODE_WORLD_WRITEABLE);
editor = pref.edit();
}
36
37 public String getString(String key, String def) {
38 return pref.getString(key, def);
39 }
40
41 public void setString(String key, String val) {
42 editor.putString(key, val);
43 editor.commit();
44
45 }
看到了吗,是放到context 的preference下, 熟悉java上层应用的人都熟悉吧。
我就是不知道preference这个东西,所以卡到context那里 不知道往下怎么跟踪了。 追到这个地方是另外一个同事发现的。
四 。 知道了原因 代码修改如下
private TVStorage(Context context) {
15 //改为指定用TV的context读写属性,所以要求第一个调用TV控制接口的应用有systemuid
16 Context tvContext;
17 try {
18 tvContext = context.createPackageContext("com.tcl.tv", Context.CONTEXT_IGNORE_SECURITY);
19 } catch (NameNotFoundException e) {
20 // TODO Auto-generated catch block
21 e.printStackTrace();
22 tvContext = context;
23 }
24 pref = tvContext.getSharedPreferences(TVCM_TABLE, Context.MODE_WORLD_WRITEABLE);
25 editor = pref.edit();
26 }
把context写死到tv context下了,即不管是tv先启动,还是launcher先启动, storage都是读取,存储到tv的preference下了。
五 。其实我对这个该法是有意见的,在TVCommonService里我们可以看到, 不仅仅是TVstorage要用到context, 其他还有地方用到context, 我们只修改了storage的context, 那么其他地方的没有改,有很大的可能会出现问题。如果都修改, 那么对代码的改动太大了, 很可能造成更多的问题。 出现这个问题的本质 是因为TVCommonService是和context相关的, 只要每次开机add service的context不固定, 就会出先问题。 我比较推荐的方法 还是 永远都是launcher先启动,永远都用launcher的context去add TVCommonService, 启动luancher后,由launcher去判断 persist.sys.bootfromui, 由launcher去决定起TV或是不起。 毕竟以前一直都是先起launcher的,而这样也最稳定, 改动也很小。 可惜launcher不是我负责的, 做luancher的不想这么改,暂时修改Storage ,写死用tv的context。在我看来,这种写法实在不好, 除了刚才我提到的原因,底层的东西怎么能写死上层的package name呢?