渴望是所有成就的起点。
在Android Launcher 专属页和生活页的实现(一)章节中,讲解了采用自定义的widget加载到桌面来显示专属页,其中讲到Widget的界面并不是采用RemoteViews,而是用createPackageContext 获取的上下文来加载的其他包的view来替代RemoteView。
那我们可不可以完全脱离widget结构,直接获取其他APK的view加载到桌面呢?当然可以了。
获得APP的上下文Context
Context mRemovteContext = newWidgetContext(context, packagename);
public static Context newWidgetContext(Context context, String packageName) {
//CONTEXT_INCLUDE_CODE的意思是包括代码,也就是说可以执行这个包里面的代码
//CONTEXT_IGNORE_SECURITY的意思是忽略安全警告,如果不加这个标志的话,有些功能是用不了的,会出现安全警告。
int contextPermission = Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY;
Context theirContext = null;
try {
//获取其他应用的上下文
theirContext = context.createPackageContext(packageName, contextPermission);
} catch (NameNotFoundException e) {
e.printStackTrace();
}
return theirContext;
}
获得APP的View视图
根据获取的mRemovteContext获得APP视图View。
//"page_main"是其他APP的布局名称
View mRootView = createView(mRemoteContext, packagename, "page_main");
public static View createView(Context remoteContext, String packagename, String resource) {
Context theirContext = remoteContext;
if (theirContext == null) {
return null;
}
LayoutInflater theirInflater = (LayoutInflater) theirContext
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
theirInflater = theirInflater.cloneInContext(theirContext);
//通过其他包的上下文获得该包的Resources对象
Resources r = theirContext.getResources();
int id = 0;
//getIdentifier()方法可以方便的获各应用包下的指定资源ID。
//第一个参数为资源名称,第二个为资源属性是ID或者是Drawable或者是layout,第三个为包名。
id = r.getIdentifier(resource, "layout", packagename);
if (id == 0) {
Log.e(TAG, "ERROR! can't get root layout id.");
return null;
}
View v = null;
try {
//inflate方法的主要作用就是将xml转换成一个View对象,用于动态的创建布局
v = theirInflater.inflate(id, null);
} catch (Exception e) {
e.printStackTrace();
}
return v;
}
获得APP的函数并执行
要获取APP的函数,我们这里用到Java的反射机制,在上文获得了mRootView类对象,通过getClass方法获得mRootView的Class。再通过Class的getDeclaredMethod*()获取类自身声明的所有方法,包含public、protected和private方法。
从Launcher界面看,我们要获取以下信息:
1、预览图
public Drawable getPreviewImg() {
try {
Method m = null;
if (mRootView!= null) {
//getDeclaredMethod方法返回一个Method对象,该对象反映此Class对象所表示的类或接口的指定已声明方法。"getPreviewImg"为方法名称
m = mRootView.getClass().getDeclaredMethod("getPreviewImg", Integer.TYPE);
if (m != null) {
//invoke方法:对带有指定参数的指定对象调用由此Method对象mRootView表示的基础方法getPreviewImg。
return (Drawable) m.invoke(mRootView, 0);
}
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
2、应用的Title名称
和上文一样,同样采用java反射机制,不在详细叙述:
mRootView.getClass().getDeclaredMethod("getTitleName", Integer.TYPE);
3、页面滑动指示图标
同上文,
info.mRootView.getClass().getDeclaredMethod("getIndicatorIcons", Integer.TYPE);
4、Hotseat的三个快捷图标信息
info.mRootView.getClass().getDeclaredMethod("getDockIntent1Name", Integer.TYPE)
总结分析
········上文通过获得APP的上下文,在通过这个上下文获得APP的视图View。然后又在通过Java反射机制,获得APP的相关信息,在把这个View添加到Launcher的Workspace上,专属页就出现了。
········其实,通过上文的解析,我们可以想象出APP中那个View是如果实现的。
1、 获得APP的View视图
View mRootView = createView(mRemoteContext, packagename, "page_main");
该方法告诉了我们APP的布局文件是:page_main.xml
2、获得APP的函数并执行
mRootView.getClass().getDeclaredMethod("getPreviewImg", Integer.TYPE);
该方法告诉我们,获取的View对象有函数getPreviewImg(),是个自定义的函数,由此我们可以判断出page_main.xml是由一个自定的视图View类组成。并且改类中有:getPreviewImg()、getTitleName()、getIndicatorIcons()、getDockIntent1Name()等函数。
········这一节我们讲到了Java的反射机制。其实我们在Android Launcher 专属页和生活页的实现(一)章节中谈到的实现方法中,也要涉及到Java的反射机制,因为我们的专属页面要和Launcher桌面的生命周期同步,为什么要同步呢,为什么不是原生widget的那种生命周期呢,因为我们做专属页虽然有widget的躯壳,但是缺乏widget的更新机制,再加上我们的专属页比较复杂。所以,我们在Launcher的声明周期中,采用Java的反射机制同时响应专属页应用的相关函数。